From f4aec154928079a5ca738413484a2088e0f129bf Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Mon, 24 May 2021 12:31:25 +0900 Subject: [PATCH 01/14] transpile only ansatz with default --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 12 ++++++++++-- qiskit/opflow/converters/circuit_sampler.py | 7 ++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index 1a6d08dc47ef..da288a650795 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -104,6 +104,7 @@ def __init__( max_evals_grouped: int = 1, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None, + split_transpile: bool = False, ) -> None: """ @@ -139,6 +140,8 @@ def __init__( These are: the evaluation count, the optimizer parameters for the ansatz, the evaluated mean and the evaluated standard deviation.` quantum_instance: Quantum Instance or Backend + split_transpile: ``True`` allows transpiling circuits for ansatz and expectation + separately. """ validate_min("max_evals_grouped", max_evals_grouped, 1) if ansatz is None: @@ -156,6 +159,7 @@ def __init__( self._expectation = expectation self._user_valid_expectation = self._expectation is not None self._include_custom = include_custom + self._split_transpile = split_transpile self._expect_op = None super().__init__( @@ -197,7 +201,8 @@ def quantum_instance( super(VQE, self.__class__).quantum_instance.__set__(self, quantum_instance) self._circuit_sampler = CircuitSampler( - self._quantum_instance, param_qobj=is_aer_provider(self._quantum_instance.backend) + self._quantum_instance, param_qobj=is_aer_provider(self._quantum_instance.backend), + skip_transpile=self._split_transpile ) @property @@ -317,7 +322,10 @@ def construct_expectation( ) observable_meas = self.expectation.convert(StateFn(operator, is_measurement=True)) - ansatz_circuit_op = CircuitStateFn(wave_function) + if self._split_transpile: + ansatz_circuit_op = CircuitStateFn(self.quantum_instance.transpile(wave_function)[0]) + else: + ansatz_circuit_op = CircuitStateFn(wave_function) return observable_meas.compose(ansatz_circuit_op).reduce() def construct_circuit( diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 5605e3e0b099..331505fe3b56 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -58,6 +58,7 @@ def __init__( param_qobj: bool = False, attach_results: bool = False, caching: str = "last", + skip_transpile: bool = False, ) -> None: """ Args: @@ -97,6 +98,7 @@ def __init__( self._transpiled_circ_cache: Optional[List[Any]] = None self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True + self._skip_transpile = skip_transpile def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -294,7 +296,10 @@ def sample_circuits( circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] try: - self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) + if self._skip_transpile: + self._transpiled_circ_cache = circuits + else: + self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " From e02a0619cfea7d1cae65269e5ed2a9abe60960f2 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Tue, 8 Jun 2021 00:03:45 +0900 Subject: [PATCH 02/14] split-transpile with considration of layout in vqe --- .../algorithms/minimum_eigen_solvers/vqe.py | 19 ++++----- qiskit/opflow/converters/circuit_sampler.py | 41 ++++++++++++++++--- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index da288a650795..e05ba39d7aae 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -9,7 +9,6 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - """The Variational Quantum Eigensolver algorithm. See https://arxiv.org/abs/1304.3061 @@ -104,7 +103,7 @@ def __init__( max_evals_grouped: int = 1, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None, - split_transpile: bool = False, + split_transpile: bool = True, ) -> None: """ @@ -199,10 +198,8 @@ def quantum_instance( ) -> None: """set quantum_instance""" super(VQE, self.__class__).quantum_instance.__set__(self, quantum_instance) - self._circuit_sampler = CircuitSampler( - self._quantum_instance, param_qobj=is_aer_provider(self._quantum_instance.backend), - skip_transpile=self._split_transpile + self._quantum_instance, param_qobj=is_aer_provider(self._quantum_instance.backend) ) @property @@ -308,6 +305,7 @@ def construct_expectation( wave_function = self.ansatz.assign_parameters(param_dict) else: wave_function = self.ansatz.construct_circuit(parameter) + self._split_transpile = False # Expectation was never created , try to create one if self._expectation is None: @@ -322,10 +320,7 @@ def construct_expectation( ) observable_meas = self.expectation.convert(StateFn(operator, is_measurement=True)) - if self._split_transpile: - ansatz_circuit_op = CircuitStateFn(self.quantum_instance.transpile(wave_function)[0]) - else: - ansatz_circuit_op = CircuitStateFn(wave_function) + ansatz_circuit_op = CircuitStateFn(wave_function) return observable_meas.compose(ansatz_circuit_op).reduce() def construct_circuit( @@ -496,8 +491,12 @@ def _energy_evaluation( zip(self._ansatz_params, parameter_sets.transpose().tolist()) ) # type: Dict + common_circuit = self.ansatz if self._split_transpile else None + start_time = time() - sampled_expect_op = self._circuit_sampler.convert(self._expect_op, params=param_bindings) + sampled_expect_op = self._circuit_sampler.convert(self._expect_op, + params=param_bindings, + common_circuit=common_circuit) means = np.real(sampled_expect_op.eval()) if self._callback is not None: diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 0e9f821f6c4a..4f0f6f492490 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -58,7 +58,6 @@ def __init__( param_qobj: bool = False, attach_results: bool = False, caching: str = "last", - skip_transpile: bool = False, ) -> None: """ Args: @@ -100,7 +99,6 @@ def __init__( self._transpiled_circ_cache: Optional[List[Any]] = None self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True - self._skip_transpile = skip_transpile def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -149,6 +147,7 @@ def convert( self, operator: OperatorBase, params: Optional[Dict[Parameter, Union[float, List[float], List[List[float]]]]] = None, + common_circuit: Optional[QuantumCircuit] = None, ) -> OperatorBase: r""" Converts the Operator to one in which the CircuitStateFns are replaced by @@ -160,6 +159,7 @@ def convert( operator: The Operator to convert params: A dictionary mapping parameters to either single binding values or lists of binding values. + common_circuit: A circuit that will appears prefix of all circuits from operator Returns: The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns. @@ -217,7 +217,9 @@ def convert( # Don't pass circuits if we have in the cache, the sampling function knows to use the cache circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) + sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, + param_bindings=p_b, + common_circuit=common_circuit) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): @@ -268,6 +270,7 @@ def sample_circuits( self, circuit_sfns: Optional[List[CircuitStateFn]] = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None, + common_circuit: Optional[QuantumCircuit] = None, ) -> Dict[int, List[StateFn]]: r""" Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their @@ -281,6 +284,7 @@ def sample_circuits( Args: circuit_sfns: The list of CircuitStateFns to sample. param_bindings: The parameterizations to bind to each CircuitStateFn. + common_circuit: A circuit that will appears prefix of all circuits from circuit_sfns Returns: The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. @@ -298,8 +302,35 @@ def sample_circuits( circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] try: - if self._skip_transpile: - self._transpiled_circ_cache = circuits + if common_circuit is not None and len(circuits) > 1: + # 1. transpile a common circuit + transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] + layout = transpiled_common_circuit._layout + + # 2. transpile diff circuits + diff_circuits = [] + for circuit in circuits: + diff_circuit = circuit.copy() + del diff_circuit.data[0:len(common_circuit)] + diff_circuits.append(diff_circuit) + orig_layout = self.quantum_instance.compile_config['initial_layout'] + self.quantum_instance.compile_config['initial_layout'] = layout + diff_circuits = self.quantum_instance.transpile(diff_circuits) + self.quantum_instance.compile_config['initial_layout'] = orig_layout + + # 3. combine + transpiled_circuits = [] + for diff_circuit in diff_circuits: + transpiled_circuit = transpiled_common_circuit.copy() + for creg in diff_circuit.cregs: + if creg not in transpiled_circuit.cregs: + transpiled_circuit.add_register(creg) + for inst, qargs, cargs in diff_circuit.data: + transpiled_circuit.append(inst, qargs, cargs) + transpiled_circuits.append(transpiled_circuit) + + # 4. set transpiled circuit cache + self._transpiled_circ_cache = transpiled_circuits else: self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) except QiskitError: From 6106e832b1c1e03f4e1f8229a27158afc40180b6 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Wed, 9 Jun 2021 21:11:57 +0900 Subject: [PATCH 03/14] remove common_circuit as params --- .../algorithms/minimum_eigen_solvers/vqe.py | 34 +++---- qiskit/opflow/converters/circuit_sampler.py | 89 ++++++++++--------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index e05ba39d7aae..9334fe9fa726 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -14,34 +14,35 @@ See https://arxiv.org/abs/1304.3061 """ -from typing import Optional, List, Callable, Union, Dict import logging from time import time +from typing import Callable, Dict, List, Optional, Union + import numpy as np from qiskit import ClassicalRegister, QuantumCircuit from qiskit.circuit import Parameter from qiskit.circuit.library import RealAmplitudes -from qiskit.providers import BaseBackend -from qiskit.providers import Backend from qiskit.opflow import ( - OperatorBase, + CircuitSampler, + CircuitStateFn, ExpectationBase, ExpectationFactory, - StateFn, - CircuitStateFn, - ListOp, I, - CircuitSampler, + ListOp, + OperatorBase, + StateFn, ) from qiskit.opflow.gradients import GradientBase -from qiskit.utils.validation import validate_min +from qiskit.providers import Backend, BaseBackend from qiskit.utils.backend_utils import is_aer_provider from qiskit.utils.quantum_instance import QuantumInstance -from ..optimizers import Optimizer, SLSQP +from qiskit.utils.validation import validate_min + +from ..exceptions import AlgorithmError +from ..optimizers import SLSQP, Optimizer from ..variational_algorithm import VariationalAlgorithm, VariationalResult from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult -from ..exceptions import AlgorithmError logger = logging.getLogger(__name__) @@ -321,7 +322,7 @@ def construct_expectation( observable_meas = self.expectation.convert(StateFn(operator, is_measurement=True)) ansatz_circuit_op = CircuitStateFn(wave_function) - return observable_meas.compose(ansatz_circuit_op).reduce() + return observable_meas.compose(ansatz_circuit_op) def construct_circuit( self, @@ -491,12 +492,11 @@ def _energy_evaluation( zip(self._ansatz_params, parameter_sets.transpose().tolist()) ) # type: Dict - common_circuit = self.ansatz if self._split_transpile else None - start_time = time() - sampled_expect_op = self._circuit_sampler.convert(self._expect_op, - params=param_bindings, - common_circuit=common_circuit) + sampled_expect_op = self._circuit_sampler.convert( + self._expect_op, + params=param_bindings, + ) means = np.real(sampled_expect_op.eval()) if self._callback is not None: diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 4f0f6f492490..0f4d857b7d1c 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -24,6 +24,7 @@ from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit from qiskit.opflow.converters.converter_base import ConverterBase from qiskit.opflow.exceptions import OpflowError +from qiskit.opflow.list_ops.composed_op import ComposedOp from qiskit.opflow.list_ops.list_op import ListOp from qiskit.opflow.operator_base import OperatorBase from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn @@ -147,7 +148,6 @@ def convert( self, operator: OperatorBase, params: Optional[Dict[Parameter, Union[float, List[float], List[List[float]]]]] = None, - common_circuit: Optional[QuantumCircuit] = None, ) -> OperatorBase: r""" Converts the Operator to one in which the CircuitStateFns are replaced by @@ -159,7 +159,6 @@ def convert( operator: The Operator to convert params: A dictionary mapping parameters to either single binding values or lists of binding values. - common_circuit: A circuit that will appears prefix of all circuits from operator Returns: The converted Operator with CircuitStateFns replaced by DictStateFns or VectorStateFns. @@ -174,18 +173,6 @@ def convert( if self._caching == "last": self.clear_cache() - # convert to circuit and reduce - operator_dicts_replaced = operator.to_circuit_op() - self._reduced_op_cache = operator_dicts_replaced.reduce() - - # extract circuits - self._circuit_ops_cache = {} - self._extract_circuitstatefns(self._reduced_op_cache) - if not self._circuit_ops_cache: - raise OpflowError( - "Circuits are empty. " - "Check that the operator is an instance of CircuitStateFn or its ListOp." - ) self._transpiled_circ_cache = None self._transpile_before_bind = True else: @@ -215,11 +202,9 @@ def convert( num_parameterizations = 1 # Don't pass circuits if we have in the cache, the sampling function knows to use the cache - circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None + circs = operator if not self._transpiled_circ_cache else None p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, - param_bindings=p_b, - common_circuit=common_circuit) + sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): @@ -270,7 +255,6 @@ def sample_circuits( self, circuit_sfns: Optional[List[CircuitStateFn]] = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None, - common_circuit: Optional[QuantumCircuit] = None, ) -> Dict[int, List[StateFn]]: r""" Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their @@ -284,7 +268,6 @@ def sample_circuits( Args: circuit_sfns: The list of CircuitStateFns to sample. param_bindings: The parameterizations to bind to each CircuitStateFn. - common_circuit: A circuit that will appears prefix of all circuits from circuit_sfns Returns: The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. @@ -295,14 +278,25 @@ def sample_circuits( raise OpflowError("CircuitStateFn is empty and there is no cache.") if circuit_sfns: - self._transpiled_circ_templates = None - if self._statevector: - circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns] - else: - circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] - - try: - if common_circuit is not None and len(circuits) > 1: + self._reduced_op_cache = circuit_sfns.to_circuit_op().reduce() + self._circuit_ops_cache = {} + self._extract_circuitstatefns(self._reduced_op_cache) + if not self._circuit_ops_cache: + raise OpflowError( + "Circuits are empty. " + "Check that the operator is an instance of CircuitStateFn or its ListOp." + ) + circuits = [ + op_c.to_circuit(meas=not self._statevector) + for op_c in list(self._circuit_ops_cache.values()) + ] + if ( + isinstance(circuit_sfns, ComposedOp) + and len(circuit_sfns) >= 2 + and isinstance(circuit_sfns[1], CircuitStateFn) + ): + common_circuit = circuit_sfns[1].to_circuit_op().reduce().to_circuit() + try: # 1. transpile a common circuit transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] layout = transpiled_common_circuit._layout @@ -311,12 +305,12 @@ def sample_circuits( diff_circuits = [] for circuit in circuits: diff_circuit = circuit.copy() - del diff_circuit.data[0:len(common_circuit)] + del diff_circuit.data[0 : len(common_circuit)] diff_circuits.append(diff_circuit) - orig_layout = self.quantum_instance.compile_config['initial_layout'] - self.quantum_instance.compile_config['initial_layout'] = layout + orig_layout = self.quantum_instance.compile_config["initial_layout"] + self.quantum_instance.compile_config["initial_layout"] = layout diff_circuits = self.quantum_instance.transpile(diff_circuits) - self.quantum_instance.compile_config['initial_layout'] = orig_layout + self.quantum_instance.compile_config["initial_layout"] = orig_layout # 3. combine transpiled_circuits = [] @@ -331,18 +325,27 @@ def sample_circuits( # 4. set transpiled circuit cache self._transpiled_circ_cache = transpiled_circuits - else: + except QiskitError: + logger.debug( + r"CircuitSampler failed to transpile circuits with unbound " + r"parameters. Attempting to transpile only when circuits are bound " + r"now, but this can hurt performance due to repeated transpilation." + ) + self._transpile_before_bind = False + self._transpiled_circ_cache = circuits + else: + self._transpiled_circ_templates = None + try: self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) - except QiskitError: - logger.debug( - r"CircuitSampler failed to transpile circuits with unbound " - r"parameters. Attempting to transpile only when circuits are bound " - r"now, but this can hurt performance due to repeated transpilation." - ) - self._transpile_before_bind = False - self._transpiled_circ_cache = circuits - else: - circuit_sfns = list(self._circuit_ops_cache.values()) + except QiskitError: + logger.debug( + r"CircuitSampler failed to transpile circuits with unbound " + r"parameters. Attempting to transpile only when circuits are bound " + r"now, but this can hurt performance due to repeated transpilation." + ) + self._transpile_before_bind = False + self._transpiled_circ_cache = circuits + circuit_sfns = list(self._circuit_ops_cache.values()) if self._circuit_ops_cache else None if param_bindings is not None: if self._param_qobj: From f00d82dd50efa30f988d6d7696e61d2ef96f0a8e Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Thu, 10 Jun 2021 12:24:04 +0900 Subject: [PATCH 04/14] correct type information in CircuitSampler --- qiskit/opflow/converters/circuit_sampler.py | 35 +++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 0f4d857b7d1c..734afcb840c1 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -202,9 +202,9 @@ def convert( num_parameterizations = 1 # Don't pass circuits if we have in the cache, the sampling function knows to use the cache - circs = operator if not self._transpiled_circ_cache else None p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) + sampled_statefn_dicts = self.sample_circuits( + operator=operator if not self._transpiled_circ_cache else None, param_bindings=p_b) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): @@ -240,20 +240,21 @@ def clear_cache(self) -> None: """Clear the cache of sampled operator expressions.""" self._cached_ops = {} - def _extract_circuitstatefns(self, operator: OperatorBase) -> None: + def _extract_circuitstatefns( + self, operator: OperatorBase, result: Dict[int, CircuitStateFn]) -> None: r""" Recursively extract the ``CircuitStateFns`` contained in operator into the ``_circuit_ops_cache`` field. """ if isinstance(operator, CircuitStateFn): - self._circuit_ops_cache[id(operator)] = operator + result[id(operator)] = operator elif isinstance(operator, ListOp): for op in operator.oplist: - self._extract_circuitstatefns(op) + self._extract_circuitstatefns(op, result) def sample_circuits( self, - circuit_sfns: Optional[List[CircuitStateFn]] = None, + operator: OperatorBase = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None, ) -> Dict[int, List[StateFn]]: r""" @@ -266,7 +267,7 @@ def sample_circuits( in this list. Args: - circuit_sfns: The list of CircuitStateFns to sample. + operator: The Operator to convert param_bindings: The parameterizations to bind to each CircuitStateFn. Returns: @@ -274,13 +275,13 @@ def sample_circuits( Raises: OpflowError: if extracted circuits are empty. """ - if not circuit_sfns and not self._transpiled_circ_cache: + if not operator and not self._transpiled_circ_cache: raise OpflowError("CircuitStateFn is empty and there is no cache.") - if circuit_sfns: - self._reduced_op_cache = circuit_sfns.to_circuit_op().reduce() + if operator: + self._reduced_op_cache = operator.to_circuit_op().reduce() self._circuit_ops_cache = {} - self._extract_circuitstatefns(self._reduced_op_cache) + self._extract_circuitstatefns(self._reduced_op_cache, self._circuit_ops_cache) if not self._circuit_ops_cache: raise OpflowError( "Circuits are empty. " @@ -291,11 +292,12 @@ def sample_circuits( for op_c in list(self._circuit_ops_cache.values()) ] if ( - isinstance(circuit_sfns, ComposedOp) - and len(circuit_sfns) >= 2 - and isinstance(circuit_sfns[1], CircuitStateFn) + len(circuits) > 1 + and isinstance(operator, ComposedOp) + and len(operator) == 2 + and isinstance(operator[1], CircuitStateFn) ): - common_circuit = circuit_sfns[1].to_circuit_op().reduce().to_circuit() + common_circuit = operator[1].to_circuit_op().reduce().to_circuit() try: # 1. transpile a common circuit transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] @@ -345,7 +347,6 @@ def sample_circuits( ) self._transpile_before_bind = False self._transpiled_circ_cache = circuits - circuit_sfns = list(self._circuit_ops_cache.values()) if self._circuit_ops_cache else None if param_bindings is not None: if self._param_qobj: @@ -376,7 +377,7 @@ def sample_circuits( # self.quantum_instance._run_config.parameterizations = None sampled_statefn_dicts = {} - for i, op_c in enumerate(circuit_sfns): + for i, op_c in enumerate(self._circuit_ops_cache.values()): # Taking square root because we're replacing a statevector # representation of probabilities. reps = len(param_bindings) if param_bindings is not None else 1 From 8948a8f224655f0637458b9e10e1a0a96b071791 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Thu, 10 Jun 2021 20:10:54 +0900 Subject: [PATCH 05/14] refactor and lint --- .../algorithms/minimum_eigen_solvers/vqe.py | 9 ++- qiskit/opflow/converters/circuit_sampler.py | 65 ++++++++++--------- test/python/opflow/test_pauli_expectation.py | 12 +--- 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index 9334fe9fa726..19160589bec6 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -200,7 +200,9 @@ def quantum_instance( """set quantum_instance""" super(VQE, self.__class__).quantum_instance.__set__(self, quantum_instance) self._circuit_sampler = CircuitSampler( - self._quantum_instance, param_qobj=is_aer_provider(self._quantum_instance.backend) + self._quantum_instance, + param_qobj=is_aer_provider(self._quantum_instance.backend), + split_transpile=self._split_transpile, ) @property @@ -493,10 +495,7 @@ def _energy_evaluation( ) # type: Dict start_time = time() - sampled_expect_op = self._circuit_sampler.convert( - self._expect_op, - params=param_bindings, - ) + sampled_expect_op = self._circuit_sampler.convert(self._expect_op, params=param_bindings) means = np.real(sampled_expect_op.eval()) if self._callback is not None: diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 734afcb840c1..5ac47db1ed8c 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -31,6 +31,7 @@ from qiskit.opflow.state_fns.dict_state_fn import DictStateFn from qiskit.opflow.state_fns.state_fn import StateFn from qiskit.providers import Backend, BaseBackend +from qiskit.utils import deprecate_arguments from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend from qiskit.utils.quantum_instance import QuantumInstance @@ -59,6 +60,7 @@ def __init__( param_qobj: bool = False, attach_results: bool = False, caching: str = "last", + split_transpile: bool = True, ) -> None: """ Args: @@ -73,6 +75,8 @@ def __init__( the circuits. caching: The caching strategy. Can be `'last'` (default) to store the last operator that was converted, set to `'all'` to cache all processed operators. + split_transpile: ``True`` allows transpiling circuits for ansatz and expectation + separately. Raises: ValueError: Set statevector or param_qobj True when not supported by backend. @@ -100,6 +104,7 @@ def __init__( self._transpiled_circ_cache: Optional[List[Any]] = None self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True + self._split_transpile = split_transpile def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -204,7 +209,8 @@ def convert( # Don't pass circuits if we have in the cache, the sampling function knows to use the cache p_b = cast(List[Dict[Parameter, float]], param_bindings) sampled_statefn_dicts = self.sample_circuits( - operator=operator if not self._transpiled_circ_cache else None, param_bindings=p_b) + operator=operator if not self._transpiled_circ_cache else None, param_bindings=p_b + ) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): @@ -241,7 +247,8 @@ def clear_cache(self) -> None: self._cached_ops = {} def _extract_circuitstatefns( - self, operator: OperatorBase, result: Dict[int, CircuitStateFn]) -> None: + self, operator: OperatorBase, result: Dict[int, CircuitStateFn] + ) -> None: r""" Recursively extract the ``CircuitStateFns`` contained in operator into the ``_circuit_ops_cache`` field. @@ -252,10 +259,13 @@ def _extract_circuitstatefns( for op in operator.oplist: self._extract_circuitstatefns(op, result) + # pylint: disable=unused-argument + @deprecate_arguments({"circuit_sfns": "operator"}) def sample_circuits( self, - operator: OperatorBase = None, + operator: Optional[OperatorBase] = None, param_bindings: Optional[List[Dict[Parameter, float]]] = None, + circuit_sfns: Optional[List[CircuitStateFn]] = None, ) -> Dict[int, List[StateFn]]: r""" Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their @@ -269,6 +279,7 @@ def sample_circuits( Args: operator: The Operator to convert param_bindings: The parameterizations to bind to each CircuitStateFn. + circuit_sfns: [DEPRECATED] The list of CircuitStateFns to sample. Returns: The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. @@ -291,14 +302,15 @@ def sample_circuits( op_c.to_circuit(meas=not self._statevector) for op_c in list(self._circuit_ops_cache.values()) ] - if ( - len(circuits) > 1 - and isinstance(operator, ComposedOp) - and len(operator) == 2 - and isinstance(operator[1], CircuitStateFn) - ): - common_circuit = operator[1].to_circuit_op().reduce().to_circuit() - try: + try: + if ( + self._split_transpile + and len(circuits) > 1 + and isinstance(operator, ComposedOp) + and len(operator) == 2 + and isinstance(operator[1], CircuitStateFn) + ): + common_circuit = operator[1].to_circuit_op().reduce().to_circuit() # 1. transpile a common circuit transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] layout = transpiled_common_circuit._layout @@ -327,26 +339,17 @@ def sample_circuits( # 4. set transpiled circuit cache self._transpiled_circ_cache = transpiled_circuits - except QiskitError: - logger.debug( - r"CircuitSampler failed to transpile circuits with unbound " - r"parameters. Attempting to transpile only when circuits are bound " - r"now, but this can hurt performance due to repeated transpilation." - ) - self._transpile_before_bind = False - self._transpiled_circ_cache = circuits - else: - self._transpiled_circ_templates = None - try: + else: + self._transpiled_circ_templates = None self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) - except QiskitError: - logger.debug( - r"CircuitSampler failed to transpile circuits with unbound " - r"parameters. Attempting to transpile only when circuits are bound " - r"now, but this can hurt performance due to repeated transpilation." - ) - self._transpile_before_bind = False - self._transpiled_circ_cache = circuits + except QiskitError: + logger.debug( + r"CircuitSampler failed to transpile circuits with unbound " + r"parameters. Attempting to transpile only when circuits are bound " + r"now, but this can hurt performance due to repeated transpilation." + ) + self._transpile_before_bind = False + self._transpiled_circ_cache = circuits if param_bindings is not None: if self._param_qobj: @@ -396,7 +399,7 @@ def sample_circuits( # which must be converted to a complex value. avg = avg[0] + 1j * avg[1] # Will be replaced with just avg when eval is called later - num_qubits = circuit_sfns[0].num_qubits + num_qubits = next(iter(self._circuit_ops_cache.values())).num_qubits result_sfn = ( DictStateFn("0" * num_qubits, is_measurement=op_c.is_measurement) * avg ) diff --git a/test/python/opflow/test_pauli_expectation.py b/test/python/opflow/test_pauli_expectation.py index 39af9223d6f5..af071f6d24e7 100644 --- a/test/python/opflow/test_pauli_expectation.py +++ b/test/python/opflow/test_pauli_expectation.py @@ -198,19 +198,13 @@ def test_grouped_pauli_expectation(self): ) wf = CX @ (H ^ I) @ Zero expect_op = PauliExpectation(group_paulis=False).convert(~StateFn(two_qubit_H2) @ wf) - self.sampler._extract_circuitstatefns(expect_op) + self.sampler.convert(expect_op) num_circuits_ungrouped = len(self.sampler._circuit_ops_cache) self.assertEqual(num_circuits_ungrouped, 5) expect_op_grouped = PauliExpectation(group_paulis=True).convert(~StateFn(two_qubit_H2) @ wf) - q_instance = QuantumInstance( - BasicAer.get_backend("statevector_simulator"), - seed_simulator=self.seed, - seed_transpiler=self.seed, - ) - sampler = CircuitSampler(q_instance) - sampler._extract_circuitstatefns(expect_op_grouped) - num_circuits_grouped = len(sampler._circuit_ops_cache) + self.sampler.convert(expect_op_grouped) + num_circuits_grouped = len(self.sampler._circuit_ops_cache) self.assertEqual(num_circuits_grouped, 2) @unittest.skip(reason="IBMQ testing not available in general.") From bab18b565359a40d22390374acae5eb2718ead48 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Fri, 11 Jun 2021 14:50:18 +0900 Subject: [PATCH 06/14] remove unused qubits from layout when split-transpilation is enable in VQE --- qiskit/opflow/converters/circuit_sampler.py | 20 ++++++++++++++++---- qiskit/transpiler/layout.py | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 5ac47db1ed8c..2bdafd5c9283 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -313,7 +313,6 @@ def sample_circuits( common_circuit = operator[1].to_circuit_op().reduce().to_circuit() # 1. transpile a common circuit transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] - layout = transpiled_common_circuit._layout # 2. transpile diff circuits diff_circuits = [] @@ -321,10 +320,23 @@ def sample_circuits( diff_circuit = circuit.copy() del diff_circuit.data[0 : len(common_circuit)] diff_circuits.append(diff_circuit) - orig_layout = self.quantum_instance.compile_config["initial_layout"] - self.quantum_instance.compile_config["initial_layout"] = layout + + if transpiled_common_circuit._layout is not None: + layout = transpiled_common_circuit._layout.copy() + used_qubits = set([]) + for diff_circuit in diff_circuits: + for used_qubit in diff_circuit.qubits: + used_qubits.add(used_qubit) + for q in list(layout.get_virtual_bits().keys()): + if q not in used_qubits: + del layout[q] + orig_layout = self.quantum_instance.compile_config["initial_layout"] + self.quantum_instance.compile_config["initial_layout"] = layout + diff_circuits = self.quantum_instance.transpile(diff_circuits) - self.quantum_instance.compile_config["initial_layout"] = orig_layout + + if transpiled_common_circuit._layout is not None: + self.quantum_instance.compile_config["initial_layout"] = orig_layout # 3. combine transpiled_circuits = [] diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 81c156d57fb6..1f65341b92ad 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -123,11 +123,11 @@ def _set_type_checked_item(self, virtual, physical): def __delitem__(self, key): if isinstance(key, int): - del self._p2v[key] del self._v2p[self._p2v[key]] + del self._p2v[key] elif isinstance(key, Qubit): - del self._v2p[key] del self._p2v[self._v2p[key]] + del self._v2p[key] else: raise LayoutError( "The key to remove should be of the form" From ce724222305dd883d2c1b61a630dc9757192a387 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Wed, 16 Jun 2021 14:37:30 +0900 Subject: [PATCH 07/14] move transpile from sample_circuits to convert --- qiskit/opflow/converters/circuit_sampler.py | 190 +++++++++++--------- 1 file changed, 106 insertions(+), 84 deletions(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 2bdafd5c9283..b2efe8503670 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -31,7 +31,6 @@ from qiskit.opflow.state_fns.dict_state_fn import DictStateFn from qiskit.opflow.state_fns.state_fn import StateFn from qiskit.providers import Backend, BaseBackend -from qiskit.utils import deprecate_arguments from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend from qiskit.utils.quantum_instance import QuantumInstance @@ -105,6 +104,7 @@ def __init__( self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True self._split_transpile = split_transpile + self._had_transpiled = False def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -170,6 +170,7 @@ def convert( Raises: OpflowError: if extracted circuits are empty. """ + self._had_transpiled = False # check if the operator should be cached op_id = operator.instance_id # op_id = id(operator) @@ -178,8 +179,93 @@ def convert( if self._caching == "last": self.clear_cache() - self._transpiled_circ_cache = None - self._transpile_before_bind = True + if ( + self._split_transpile + and isinstance(operator, ComposedOp) + and len(operator) == 2 + and isinstance(operator[1], CircuitStateFn) + ): + self._reduced_op_cache = operator.to_circuit_op().reduce() + self._circuit_ops_cache = {} + self._extract_circuitstatefns(self._reduced_op_cache) + if not self._circuit_ops_cache: + raise OpflowError( + "Circuits are empty. " + "Check that the operator is an instance of CircuitStateFn or its ListOp." + ) + circuits = [ + op_c.to_circuit(meas=not self._statevector) + for op_c in list(self._circuit_ops_cache.values()) + ] + + common_circuit = operator[1].to_circuit_op().reduce().to_circuit() + try: + # 1. transpile a common circuit + transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] + + # 2. transpile diff circuits + diff_circuits = [] + for circuit in circuits: + diff_circuit = circuit.copy() + del diff_circuit.data[0 : len(common_circuit)] + diff_circuits.append(diff_circuit) + + if transpiled_common_circuit._layout is not None: + layout = transpiled_common_circuit._layout.copy() + used_qubits = set([]) + for diff_circuit in diff_circuits: + for used_qubit in diff_circuit.qubits: + used_qubits.add(used_qubit) + for q in list(layout.get_virtual_bits().keys()): + if q not in used_qubits: + del layout[q] + orig_layout = self.quantum_instance.compile_config["initial_layout"] + self.quantum_instance.compile_config["initial_layout"] = layout + + diff_circuits = self.quantum_instance.transpile(diff_circuits) + + if transpiled_common_circuit._layout is not None: + self.quantum_instance.compile_config["initial_layout"] = orig_layout + + # 3. combine + transpiled_circuits = [] + for diff_circuit in diff_circuits: + transpiled_circuit = transpiled_common_circuit.copy() + for creg in diff_circuit.cregs: + if creg not in transpiled_circuit.cregs: + transpiled_circuit.add_register(creg) + for inst, qargs, cargs in diff_circuit.data: + transpiled_circuit.append(inst, qargs, cargs) + transpiled_circuits.append(transpiled_circuit) + + # 4. set transpiled circuit cache + self._transpiled_circ_cache = transpiled_circuits + self._transpile_before_bind = False + self._had_transpiled = True + except QiskitError: + logger.debug( + r"CircuitSampler failed to transpile circuits with unbound " + r"parameters. Attempting to transpile only when circuits are bound " + r"now, but this can hurt performance due to repeated transpilation." + ) + self._transpile_before_bind = False + self._transpiled_circ_cache = circuits + else: + # convert to circuit and reduce + operator_dicts_replaced = operator.to_circuit_op() + self._reduced_op_cache = operator_dicts_replaced.reduce() + + # extract circuits + self._circuit_ops_cache = {} + self._extract_circuitstatefns(self._reduced_op_cache) + if not self._circuit_ops_cache: + raise OpflowError( + "Circuits are empty. " + "Check that the operator is an instance of CircuitStateFn or its ListOp." + ) + + self._transpiled_circ_cache = None + self._transpile_before_bind = True else: # load the cached circuits self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache @@ -207,10 +293,9 @@ def convert( num_parameterizations = 1 # Don't pass circuits if we have in the cache, the sampling function knows to use the cache + circs = list(self._circuit_ops_cache.values()) if not self._transpiled_circ_cache else None p_b = cast(List[Dict[Parameter, float]], param_bindings) - sampled_statefn_dicts = self.sample_circuits( - operator=operator if not self._transpiled_circ_cache else None, param_bindings=p_b - ) + sampled_statefn_dicts = self.sample_circuits(circuit_sfns=circs, param_bindings=p_b) def replace_circuits_with_dicts(operator, param_index=0): if isinstance(operator, CircuitStateFn): @@ -246,26 +331,21 @@ def clear_cache(self) -> None: """Clear the cache of sampled operator expressions.""" self._cached_ops = {} - def _extract_circuitstatefns( - self, operator: OperatorBase, result: Dict[int, CircuitStateFn] - ) -> None: + def _extract_circuitstatefns(self, operator: OperatorBase) -> None: r""" Recursively extract the ``CircuitStateFns`` contained in operator into the ``_circuit_ops_cache`` field. """ if isinstance(operator, CircuitStateFn): - result[id(operator)] = operator + self._circuit_ops_cache[id(operator)] = operator elif isinstance(operator, ListOp): for op in operator.oplist: - self._extract_circuitstatefns(op, result) + self._extract_circuitstatefns(op) - # pylint: disable=unused-argument - @deprecate_arguments({"circuit_sfns": "operator"}) def sample_circuits( self, - operator: Optional[OperatorBase] = None, - param_bindings: Optional[List[Dict[Parameter, float]]] = None, circuit_sfns: Optional[List[CircuitStateFn]] = None, + param_bindings: Optional[List[Dict[Parameter, float]]] = None, ) -> Dict[int, List[StateFn]]: r""" Samples the CircuitStateFns and returns a dict associating their ``id()`` values to their @@ -277,83 +357,23 @@ def sample_circuits( in this list. Args: - operator: The Operator to convert + circuit_sfns: The list of CircuitStateFns to sample. param_bindings: The parameterizations to bind to each CircuitStateFn. - circuit_sfns: [DEPRECATED] The list of CircuitStateFns to sample. Returns: The dictionary mapping ids of the CircuitStateFns to their replacement StateFns. Raises: OpflowError: if extracted circuits are empty. """ - if not operator and not self._transpiled_circ_cache: + if not circuit_sfns and not self._transpiled_circ_cache: raise OpflowError("CircuitStateFn is empty and there is no cache.") - if operator: - self._reduced_op_cache = operator.to_circuit_op().reduce() - self._circuit_ops_cache = {} - self._extract_circuitstatefns(self._reduced_op_cache, self._circuit_ops_cache) - if not self._circuit_ops_cache: - raise OpflowError( - "Circuits are empty. " - "Check that the operator is an instance of CircuitStateFn or its ListOp." - ) - circuits = [ - op_c.to_circuit(meas=not self._statevector) - for op_c in list(self._circuit_ops_cache.values()) - ] - try: - if ( - self._split_transpile - and len(circuits) > 1 - and isinstance(operator, ComposedOp) - and len(operator) == 2 - and isinstance(operator[1], CircuitStateFn) - ): - common_circuit = operator[1].to_circuit_op().reduce().to_circuit() - # 1. transpile a common circuit - transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] + if circuit_sfns and not self._had_transpiled: + self._transpiled_circ_templates = None + circuits = [op_c.to_circuit(meas=not self._statevector) for op_c in circuit_sfns] - # 2. transpile diff circuits - diff_circuits = [] - for circuit in circuits: - diff_circuit = circuit.copy() - del diff_circuit.data[0 : len(common_circuit)] - diff_circuits.append(diff_circuit) - - if transpiled_common_circuit._layout is not None: - layout = transpiled_common_circuit._layout.copy() - used_qubits = set([]) - for diff_circuit in diff_circuits: - for used_qubit in diff_circuit.qubits: - used_qubits.add(used_qubit) - for q in list(layout.get_virtual_bits().keys()): - if q not in used_qubits: - del layout[q] - orig_layout = self.quantum_instance.compile_config["initial_layout"] - self.quantum_instance.compile_config["initial_layout"] = layout - - diff_circuits = self.quantum_instance.transpile(diff_circuits) - - if transpiled_common_circuit._layout is not None: - self.quantum_instance.compile_config["initial_layout"] = orig_layout - - # 3. combine - transpiled_circuits = [] - for diff_circuit in diff_circuits: - transpiled_circuit = transpiled_common_circuit.copy() - for creg in diff_circuit.cregs: - if creg not in transpiled_circuit.cregs: - transpiled_circuit.add_register(creg) - for inst, qargs, cargs in diff_circuit.data: - transpiled_circuit.append(inst, qargs, cargs) - transpiled_circuits.append(transpiled_circuit) - - # 4. set transpiled circuit cache - self._transpiled_circ_cache = transpiled_circuits - else: - self._transpiled_circ_templates = None - self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) + try: + self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " @@ -362,6 +382,8 @@ def sample_circuits( ) self._transpile_before_bind = False self._transpiled_circ_cache = circuits + else: + circuit_sfns = list(self._circuit_ops_cache.values()) if param_bindings is not None: if self._param_qobj: @@ -392,7 +414,7 @@ def sample_circuits( # self.quantum_instance._run_config.parameterizations = None sampled_statefn_dicts = {} - for i, op_c in enumerate(self._circuit_ops_cache.values()): + for i, op_c in enumerate(circuit_sfns): # Taking square root because we're replacing a statevector # representation of probabilities. reps = len(param_bindings) if param_bindings is not None else 1 @@ -411,7 +433,7 @@ def sample_circuits( # which must be converted to a complex value. avg = avg[0] + 1j * avg[1] # Will be replaced with just avg when eval is called later - num_qubits = next(iter(self._circuit_ops_cache.values())).num_qubits + num_qubits = circuit_sfns[0].num_qubits result_sfn = ( DictStateFn("0" * num_qubits, is_measurement=op_c.is_measurement) * avg ) From 7b13a91d8bfc03576dafc9c5e11bf2eb3f4fe601 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Thu, 17 Jun 2021 19:42:53 +0900 Subject: [PATCH 08/14] fix lint --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index bb0a0f29b1d2..dda214790b74 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -38,7 +38,6 @@ from qiskit.utils import QuantumInstance, algorithm_globals from qiskit.utils.backend_utils import is_aer_provider from qiskit.utils.deprecation import deprecate_function -from qiskit.utils.quantum_instance import QuantumInstance from qiskit.utils.validation import validate_min from ..exceptions import AlgorithmError From c65902a410578968a3169fcb1178f981acc5e8d7 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Fri, 18 Jun 2021 12:42:00 +0900 Subject: [PATCH 09/14] remove unnecessary params --- .../algorithms/minimum_eigen_solvers/vqe.py | 3 +-- qiskit/opflow/converters/circuit_sampler.py | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index dda214790b74..dc9030d5344e 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -374,7 +374,6 @@ def construct_expectation( observable_meas = expectation.convert(StateFn(operator, is_measurement=True)) ansatz_circuit_op = CircuitStateFn(wave_function) - expect_op = observable_meas.compose(ansatz_circuit_op) if return_expectation: @@ -424,7 +423,7 @@ def _eval_aux_ops( threshold: float = 1e-12, ) -> np.ndarray: # Create new CircuitSampler to avoid breaking existing one's caches. - sampler = CircuitSampler(self.quantum_instance) + sampler = CircuitSampler(self.quantum_instance, split_transpile=self._split_transpile) aux_op_meas = expectation.convert(StateFn(ListOp(aux_operators), is_measurement=True)) aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.ansatz.bind_parameters(parameters))) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index b2efe8503670..096746b48665 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -104,7 +104,6 @@ def __init__( self._transpiled_circ_templates: Optional[List[Any]] = None self._transpile_before_bind = True self._split_transpile = split_transpile - self._had_transpiled = False def _check_quantum_instance_and_modes_consistent(self) -> None: """Checks whether the statevector and param_qobj settings are compatible with the @@ -170,7 +169,6 @@ def convert( Raises: OpflowError: if extracted circuits are empty. """ - self._had_transpiled = False # check if the operator should be cached op_id = operator.instance_id # op_id = id(operator) @@ -212,10 +210,11 @@ def convert( if transpiled_common_circuit._layout is not None: layout = transpiled_common_circuit._layout.copy() - used_qubits = set([]) - for diff_circuit in diff_circuits: - for used_qubit in diff_circuit.qubits: - used_qubits.add(used_qubit) + used_qubits = set( + used_qubit + for diff_circuit in diff_circuits + for used_qubit in diff_circuit.qubits + ) for q in list(layout.get_virtual_bits().keys()): if q not in used_qubits: del layout[q] @@ -240,8 +239,7 @@ def convert( # 4. set transpiled circuit cache self._transpiled_circ_cache = transpiled_circuits - self._transpile_before_bind = False - self._had_transpiled = True + self._transpile_before_bind = True except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " @@ -368,9 +366,12 @@ def sample_circuits( if not circuit_sfns and not self._transpiled_circ_cache: raise OpflowError("CircuitStateFn is empty and there is no cache.") - if circuit_sfns and not self._had_transpiled: + if circuit_sfns: self._transpiled_circ_templates = None - circuits = [op_c.to_circuit(meas=not self._statevector) for op_c in circuit_sfns] + if self._statevector: + circuits = [op_c.to_circuit(meas=False) for op_c in circuit_sfns] + else: + circuits = [op_c.to_circuit(meas=True) for op_c in circuit_sfns] try: self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) From d25c5c708e7d68764327093def76ab262bb44881 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Fri, 18 Jun 2021 13:19:55 +0900 Subject: [PATCH 10/14] remove param stateless vqe doesn't use --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index dc9030d5344e..43b5b5897bd1 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -170,7 +170,6 @@ def __init__( self._expectation = expectation self._include_custom = include_custom self._split_transpile = split_transpile - self._expect_op = None # set ansatz -- still supporting pre 0.18.0 sorting self._sort_parameters_by_name = sort_parameters_by_name From 7418ac2a5543e8795f5bdec5725b1deaeb54ee54 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Tue, 29 Jun 2021 20:10:10 +0900 Subject: [PATCH 11/14] final layout hack --- qiskit/opflow/converters/circuit_sampler.py | 42 +++++++++----- .../python/opflow/test_state_op_meas_evals.py | 58 +++++++++++++++++-- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 29d8143f49c3..712f3f23a126 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -31,6 +31,7 @@ from qiskit.opflow.state_fns.dict_state_fn import DictStateFn from qiskit.opflow.state_fns.state_fn import StateFn from qiskit.providers import Backend, BaseBackend +from qiskit.transpiler.layout import Layout from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend from qiskit.utils.quantum_instance import QuantumInstance @@ -197,34 +198,41 @@ def convert( ] common_circuit = operator[1].to_circuit_op().reduce().to_circuit() + len_common_circuit = len(common_circuit) try: # 1. transpile a common circuit + common_circuit.measure_all() transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0] + layout = Layout( + { + i: qr[0] + for i, (_, qr, _) in enumerate( + transpiled_common_circuit[-common_circuit.num_qubits :] + ) + } + ) + transpiled_common_circuit.remove_final_measurements() # 2. transpile diff circuits diff_circuits = [] for circuit in circuits: diff_circuit = circuit.copy() - del diff_circuit.data[0 : len(common_circuit)] + del diff_circuit.data[0 : len_common_circuit] diff_circuits.append(diff_circuit) - if transpiled_common_circuit._layout is not None: - layout = transpiled_common_circuit._layout.copy() - used_qubits = set( - used_qubit - for diff_circuit in diff_circuits - for used_qubit in diff_circuit.qubits - ) - for q in list(layout.get_virtual_bits().keys()): - if q not in used_qubits: - del layout[q] - orig_layout = self.quantum_instance.compile_config["initial_layout"] - self.quantum_instance.compile_config["initial_layout"] = layout + used_qubits = set( + used_qubit + for diff_circuit in diff_circuits + for used_qubit in diff_circuit.qubits + ) + for q in list(layout.get_virtual_bits().keys()): + if q not in used_qubits: + del layout[q] + orig_layout = self.quantum_instance.compile_config["initial_layout"] + self.quantum_instance.compile_config["initial_layout"] = layout diff_circuits = self.quantum_instance.transpile(diff_circuits) - - if transpiled_common_circuit._layout is not None: - self.quantum_instance.compile_config["initial_layout"] = orig_layout + self.quantum_instance.compile_config["initial_layout"] = orig_layout # 3. combine transpiled_circuits = [] @@ -240,6 +248,7 @@ def convert( # 4. set transpiled circuit cache self._transpiled_circ_cache = transpiled_circuits self._transpile_before_bind = True + except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " @@ -375,6 +384,7 @@ def sample_circuits( try: self._transpiled_circ_cache = self.quantum_instance.transpile(circuits) + except QiskitError: logger.debug( r"CircuitSampler failed to transpile circuits with unbound " diff --git a/test/python/opflow/test_state_op_meas_evals.py b/test/python/opflow/test_state_op_meas_evals.py index 5b7c84abb427..3466dcb95118 100644 --- a/test/python/opflow/test_state_op_meas_evals.py +++ b/test/python/opflow/test_state_op_meas_evals.py @@ -17,13 +17,29 @@ import unittest from test.python.opflow import QiskitOpflowTestCase -from ddt import ddt, data -import numpy -from qiskit.circuit import QuantumCircuit, Parameter -from qiskit.utils import QuantumInstance -from qiskit.opflow import StateFn, Zero, One, H, X, I, Z, Plus, Minus, CircuitSampler, ListOp +import numpy +from ddt import data, ddt + +from qiskit import BasicAer +from qiskit.circuit import Parameter, QuantumCircuit +from qiskit.circuit.library import RealAmplitudes +from qiskit.opflow import ( + CircuitSampler, + H, + I, + ListOp, + Minus, + One, + PauliExpectation, + Plus, + StateFn, + X, + Z, + Zero, +) from qiskit.opflow.exceptions import OpflowError +from qiskit.utils import QuantumInstance @ddt @@ -217,6 +233,38 @@ def test_quantum_instance_with_backend_shots(self): res = sampler.convert(~Plus @ Plus).eval() self.assertAlmostEqual(res, 1 + 0j, places=2) + def test_split_transpile(self): + """Test split transpile for CircuitSampler""" + ansatz = StateFn(RealAmplitudes(num_qubits=3, reps=1, entanglement="linear")) + observable = StateFn(X ^ I ^ I, is_measurement=True) + expectation = PauliExpectation(False).convert(observable @ ansatz) + param_bindings = dict(zip(ansatz.parameters, [1] * len(ansatz.parameters))) + sampled = CircuitSampler( + QuantumInstance( + BasicAer.get_backend("qasm_simulator"), + seed_simulator=15, + seed_transpiler=15, + initial_layout=[0, 1, 2], + coupling_map=[[0, 1], [0, 2]], + ), + attach_results=True, + split_transpile=False, + ).convert(expectation, param_bindings) + counts_expected = sampled[1].execution_results["counts"] + sampled = CircuitSampler( + QuantumInstance( + BasicAer.get_backend("qasm_simulator"), + seed_simulator=15, + seed_transpiler=15, + initial_layout=[0, 1, 2], + coupling_map=[[0, 1], [0, 2]], + ), + attach_results=True, + split_transpile=True, + ).convert(expectation, param_bindings) + counts_target = sampled[1].execution_results["counts"] + self.assertDictEqual(counts_target, counts_expected) + if __name__ == "__main__": unittest.main() From 410538ea61a2a52acdac65be9f8057a5b7c51289 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Tue, 29 Jun 2021 20:29:02 +0900 Subject: [PATCH 12/14] fix black --- qiskit/opflow/converters/circuit_sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index 712f3f23a126..b6c10191bc83 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -217,7 +217,7 @@ def convert( diff_circuits = [] for circuit in circuits: diff_circuit = circuit.copy() - del diff_circuit.data[0 : len_common_circuit] + del diff_circuit.data[0:len_common_circuit] diff_circuits.append(diff_circuit) used_qubits = set( From 6990e3674597edc933e2095d8a9e55a4ecc8d70b Mon Sep 17 00:00:00 2001 From: ikkoham Date: Tue, 29 Jun 2021 22:30:41 +0900 Subject: [PATCH 13/14] add releasenote --- .../notes/transpile-separately-f4fee95cd8868c43.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/transpile-separately-f4fee95cd8868c43.yaml diff --git a/releasenotes/notes/transpile-separately-f4fee95cd8868c43.yaml b/releasenotes/notes/transpile-separately-f4fee95cd8868c43.yaml new file mode 100644 index 000000000000..b50c7a39a080 --- /dev/null +++ b/releasenotes/notes/transpile-separately-f4fee95cd8868c43.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add the `split_transpile` option to :class:`~qiskit.opflow.CircuitSampler`. + This reduce the transpilation time because a parameterized circuit of ansatz is transpiled only + once. From 08b51c8f93e66275ea1126715281255c123dd088 Mon Sep 17 00:00:00 2001 From: ikkoham Date: Wed, 30 Jun 2021 13:22:09 +0900 Subject: [PATCH 14/14] fix the bug --- qiskit/opflow/converters/circuit_sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index b6c10191bc83..990f31919e53 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -197,7 +197,7 @@ def convert( for op_c in list(self._circuit_ops_cache.values()) ] - common_circuit = operator[1].to_circuit_op().reduce().to_circuit() + common_circuit = operator[1].to_circuit_op().reduce().to_circuit().copy() len_common_circuit = len(common_circuit) try: # 1. transpile a common circuit