diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index e8e5938617e7..99e0070bdf60 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -18,10 +18,12 @@ from time import time from typing import Any, Dict, List, Optional, Tuple, Union, cast +import copy import numpy as np from qiskit import QiskitError from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit +from qiskit.circuit.parametertable import ParameterTable from qiskit.opflow.converters.converter_base import ConverterBase from qiskit.opflow.exceptions import OpflowError from qiskit.opflow.list_ops.list_op import ListOp @@ -104,6 +106,10 @@ def __init__( self._transpiled_circ_cache: Optional[List[Any]] = None self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True + self._reuse_circs = [] + self._reuse_parameter_tables = [] + self._preserved_parameter_tables = [] + self._reuse_global_phase = [] def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -301,6 +307,26 @@ def sample_circuits( self._transpiled_circ_cache = self.quantum_instance.transpile( circuits, pass_manager=self.quantum_instance.unbound_pass_manager ) + + # copy the original circuit stored in _transpiled_circ_cache + self._reuse_circs = [] + self._reuse_parameter_tables = [] + self._preserved_parameter_tables = [] + self._reuse_global_phase = [] + for circ in self._transpiled_circ_cache: + shadow = circ.copy() + shadow._increment_instances() + shadow._name_update() + self._reuse_circs.append([shadow]) + parameter_table_preserved = ParameterTable() + for param, instr in shadow._parameter_table.items(): + parameter_table_preserved[param] = instr + self._reuse_parameter_tables.append([parameter_table_preserved]) + self._preserved_parameter_tables.append( + [copy.deepcopy(shadow._parameter_table)] + ) + self._reuse_global_phase.append([shadow.global_phase]) + except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " @@ -313,6 +339,23 @@ def sample_circuits( circuit_sfns = list(self._circuit_ops_cache.values()) if param_bindings is not None: + if self._reuse_circs != []: + # copy quantum circuit if len(param_bindings) > 1 + append_size = len(param_bindings) - len(self._reuse_circs[0]) + for i, _ in enumerate(self._transpiled_circ_cache): + for _ in range(append_size): + shadow = self._reuse_circs[i][0].copy() + shadow._increment_instances() + shadow._name_update() + self._reuse_circs[i].append(shadow) + parameter_table_preserved = ParameterTable() + for param, instr in shadow._parameter_table.items(): + parameter_table_preserved[param] = instr + self._reuse_parameter_tables[i].append(parameter_table_preserved) + self._preserved_parameter_tables[i].append( + copy.deepcopy(shadow._parameter_table) + ) + self._reuse_global_phase[i].append(shadow.global_phase) if self._param_qobj: start_time = time() ready_circs = self._prepare_parameterized_run_config(param_bindings) @@ -320,11 +363,21 @@ def sample_circuits( logger.debug("Parameter conversion %.5f (ms)", (end_time - start_time) * 1000) else: start_time = time() - ready_circs = [ - circ.assign_parameters(_filter_params(circ, binding)) - for circ in self._transpiled_circ_cache - for binding in param_bindings - ] + if self._reuse_circs == []: + ready_circs = [ + circ.assign_parameters(_filter_params(circ, binding)) + for circ in self._transpiled_circ_cache + for binding in param_bindings + ] + else: + ready_circs = [] + for i, cached_circ in enumerate(self._transpiled_circ_cache): + for j, binding in enumerate(param_bindings): + self._reuse_circs[i][j].assign_parameters( + _filter_params(cached_circ, binding), inplace=True + ) + ready_circs.append(self._reuse_circs[i][j]) + end_time = time() logger.debug("Parameter binding %.5f (ms)", (end_time - start_time) * 1000) else: @@ -340,6 +393,34 @@ def sample_circuits( ready_circs, had_transpiled=self._transpile_before_bind ) + # restore the original parameter table and global phase in shadow_circs + for i, circ in enumerate(self._reuse_circs): + for j, _ in enumerate(circ): + for param in self._reuse_parameter_tables[i][j]: + # restore ParameterExpression to its pre-calculation state + for k, instr in enumerate(self._reuse_parameter_tables[i][j][param]): + instr[0].params = ( + self._preserved_parameter_tables[i][j][param] + .__getstate__()[k][0] + .params + ) + self._preserved_parameter_tables[i][j][param].__getstate__()[k][ + 0 + ].params = copy.deepcopy( + self._preserved_parameter_tables[i][j][param] + .__getstate__()[k][0] + .params + ) + circ[j]._parameter_table = self._reuse_parameter_tables[i][j] + circ[j].global_phase = self._reuse_global_phase[i][j] + parameter_table_preserved = ParameterTable() + for param, instr in circ[j]._parameter_table.items(): + parameter_table_preserved[param] = instr + self._reuse_parameter_tables[i][j] = parameter_table_preserved + self._preserved_parameter_tables[i][j] = copy.deepcopy( + self._reuse_parameter_tables[i][j] + ) + if param_bindings is not None and self._param_qobj: self._clean_parameterized_run_config()