From 20138a6ba221322f707ffddf0f341137740e8752 Mon Sep 17 00:00:00 2001 From: molar-volume Date: Mon, 21 Dec 2020 13:39:14 +0100 Subject: [PATCH 1/9] 1. params are required in convert method in GradientBased and HessianBased subclasses 2. fix on handling params of type ParameterExpression --- .../gradients/circuit_gradients/lin_comb.py | 19 +++++++++--------- .../circuit_gradients/param_shift.py | 15 ++++++-------- .../gradients/circuit_qfis/circuit_qfi.py | 5 ++--- .../gradients/circuit_qfis/lin_comb_full.py | 8 ++++---- .../circuit_qfis/overlap_block_diag.py | 14 ++++++++----- .../gradients/circuit_qfis/overlap_diag.py | 14 +++++++------ qiskit/opflow/gradients/gradient.py | 9 +++------ qiskit/opflow/gradients/hessian.py | 20 +++++++------------ qiskit/opflow/gradients/natural_gradient.py | 5 ++--- qiskit/opflow/gradients/qfi.py | 6 +++--- test/python/opflow/test_gradients.py | 2 +- 11 files changed, 54 insertions(+), 63 deletions(-) diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py index c34e736b0b18..1463249f7b2c 100644 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py @@ -53,13 +53,13 @@ class LinComb(CircuitGradient): see e.g. https://arxiv.org/pdf/1811.11184.pdf """ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]]]] - = None, + params: Union[ParameterExpression, ParameterVector, + List[ParameterExpression], + Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """ Convert the given operator into an operator object that represents the gradient w.r.t. params @@ -83,11 +83,10 @@ def convert(self, # pylint: disable=too-many-return-statements def _prepare_operator(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, - ParameterExpression]]]] = None + params: Union[ParameterExpression, ParameterVector, + List[ParameterExpression], + Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """ Traverse through the given operator to get back the adapted operator representing the gradient diff --git a/qiskit/opflow/gradients/circuit_gradients/param_shift.py b/qiskit/opflow/gradients/circuit_gradients/param_shift.py index b4cc51271d64..82b83fd92f24 100644 --- a/qiskit/opflow/gradients/circuit_gradients/param_shift.py +++ b/qiskit/opflow/gradients/circuit_gradients/param_shift.py @@ -15,7 +15,7 @@ from collections.abc import Iterable from copy import deepcopy from functools import partial -from typing import List, Union, Optional, Tuple, Dict +from typing import List, Union, Tuple, Dict import numpy as np from qiskit import transpile, QuantumCircuit @@ -78,14 +78,13 @@ def epsilon(self) -> float: """ return self._epsilon - # pylint: disable=arguments-differ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression], - Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, - ParameterExpression]]]] = None) -> OperatorBase: + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression], + Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] + ) -> OperatorBase: """ Args: operator: The operator corresponding to our quantum state we are taking the @@ -152,8 +151,6 @@ def _parameter_shift(self, # By this point, it's only one parameter param = params - if not isinstance(param, ParameterExpression): - raise ValueError if isinstance(operator, ListOp) and not isinstance(operator, ComposedOp): return_op = operator.traverse(partial(self._parameter_shift, params=param)) diff --git a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py index 23d1e8437873..0a368a77c762 100644 --- a/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py +++ b/qiskit/opflow/gradients/circuit_qfis/circuit_qfi.py @@ -13,7 +13,7 @@ """ CircuitQFI Class """ from abc import abstractmethod -from typing import List, Optional, Union +from typing import List, Union from qiskit.circuit import ParameterExpression, ParameterVector from ...converters.converter_base import ConverterBase @@ -39,8 +39,7 @@ class CircuitQFI(ConverterBase): @abstractmethod def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None, + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> OperatorBase: r""" Args: diff --git a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py index 0510fef22e12..3f3ece4d0a5e 100644 --- a/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py +++ b/qiskit/opflow/gradients/circuit_qfis/lin_comb_full.py @@ -13,7 +13,7 @@ """The module for Quantum the Fisher Information.""" from copy import deepcopy -from typing import List, Union, Optional, Tuple +from typing import List, Union, Tuple import numpy as np from qiskit.circuit import Gate, Qubit @@ -41,8 +41,7 @@ class LinCombFull(CircuitQFI): def convert(self, operator: CircuitStateFn, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None, + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -70,8 +69,9 @@ def convert(self, 'LinCombFull is only compatible with states that are given as CircuitStateFn') # If a single parameter is given wrap it into a list. - if not isinstance(params, (list, np.ndarray)): + if isinstance(params, ParameterExpression): params = [params] + state_qc = operator.primitive # First, the operators are computed which can compensate for a potential phase-mismatch diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py index bca57ce0fac4..77b57babbcdc 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_block_diag.py @@ -12,7 +12,7 @@ """The module for Quantum the Fisher Information.""" -from typing import List, Union, Optional +from typing import List, Union import numpy as np from scipy.linalg import block_diag @@ -39,8 +39,7 @@ class OverlapBlockDiag(CircuitQFI): def convert(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -61,8 +60,9 @@ def convert(self, def _block_diag_approx(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, + ParameterVector, + List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -81,6 +81,10 @@ def _block_diag_approx(self, """ + # If a single parameter is given wrap it into a list. + if isinstance(params, ParameterExpression): + params = [params] + circuit = operator.primitive # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ layers = _partition_circuit(circuit) diff --git a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py index 0b7436912eb6..0bd04e8015eb 100644 --- a/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py +++ b/qiskit/opflow/gradients/circuit_qfis/overlap_diag.py @@ -12,13 +12,12 @@ """The module for Quantum the Fisher Information.""" import copy -from typing import List, Union, Optional +from typing import List, Union import numpy as np from qiskit.circuit import ParameterVector, ParameterExpression from qiskit.circuit.library import RZGate, RXGate, RYGate from qiskit.converters import dag_to_circuit, circuit_to_dag -from ...operator_base import OperatorBase from ...list_ops.list_op import ListOp from ...primitive_ops.circuit_op import CircuitOp from ...expectations.pauli_expectation import PauliExpectation @@ -39,8 +38,7 @@ class OverlapDiag(CircuitQFI): def convert(self, operator: Union[CircuitOp, CircuitStateFn], - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: @@ -66,8 +64,8 @@ def convert(self, # This should be fixed. def _diagonal_approx(self, operator: Union[CircuitOp, CircuitStateFn], - params: Union[ParameterExpression, ParameterVector, List] = None - ) -> OperatorBase: + params: Union[ParameterExpression, ParameterVector, List] + ) -> ListOp: """ Args: operator: The operator corresponding to the quantum state |ψ(ω)〉for which we compute @@ -88,6 +86,10 @@ def _diagonal_approx(self, if not isinstance(operator, CircuitStateFn): raise NotImplementedError('operator must be a CircuitStateFn') + # If a single parameter is given wrap it into a list. + if isinstance(params, ParameterExpression): + params = [params] + circuit = operator.primitive # Partition the circuit into layers, and build the circuits to prepare $\psi_i$ diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py index 26ddd683c60b..f7b9c7eef2ee 100644 --- a/qiskit/opflow/gradients/gradient.py +++ b/qiskit/opflow/gradients/gradient.py @@ -12,7 +12,7 @@ """The base interface for Aqua's gradient.""" -from typing import Union, List, Optional +from typing import Union, List import numpy as np from qiskit.exceptions import MissingOptionalLibraryError @@ -38,10 +38,10 @@ class Gradient(GradientBase): """Convert an operator expression to the first-order gradient.""" + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterVector, ParameterExpression, - List[ParameterExpression]]] = None + params: Union[ParameterVector, ParameterExpression, List[ParameterExpression]] ) -> OperatorBase: r""" Args: @@ -55,9 +55,6 @@ def convert(self, ValueError: If ``params`` contains a parameter not present in ``operator``. """ - if params is None: - raise ValueError("No parameters were provided to differentiate") - if isinstance(params, (ParameterVector, list)): param_grads = [self.convert(operator, param) for param in params] absent_params = [params[i] diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index 03ece8c5fa07..f7ea7ef08493 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -12,7 +12,7 @@ """The module to compute Hessians.""" -from typing import Optional, Union, List, Tuple +from typing import Union, List, Tuple import numpy as np from qiskit.exceptions import MissingOptionalLibraryError @@ -40,11 +40,12 @@ class Hessian(HessianBase): """Compute the Hessian of an expected value.""" + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]], - List[ParameterExpression], ParameterVector]] = None + params: Union[Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]], + List[ParameterExpression], ParameterVector] ) -> OperatorBase: """ Args: @@ -56,14 +57,8 @@ def convert(self, Returns: OperatorBase: An operator whose evaluation yields the Hessian - - Raises: - ValueError: If `params` is not set. """ # if input is a tuple instead of a list, wrap it into a list - if params is None: - raise ValueError("No parameters were provided to differentiate") - if isinstance(params, (ParameterVector, list)): # Case: a list of parameters were given, compute the Hessian for all param pairs if all(isinstance(param, ParameterExpression) for param in params): @@ -81,9 +76,8 @@ def convert(self, # pylint: disable=too-many-return-statements def get_hessian(self, operator: OperatorBase, - params: Optional[Union[Tuple[ParameterExpression, ParameterExpression], - List[Tuple[ParameterExpression, ParameterExpression]]]] - = None + params: Union[Tuple[ParameterExpression, ParameterExpression], + List[Tuple[ParameterExpression, ParameterExpression]]] ) -> OperatorBase: """Get the Hessian for the given operator w.r.t. the given parameters diff --git a/qiskit/opflow/gradients/natural_gradient.py b/qiskit/opflow/gradients/natural_gradient.py index 83e5f87790c8..b8050afeddff 100644 --- a/qiskit/opflow/gradients/natural_gradient.py +++ b/qiskit/opflow/gradients/natural_gradient.py @@ -68,11 +68,10 @@ def __init__(self, self._regularization = regularization self._epsilon = kwargs.get('epsilon', 1e-6) - # pylint: disable=arguments-differ + # pylint: disable=signature-differs def convert(self, operator: OperatorBase, - params: Optional[Union[ParameterVector, ParameterExpression, - List[ParameterExpression]]] = None + params: Union[ParameterVector, ParameterExpression, List[ParameterExpression]] ) -> OperatorBase: r""" Args: diff --git a/qiskit/opflow/gradients/qfi.py b/qiskit/opflow/gradients/qfi.py index c93df73c4379..4a33c04f2d33 100644 --- a/qiskit/opflow/gradients/qfi.py +++ b/qiskit/opflow/gradients/qfi.py @@ -12,7 +12,7 @@ """The module for Quantum the Fisher Information.""" -from typing import List, Union, Optional +from typing import List, Union from qiskit.circuit import (ParameterExpression, ParameterVector) from ..list_ops.list_op import ListOp @@ -33,10 +33,10 @@ class QFI(QFIBase): """ + # pylint: disable=signature-differs def convert(self, operator: CircuitStateFn, - params: Optional[Union[ParameterExpression, ParameterVector, - List[ParameterExpression]]] = None + params: Union[ParameterExpression, ParameterVector, List[ParameterExpression]] ) -> ListOp: r""" Args: diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index 2b0edc0e3e46..f96cf4c46bf6 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -560,7 +560,7 @@ def test_natural_gradient(self, method, regularization): def test_natural_gradient2(self): """Test the natural gradient 2""" with self.assertRaises(TypeError): - _ = NaturalGradient().convert(None) + _ = NaturalGradient().convert(None, None) @idata(zip(['lin_comb_full', 'overlap_block_diag', 'overlap_diag'], [LinCombFull, OverlapBlockDiag, OverlapDiag])) From b6ab1cdb2a01c53b98ea76eae4d2502645547eb1 Mon Sep 17 00:00:00 2001 From: Miroslav Tomasik Date: Thu, 24 Dec 2020 12:32:44 +0100 Subject: [PATCH 2/9] Update qiskit/opflow/gradients/gradient.py Co-authored-by: Julien Gacon --- qiskit/opflow/gradients/gradient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/opflow/gradients/gradient.py b/qiskit/opflow/gradients/gradient.py index f7b9c7eef2ee..be70f72ad21b 100644 --- a/qiskit/opflow/gradients/gradient.py +++ b/qiskit/opflow/gradients/gradient.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The base interface for Aqua's gradient.""" +"""The base interface for Opflow's gradient.""" from typing import Union, List From fd6328a88ad4f6b024ff9f1402e95642c4dd8393 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 22 Dec 2020 22:24:39 +0100 Subject: [PATCH 3/9] passmanager(..., callback=...) parameter removed (#5522) * passmanager callback removal * unused-import * reno --- qiskit/transpiler/passmanager.py | 26 +++---------------- .../pm_cb_remove_5522-30358587a8db2701.yaml | 5 ++++ 2 files changed, 9 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/pm_cb_remove_5522-30358587a8db2701.yaml diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 13117798a4ae..b6407e238298 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -12,7 +12,6 @@ """Manager for a set of Passes and their scheduling during transpilation.""" -import warnings from typing import Union, List, Callable, Dict, Any import dill @@ -31,9 +30,7 @@ class PassManager: def __init__( self, passes: Union[BasePass, List[BasePass]] = None, - max_iteration: int = 1000, - callback: Callable = None - ): + max_iteration: int = 1000): """Initialize an empty `PassManager` object (with no passes scheduled). Args: @@ -41,19 +38,7 @@ def __init__( to be added to the pass manager schedule. max_iteration: The maximum number of iterations the schedule will be looped if the condition is not met. - callback: DEPRECATED - A callback function that will be called after each pass - execution. - - .. deprecated:: 0.13.0 - The ``callback`` parameter is deprecated in favor of - ``PassManager.run(..., callback=callback, ...)``. """ - self.callback = None - - if callback: - warnings.warn("Setting a callback at construction time is being deprecated in favor of" - "PassManager.run(..., callback=callback,...)", DeprecationWarning, 2) - self.callback = callback # the pass manager's schedule of passes, including any control-flow. # Populated via PassManager.append(). @@ -148,7 +133,7 @@ def __len__(self): return len(self._pass_sets) def __getitem__(self, index): - new_passmanager = PassManager(max_iteration=self.max_iteration, callback=self.callback) + new_passmanager = PassManager(max_iteration=self.max_iteration) _pass_sets = self._pass_sets[index] if isinstance(_pass_sets, dict): _pass_sets = [_pass_sets] @@ -157,13 +142,12 @@ def __getitem__(self, index): def __add__(self, other): if isinstance(other, PassManager): - new_passmanager = PassManager(max_iteration=self.max_iteration, callback=self.callback) + new_passmanager = PassManager(max_iteration=self.max_iteration) new_passmanager._pass_sets = self._pass_sets + other._pass_sets return new_passmanager else: try: - new_passmanager = PassManager(max_iteration=self.max_iteration, - callback=self.callback) + new_passmanager = PassManager(max_iteration=self.max_iteration) new_passmanager._pass_sets += self._pass_sets new_passmanager.append(other) return new_passmanager @@ -283,8 +267,6 @@ def _run_single_circuit( The transformed circuit. """ running_passmanager = self._create_running_passmanager() - if callback is None and self.callback: # TODO to remove with __init__(callback) - callback = self.callback result = running_passmanager.run(circuit, output_name=output_name, callback=callback) self.property_set = running_passmanager.property_set return result diff --git a/releasenotes/notes/pm_cb_remove_5522-30358587a8db2701.yaml b/releasenotes/notes/pm_cb_remove_5522-30358587a8db2701.yaml new file mode 100644 index 000000000000..c507aa5ad379 --- /dev/null +++ b/releasenotes/notes/pm_cb_remove_5522-30358587a8db2701.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + The parameter `callback` in :class:`~qiskit.transpiler.PassManager` class contruction is being removed. + The deprecation warning was released in 0.13 (April 2020) and now it is being fully dropped. From bd91a5108391eefb6101148356dc25f3eadd8a7c Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Tue, 22 Dec 2020 23:42:33 +0100 Subject: [PATCH 4/9] better initial_layout validation (#5526) Co-authored-by: Kevin Krsulich --- .../passes/layout/full_ancilla_allocation.py | 11 ++++++++++ qiskit/transpiler/passes/layout/set_layout.py | 2 +- test/python/compiler/test_transpiler.py | 8 ++++--- .../test_full_ancilla_allocation.py | 21 +++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index db365a676be1..39944a6a4412 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -66,6 +66,7 @@ def run(self, dag): raise TranspilerError('FullAncillaAllocation pass requires property_set["layout"].') if layout: + FullAncillaAllocation.validate_layout(layout.get_registers(), set(dag.qregs.values())) layout_physical_qubits = list(range(max(layout.get_physical_bits()) + 1)) else: layout_physical_qubits = [] @@ -90,3 +91,13 @@ def run(self, dag): self.property_set['layout'][idle_q] = qreg[idx] return dag + + @staticmethod + def validate_layout(layout_qregs, dag_qregs): + """ + Checks if all the qregs in layout_qregs already exist in dag_qregs. Otherwise, raise. + """ + for qreg in layout_qregs: + if qreg not in dag_qregs: + raise TranspilerError('FullAncillaAllocation: The layout refers to a quantum ' + 'register that does not exist in circuit.') diff --git a/qiskit/transpiler/passes/layout/set_layout.py b/qiskit/transpiler/passes/layout/set_layout.py index 052475d9c44e..96e94139788a 100644 --- a/qiskit/transpiler/passes/layout/set_layout.py +++ b/qiskit/transpiler/passes/layout/set_layout.py @@ -41,5 +41,5 @@ def run(self, dag): Returns: DAGCircuit: the original DAG. """ - self.property_set['layout'] = self.layout + self.property_set['layout'] = None if self.layout is None else self.layout.copy() return dag diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index e7fed429a020..b4204242689c 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -30,7 +30,6 @@ from qiskit.circuit import Parameter, Gate from qiskit.compiler import transpile from qiskit.converters import circuit_to_dag -from qiskit.dagcircuit.exceptions import DAGCircuitError from qiskit.circuit.library import CXGate, U3Gate, U2Gate, U1Gate, RXGate, RYGate from qiskit.test import QiskitTestCase, Path from qiskit.test.mock import FakeMelbourne, FakeRueschlikon, FakeAlmaden @@ -423,8 +422,11 @@ def test_wrong_initial_layout(self): QuantumRegister(3, 'q')[1], QuantumRegister(3, 'q')[2]] - self.assertRaises(DAGCircuitError, transpile, - qc, backend, initial_layout=bad_initial_layout) + with self.assertRaises(TranspilerError) as cm: + transpile(qc, backend, initial_layout=bad_initial_layout) + + self.assertEqual("FullAncillaAllocation: The layout refers to a quantum register that does " + "not exist in circuit.", cm.exception.message) def test_parameterized_circuit_for_simulator(self): """Verify that a parameterized circuit can be transpiled for a simulator backend.""" diff --git a/test/python/transpiler/test_full_ancilla_allocation.py b/test/python/transpiler/test_full_ancilla_allocation.py index d5b6ef8ab9a2..86d5c22f8745 100644 --- a/test/python/transpiler/test_full_ancilla_allocation.py +++ b/test/python/transpiler/test_full_ancilla_allocation.py @@ -19,6 +19,7 @@ from qiskit.transpiler import CouplingMap, Layout from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.test import QiskitTestCase +from qiskit.transpiler.exceptions import TranspilerError class TestFullAncillaAllocation(QiskitTestCase): @@ -146,6 +147,26 @@ def test_name_collision(self): self.assertEqual(len(other_reg), 2) self.assertRegex(other_reg.name, r'^ancilla\d+$') + def test_bad_layout(self): + """Layout referes to a register that do not exist in the circuit + """ + qr = QuantumRegister(3, 'q') + circ = QuantumCircuit(qr) + dag = circuit_to_dag(circ) + + initial_layout = Layout() + initial_layout[0] = QuantumRegister(4, 'q')[0] + initial_layout[1] = QuantumRegister(4, 'q')[1] + initial_layout[2] = QuantumRegister(4, 'q')[2] + + pass_ = FullAncillaAllocation(self.cmap5) + pass_.property_set['layout'] = initial_layout + + with self.assertRaises(TranspilerError) as cm: + pass_.run(dag) + self.assertEqual("FullAncillaAllocation: The layout refers to a quantum register that does " + "not exist in circuit.", cm.exception.message) + if __name__ == '__main__': unittest.main() From 1f504ab9e4dc80b1034884ac86abf149c980a736 Mon Sep 17 00:00:00 2001 From: molar-volume Date: Sat, 26 Dec 2020 20:37:09 +0100 Subject: [PATCH 5/9] 1) unit tests modified to test ParameterVector input for Gradient, NaturalGradient, QFI and Hessian 2) index method added to ParameterVector 3) handling of ParameterVector changed for Hessian to be consistent with list params --- qiskit/circuit/parametervector.py | 4 ++ qiskit/opflow/gradients/hessian.py | 6 +- test/python/opflow/test_gradients.py | 90 +++++++++++++--------------- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/qiskit/circuit/parametervector.py b/qiskit/circuit/parametervector.py index 513df7179bdf..e8ff52503df4 100644 --- a/qiskit/circuit/parametervector.py +++ b/qiskit/circuit/parametervector.py @@ -35,6 +35,10 @@ def params(self): """Returns the list of parameters in the ParameterVector.""" return self._params + def index(self, value): + """Returns first index of value.""" + return self._params.index(value) + def __getitem__(self, key): if isinstance(key, slice): start, stop, step = key.indices(self._size) diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index f7ea7ef08493..17736939ad80 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -62,8 +62,10 @@ def convert(self, if isinstance(params, (ParameterVector, list)): # Case: a list of parameters were given, compute the Hessian for all param pairs if all(isinstance(param, ParameterExpression) for param in params): - return ListOp( - [ListOp([self.convert(operator, (p0, p1)) for p1 in params]) for p0 in params]) + return ListOp([self.convert(operator, (param_i, param_j)) + for i, param_i in enumerate(params) + for j, param_j in enumerate(params) + if i <= j]) # Case: a list was given containing tuples of parameter pairs. # Compute the Hessian entries corresponding to these pairs of parameters. elif all(isinstance(param, tuple) for param in params): diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index f96cf4c46bf6..c71dcb54eb66 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -370,21 +370,20 @@ def test_state_hessian(self, method): """ ham = 0.5 * X - 1 * Z - a = Parameter('a') - b = Parameter('b') - params = [(a, a), (a, b), (b, b)] + params = ParameterVector('a', 2) q = QuantumRegister(1) qc = QuantumCircuit(q) qc.h(q) - qc.rz(a, q[0]) - qc.rx(b, q[0]) + qc.rz(params[0], q[0]) + qc.rx(params[1], q[0]) op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) state_hess = Hessian(hess_method=method).convert(operator=op, params=params) - values_dict = [{a: np.pi / 4, b: np.pi}, {a: np.pi / 4, b: np.pi / 4}, - {a: np.pi / 2, b: np.pi / 4}] + values_dict = [{params[0]: np.pi / 4, params[1]: np.pi}, + {params[0]: np.pi / 4, params[1]: np.pi / 4}, + {params[0]: np.pi / 2, params[1]: np.pi / 4}] correct_values = [[-0.5 / np.sqrt(2), 1 / np.sqrt(2), 0], [-0.5 / np.sqrt(2) + 0.5, -1 / 2., 0.5], [1 / np.sqrt(2), 0, 1 / np.sqrt(2)]] @@ -506,25 +505,23 @@ def test_qfi(self, method): QFI = [[1, 0], [0, 1]] - [[0, 0], [0, cos^2(a)]] """ + for params in (ParameterVector('a', 2), + [Parameter('a'), Parameter('b')]): + q = QuantumRegister(1) + qc = QuantumCircuit(q) + qc.h(q) + qc.rz(params[0], q[0]) + qc.rx(params[1], q[0]) - a = Parameter('a') - b = Parameter('b') - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = CircuitStateFn(primitive=qc, coeff=1.) - qfi = QFI(qfi_method=method).convert(operator=op, params=params) - values_dict = [{params[0]: np.pi / 4, params[1]: 0.1}, {params[0]: np.pi, params[1]: 0.1}, - {params[0]: np.pi / 2, params[1]: 0.1}] - correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal(qfi.assign_parameters(value_dict).eval(), - correct_values[i], decimal=1) + op = CircuitStateFn(primitive=qc, coeff=1.) + qfi = QFI(qfi_method=method).convert(operator=op, params=params) + values_dict = [{params[0]: np.pi / 4, params[1]: 0.1}, + {params[0]: np.pi, params[1]: 0.1}, + {params[0]: np.pi / 2, params[1]: 0.1}] + correct_values = [[[1, 0], [0, 0.5]], [[1, 0], [0, 0]], [[1, 0], [0, 1]]] + for i, value_dict in enumerate(values_dict): + np.testing.assert_array_almost_equal(qfi.assign_parameters(value_dict).eval(), + correct_values[i], decimal=1) @idata(product(['lin_comb', 'param_shift', 'fin_diff'], [None, 'lasso', 'ridge', 'perturb_diag', 'perturb_diag_elements'])) @@ -532,28 +529,27 @@ def test_qfi(self, method): def test_natural_gradient(self, method, regularization): """Test the natural gradient""" try: - ham = 0.5 * X - 1 * Z - a = Parameter('a') - b = Parameter('b') - params = [a, b] - - q = QuantumRegister(1) - qc = QuantumCircuit(q) - qc.h(q) - qc.rz(params[0], q[0]) - qc.rx(params[1], q[0]) - - op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) - nat_grad = NaturalGradient(grad_method=method, - regularization=regularization).convert(operator=op, - params=params) - values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] - correct_values = [[-2.36003979, 2.06503481]] \ - if regularization == 'ridge' else [[-4.2, 0]] - for i, value_dict in enumerate(values_dict): - np.testing.assert_array_almost_equal(nat_grad.assign_parameters(value_dict).eval(), - correct_values[i], - decimal=0) + for params in (ParameterVector('a', 2), + [Parameter('a'), Parameter('b')]): + ham = 0.5 * X - 1 * Z + + q = QuantumRegister(1) + qc = QuantumCircuit(q) + qc.h(q) + qc.rz(params[0], q[0]) + qc.rx(params[1], q[0]) + + op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.) + nat_grad = NaturalGradient(grad_method=method, regularization=regularization)\ + .convert(operator=op, params=params) + values_dict = [{params[0]: np.pi / 4, params[1]: np.pi / 2}] + correct_values = [[-2.36003979, 2.06503481]] \ + if regularization == 'ridge' else [[-4.2, 0]] + for i, value_dict in enumerate(values_dict): + np.testing.assert_array_almost_equal( + nat_grad.assign_parameters(value_dict).eval(), + correct_values[i], + decimal=0) except MissingOptionalLibraryError as ex: self.skipTest(str(ex)) From 6b4e4bd1ce5b0f6eebe97f488db3c06d685126b2 Mon Sep 17 00:00:00 2001 From: molar-volume Date: Mon, 28 Dec 2020 21:26:51 +0100 Subject: [PATCH 6/9] Hessian with respect to vector-like params returns matrix, unit test modified accordingly --- qiskit/opflow/gradients/hessian.py | 6 ++---- test/python/opflow/test_gradients.py | 8 +++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index 17736939ad80..f7ea7ef08493 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -62,10 +62,8 @@ def convert(self, if isinstance(params, (ParameterVector, list)): # Case: a list of parameters were given, compute the Hessian for all param pairs if all(isinstance(param, ParameterExpression) for param in params): - return ListOp([self.convert(operator, (param_i, param_j)) - for i, param_i in enumerate(params) - for j, param_j in enumerate(params) - if i <= j]) + return ListOp( + [ListOp([self.convert(operator, (p0, p1)) for p1 in params]) for p0 in params]) # Case: a list was given containing tuples of parameter pairs. # Compute the Hessian entries corresponding to these pairs of parameters. elif all(isinstance(param, tuple) for param in params): diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index c71dcb54eb66..0774cda6bb30 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -382,11 +382,9 @@ def test_state_hessian(self, method): state_hess = Hessian(hess_method=method).convert(operator=op, params=params) values_dict = [{params[0]: np.pi / 4, params[1]: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}] - correct_values = [[-0.5 / np.sqrt(2), 1 / np.sqrt(2), 0], - [-0.5 / np.sqrt(2) + 0.5, -1 / 2., 0.5], - [1 / np.sqrt(2), 0, 1 / np.sqrt(2)]] + {params[0]: np.pi / 4, params[1]: np.pi / 4}] + correct_values = [[[-0.5 / np.sqrt(2), 1 / np.sqrt(2)], [1 / np.sqrt(2), 0]], + [[-0.5 / np.sqrt(2) + 0.5, -1 / 2.], [-1 / 2., 0.5]]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal(state_hess.assign_parameters(value_dict).eval(), From e331296f549070eeb7daa243be491ec74c7db434 Mon Sep 17 00:00:00 2001 From: Almudena Carrera Vazquez Date: Mon, 28 Dec 2020 19:27:44 +0100 Subject: [PATCH 7/9] Add PiecewiseChebyshev arithmetic circuit (#5364) * general polynomial approximation * release notes * fix qubit re-allocation * fix re-calling build * Update qiskit/circuit/library/arithmetic/piecewise_chebyshev.py Co-authored-by: Julien Gacon * Update qiskit/circuit/library/arithmetic/piecewise_chebyshev.py Co-authored-by: Julien Gacon * Update qiskit/circuit/library/arithmetic/piecewise_chebyshev.py Co-authored-by: Julien Gacon * fix indentation lint error * move tests to library module * Add suggestions from code review Co-authored-by: Julien Gacon --- .../library/arithmetic/piecewise_chebyshev.py | 327 ++++++++++++++++++ .../piecewise_polynomial_pauli_rotations.py | 25 +- .../arithmetic/polynomial_pauli_rotations.py | 9 +- qiskit/circuit/library/blueprintcircuit.py | 2 +- ...polynomial-chebyshev-66c82ffe1d749dd6.yaml | 6 + .../library/test_piecewise_chebyshev.py | 127 +++++++ 6 files changed, 487 insertions(+), 9 deletions(-) create mode 100644 qiskit/circuit/library/arithmetic/piecewise_chebyshev.py create mode 100644 releasenotes/notes/add-piecewise-polynomial-chebyshev-66c82ffe1d749dd6.yaml create mode 100644 test/python/circuit/library/test_piecewise_chebyshev.py diff --git a/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py new file mode 100644 index 000000000000..65cdc4a444a5 --- /dev/null +++ b/qiskit/circuit/library/arithmetic/piecewise_chebyshev.py @@ -0,0 +1,327 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# 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. + +"""Piecewise polynomial Chebyshev approximation to a given f(x).""" + +from typing import Callable, List, Optional +import numpy as np +from numpy.polynomial.chebyshev import Chebyshev + +from qiskit.circuit import QuantumRegister, AncillaRegister +from qiskit.circuit.library.blueprintcircuit import BlueprintCircuit +from qiskit.circuit.exceptions import CircuitError + +from .piecewise_polynomial_pauli_rotations import PiecewisePolynomialPauliRotations + + +class PiecewiseChebyshev(BlueprintCircuit): + r"""Piecewise Chebyshev approximation to an input function. + + For a given function :math:`f(x)` and degree :math:`d`, this class implements a piecewise + polynomial Chebyshev approximation on :math:`n` qubits to :math:`f(x)` on the given intervals. + All the polynomials in the approximation are of degree :math:`d`. + + The values of the parameters are calculated according to [1]. + + Examples: + + .. jupyer-execute:: + + import numpy as np + from qiskit import QuantumCircuit + from qiskit.circuit.library.arithmetic.piecewise_chebyshev import PiecewiseChebyshev + f_x, degree, breakpoints, num_state_qubits = lambda x: np.arcsin(1 / x), 2, [2, 4], 2 + pw_approximation = PiecewiseChebyshev(f_x, degree, breakpoints, num_state_qubits) + pw_approximation._build() + qc = QuantumCircuit(pw_approximation.num_qubits) + qc.h(list(range(num_state_qubits))) + qc.append(pw_approximation.to_instruction(), qc.qubits) + print(qc.draw(output='mpl')) + + References: + + [1]: Haener, T., Roetteler, M., & Svore, K. M. (2018). + Optimizing Quantum Circuits for Arithmetic. + `arXiv:1805.12445 `_ + """ + + def __init__(self, + f_x: Callable[[int], float], + degree: Optional[int] = None, + breakpoints: Optional[List[int]] = None, + num_state_qubits: Optional[int] = None, + name: str = 'pw_cheb') -> None: + r""" + Args: + f_x: the function to be approximated. + degree: the degree of the polynomials. + Defaults to ``1``. + breakpoints: the breakpoints to define the piecewise-linear function. + Defaults to the full interval. + num_state_qubits: number of qubits representing the state. + name: The name of the circuit object. + """ + super().__init__(name=name) + + # define internal parameters + self._num_state_qubits = None + + # Store parameters + self._f_x = f_x + self._degree = degree if degree is not None else 1 + self._breakpoints = breakpoints if breakpoints is not None else [0] + + self._polynomials = None + + self.num_state_qubits = num_state_qubits + + def _check_configuration(self, raise_on_failure: bool = True) -> bool: + valid = True + + if self._f_x is None: + valid = False + if raise_on_failure: + raise AttributeError('The function to be approximated has not been set.') + + if self._degree is None: + valid = False + if raise_on_failure: + raise AttributeError('The degree of the polynomials has not been set.') + + if self._breakpoints is None: + valid = False + if raise_on_failure: + raise AttributeError('The breakpoints have not been set.') + + if self.num_state_qubits is None: + valid = False + if raise_on_failure: + raise AttributeError('The number of qubits has not been set.') + + if self.num_qubits < self.num_state_qubits + 1: + valid = False + if raise_on_failure: + raise CircuitError('Not enough qubits in the circuit, need at least ' + '{}.'.format(self.num_state_qubits + 1)) + + return valid + + @property + def f_x(self) -> Callable[[int], float]: + """The function to be approximated. + + Returns: + The function to be approximated. + """ + return self._f_x + + @f_x.setter + def f_x(self, f_x: Optional[Callable[[int], float]]) -> None: + """Set the function to be approximated. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + f_x: The new function to be approximated. + """ + if self._f_x is None or f_x != self._f_x: + self._invalidate() + self._f_x = f_x + + self._reset_registers(self.num_state_qubits) + + @property + def degree(self) -> int: + """The degree of the polynomials. + + Returns: + The degree of the polynomials. + """ + return self._degree + + @degree.setter + def degree(self, degree: Optional[int]) -> None: + """Set the error tolerance. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + degree: The new degree. + """ + if self._degree is None or degree != self._degree: + self._invalidate() + self._degree = degree + + self._reset_registers(self.num_state_qubits) + + @property + def breakpoints(self) -> List[int]: + """The breakpoints for the piecewise approximation. + + Returns: + The breakpoints for the piecewise approximation. + """ + breakpoints = self._breakpoints + + # it the state qubits are set ensure that the breakpoints match beginning and end + if self.num_state_qubits is not None: + num_states = 2 ** self.num_state_qubits + + # If the last breakpoint is < num_states, add the identity polynomial + if breakpoints[-1] < num_states: + breakpoints = breakpoints + [num_states] + + # If the first breakpoint is > 0, add the identity polynomial + if breakpoints[0] > 0: + breakpoints = [0] + breakpoints + + return breakpoints + + @breakpoints.setter + def breakpoints(self, breakpoints: Optional[List[int]]) -> None: + """Set the breakpoints for the piecewise approximation. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + breakpoints: The new breakpoints for the piecewise approximation. + """ + if self._breakpoints is None or breakpoints != self._breakpoints: + self._invalidate() + self._breakpoints = breakpoints + + self._reset_registers(self.num_state_qubits) + + @property + def polynomials(self) -> List[List[float]]: + """The polynomials for the piecewise approximation. + + Returns: + The polynomials for the piecewise approximation. + """ + if self.num_state_qubits is None: + return [[]] + + # note this must be the private attribute since we handle missing breakpoints at + # 0 and 2 ^ num_qubits here (e.g. if the function we approximate is not defined at 0 + # and the user takes that into account we just add an identity) + num_intervals = len(self._breakpoints) + + # Calculate the polynomials + polynomials = [] + for i in range(0, num_intervals - 1): + # Calculate the polynomial approximating the function on the current interval + poly = Chebyshev.interpolate(self._f_x, self._degree, + domain=[self._breakpoints[i], + self._breakpoints[i + 1]]) + # Convert polynomial to the standard basis and rescale it for the rotation gates + poly = 2 * poly.convert(kind=np.polynomial.Polynomial).coef + # Convert to list and append + polynomials.append(poly.tolist()) + + # If the last breakpoint is < 2 ** num_qubits, add the identity polynomial + if self._breakpoints[-1] < 2 ** self.num_state_qubits: + polynomials = polynomials + [[2 * np.arcsin(1)]] + + # If the first breakpoint is > 0, add the identity polynomial + if self._breakpoints[0] > 0: + polynomials = [[2 * np.arcsin(1)]] + polynomials + + return polynomials + + @polynomials.setter + def polynomials(self, polynomials: Optional[List[List[float]]]) -> None: + """Set the polynomials for the piecewise approximation. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + polynomials: The new breakpoints for the piecewise approximation. + """ + if self._polynomials is None or polynomials != self._polynomials: + self._invalidate() + self._polynomials = polynomials + + self._reset_registers(self.num_state_qubits) + + @property + def num_state_qubits(self) -> int: + r"""The number of state qubits representing the state :math:`|x\rangle`. + + Returns: + The number of state qubits. + """ + return self._num_state_qubits + + @num_state_qubits.setter + def num_state_qubits(self, num_state_qubits: Optional[int]) -> None: + """Set the number of state qubits. + + Note that this may change the underlying quantum register, if the number of state qubits + changes. + + Args: + num_state_qubits: The new number of qubits. + """ + if self._num_state_qubits is None or num_state_qubits != self._num_state_qubits: + self._invalidate() + self._num_state_qubits = num_state_qubits + + # Set breakpoints if they haven't been set + if num_state_qubits is not None and self._breakpoints is None: + self.breakpoints = [0, 2 ** num_state_qubits] + + self._reset_registers(num_state_qubits) + + def _reset_registers(self, num_state_qubits: Optional[int]) -> None: + if num_state_qubits is not None: + qr_state = QuantumRegister(num_state_qubits, 'state') + qr_target = QuantumRegister(1, 'target') + self.qregs = [qr_state, qr_target] + self._ancillas = [] + self._qubits = qr_state[:] + qr_target[:] + + num_ancillas = num_state_qubits + if num_ancillas > 0: + qr_ancilla = AncillaRegister(num_ancillas) + self.add_register(qr_ancilla) + + else: + self.qregs = [] + self._qubits = [] + self._ancillas = [] + + def _build(self): + """Build the circuit. The operation is considered successful when q_objective is :math:`|1>` + """ + # do not build the circuit if _data is already populated + if self._data is not None: + return + + self._data = [] + + # check whether the configuration is valid + self._check_configuration() + + poly_r = PiecewisePolynomialPauliRotations(self.num_state_qubits, + self.breakpoints, self.polynomials) + + qr_state = self.qubits[:self.num_state_qubits] + qr_target = [self.qubits[self.num_state_qubits]] + qr_ancillas = self.qubits[self.num_state_qubits + 1:] + + # Apply polynomial approximation + self.append(poly_r.to_instruction(), qr_state[:] + qr_target + qr_ancillas[:]) diff --git a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py index bfe8726b6a5b..ff576c4da80a 100644 --- a/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py @@ -127,6 +127,10 @@ def breakpoints(self) -> List[int]: Returns: The list of breakpoints. """ + if self.num_state_qubits is not None and len(self._breakpoints) == len(self.coeffs) and\ + self._breakpoints[-1] < 2 ** self.num_state_qubits: + return self._breakpoints + [2 ** self.num_state_qubits] + return self._breakpoints @breakpoints.setter @@ -246,20 +250,27 @@ def _reset_registers(self, num_state_qubits: Optional[int]) -> None: if self.contains_zero_breakpoint: num_ancillas -= 1 if num_ancillas > 0: - self._ancillas = [] qr_ancilla = AncillaRegister(num_ancillas) self.add_register(qr_ancilla) + else: + qr_ancilla = [] + + self._qubits = qr_state[:] + qr_target[:] + qr_ancilla[:] + self._ancillas = qr_ancilla[:] else: self.qregs = [] + self._qubits = [] + self._ancillas = [] def _build(self): - # Add the last breakpoint if necessary - if self.num_state_qubits is not None and len(self._breakpoints) == len(self._coeffs) and\ - self._breakpoints[-1] < 2 ** self.num_state_qubits: - self.breakpoints = self._breakpoints + [2 ** self.num_state_qubits] + # do not build the circuit if _data is already populated + if self._data is not None: + return + + self._data = [] - # The number of ancilla might have changed, so reset registers - super()._build() + # check whether the configuration is valid + self._check_configuration() qr_state = self.qubits[:self.num_state_qubits] qr_target = [self.qubits[self.num_state_qubits]] diff --git a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py index c43767a68b66..3c093a40e25e 100644 --- a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py @@ -303,7 +303,14 @@ def _get_rotation_coefficients(self) -> Dict[Sequence[int], float]: return rotation_coeffs def _build(self): - super()._build() + # do not build the circuit if _data is already populated + if self._data is not None: + return + + self._data = [] + + # check whether the configuration is valid + self._check_configuration() qr_state = self.qubits[:self.num_state_qubits] qr_target = self.qubits[self.num_state_qubits] diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index bf26cbcff327..2131e477b6e1 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -59,7 +59,7 @@ def _check_configuration(self, raise_on_failure: bool = True) -> bool: def _build(self) -> None: """Build the circuit.""" # do not build the circuit if _data is already populated - if self._data: + if self._data is not None: return self._data = [] diff --git a/releasenotes/notes/add-piecewise-polynomial-chebyshev-66c82ffe1d749dd6.yaml b/releasenotes/notes/add-piecewise-polynomial-chebyshev-66c82ffe1d749dd6.yaml new file mode 100644 index 000000000000..3bb21dc3f530 --- /dev/null +++ b/releasenotes/notes/add-piecewise-polynomial-chebyshev-66c82ffe1d749dd6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + For a given function :math:`f(x)` and degree :math:`d`, this class implements a piecewise + polynomial Chebyshev approximation on :math:`n` qubits to :math:`f(x)` on the given intervals. + All the polynomials in the approximation are of degree :math:`d`. diff --git a/test/python/circuit/library/test_piecewise_chebyshev.py b/test/python/circuit/library/test_piecewise_chebyshev.py new file mode 100644 index 000000000000..78940f91b7b0 --- /dev/null +++ b/test/python/circuit/library/test_piecewise_chebyshev.py @@ -0,0 +1,127 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# 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. + +"""Test the piecewise Chebyshev approximation.""" + +import unittest +from collections import defaultdict +import numpy as np +from ddt import ddt, data, unpack + +from qiskit.test.base import QiskitTestCase +from qiskit import BasicAer, execute +from qiskit.circuit import QuantumCircuit +from qiskit.circuit.library.arithmetic.piecewise_chebyshev import PiecewiseChebyshev + + +@ddt +class TestPiecewiseChebyshev(QiskitTestCase): + """Test the piecewise Chebyshev approximation.""" + + def assertFunctionIsCorrect(self, function_circuit, reference): + """Assert that ``function_circuit`` implements the reference function ``reference``.""" + function_circuit._build() + num_state_qubits = function_circuit.num_state_qubits + num_ancilla_qubits = function_circuit.num_ancillas + circuit = QuantumCircuit(num_state_qubits + 1 + num_ancilla_qubits) + circuit.h(list(range(num_state_qubits))) + circuit.append(function_circuit.to_instruction(), list(range(circuit.num_qubits))) + + backend = BasicAer.get_backend('statevector_simulator') + statevector = execute(circuit, backend).result().get_statevector() + + probabilities = defaultdict(float) + for i, statevector_amplitude in enumerate(statevector): + i = bin(i)[2:].zfill(circuit.num_qubits)[num_ancilla_qubits:] + probabilities[i] += np.real(np.abs(statevector_amplitude) ** 2) + + unrolled_probabilities = [] + unrolled_expectations = [] + for i, probability in probabilities.items(): + x, last_qubit = int(i[1:], 2), i[0] + if last_qubit == '0': + expected_amplitude = np.cos(reference(x)) / np.sqrt(2 ** num_state_qubits) + else: + expected_amplitude = np.sin(reference(x)) / np.sqrt(2 ** num_state_qubits) + + unrolled_probabilities += [probability] + unrolled_expectations += [np.real(np.abs(expected_amplitude) ** 2)] + + np.testing.assert_array_almost_equal(unrolled_probabilities, unrolled_expectations, + decimal=3) + + @ data((lambda x: np.arcsin(1 / x), 2, [2, 4], 2), + (lambda x: x / 8, 1, [1, 8], 3) + ) + @ unpack + def test_piecewise_chebyshev(self, f_x, degree, breakpoints, num_state_qubits): + """Test the piecewise Chebyshev approximation.""" + + def pw_poly(x): + if breakpoints[0] <= x < breakpoints[-1]: + return f_x(x) + return np.arcsin(1) + + pw_approximation = PiecewiseChebyshev(f_x, degree, breakpoints, num_state_qubits) + + self.assertFunctionIsCorrect(pw_approximation, pw_poly) + + def test_piecewise_chebyshev_mutability(self): + """Test the mutability of the piecewise Chebyshev approximation.""" + + def pw_poly(x, f_x): + if breakpoints[0] <= x < breakpoints[-1]: + return f_x(x) + return np.arcsin(1) + + def f_x_1(x): + return x / 2 + + pw_approximation = PiecewiseChebyshev(f_x_1) + + with self.subTest(msg='missing number of state qubits'): + with self.assertRaises(AttributeError): # no state qubits set + print(pw_approximation.draw()) + + with self.subTest(msg='default setup, just setting number of state qubits'): + pw_approximation.num_state_qubits = 2 + pw_approximation.f_x = f_x_1 + # set to the default breakpoints for pw_poly + breakpoints = [0, 4] + pw_approximation.breakpoints = breakpoints + self.assertFunctionIsCorrect(pw_approximation, lambda x: pw_poly(x, f_x_1)) + + def f_x_2(x): + return x / 4 + with self.subTest(msg='setting non-default values'): + breakpoints = [0, 2] + degree = 2 + pw_approximation.breakpoints = breakpoints + pw_approximation.degree = degree + pw_approximation.f_x = f_x_2 + self.assertFunctionIsCorrect(pw_approximation, lambda x: pw_poly(x, f_x_2)) + + def f_x_3(x): + return x ** 2 + + with self.subTest(msg='changing all values'): + pw_approximation.num_state_qubits = 4 + breakpoints = [1, 3, 6] + degree = 3 + pw_approximation.breakpoints = breakpoints + pw_approximation.degree = degree + pw_approximation.f_x = f_x_3 + self.assertFunctionIsCorrect(pw_approximation, lambda x: pw_poly(x, f_x_3)) + + +if __name__ == '__main__': + unittest.main() From 4e8a0769d09c8f8ceb0f57a24f6c0bb0ff58675f Mon Sep 17 00:00:00 2001 From: Max Rossmannek Date: Mon, 28 Dec 2020 20:55:10 +0100 Subject: [PATCH 8/9] Ensure aux_operator eigenvalues are normalized (#5496) As reported on multiple occasions in Qiskit Aqua ([1], [2]), the eigenvalues of the auxiliary operators are not correctly normalized when the QASM backend is used. This commit fixes this short-coming by applying the same normalization to the VQE's eigenstate when obtained from a QASM backend (in which case this is a dictionary) as done by the `CircuitSampler` in its `sample_circuits` function. The case of the statevector backend is unaffected by this change, as it will return the eigenstate as a list. A unittest is added which asserts this behavior. [1]: https://github.com/Qiskit/qiskit-aqua/issues/1460 [2]: https://github.com/Qiskit/qiskit-aqua/issues/1467 Co-authored-by: Julien Gacon Co-authored-by: Julien Gacon Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/algorithms/minimum_eigen_solvers/vqe.py | 5 ++++- test/python/algorithms/test_vqe.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/minimum_eigen_solvers/vqe.py b/qiskit/algorithms/minimum_eigen_solvers/vqe.py index 74269ab217e4..d0f000476707 100755 --- a/qiskit/algorithms/minimum_eigen_solvers/vqe.py +++ b/qiskit/algorithms/minimum_eigen_solvers/vqe.py @@ -577,7 +577,10 @@ def get_optimal_vector(self) -> Union[List[float], Dict[str, int]]: qc.barrier(q) qc.measure(q, c) ret = self._quantum_instance.execute(qc) - self._ret['min_vector'] = ret.get_counts(qc) + counts = ret.get_counts(qc) + # normalize, just as done in CircuitSampler.sample_circuits + shots = self._quantum_instance._run_config.shots + self._ret['min_vector'] = {b: (v / shots) ** 0.5 for (b, v) in counts.items()} return self._ret['min_vector'] @property diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py index c6277f6eea23..c19a029344b0 100644 --- a/test/python/algorithms/test_vqe.py +++ b/test/python/algorithms/test_vqe.py @@ -152,6 +152,22 @@ def test_basic_aer_qasm(self): result = vqe.run(self.qasm_simulator) self.assertAlmostEqual(result.eigenvalue.real, -1.86823, places=2) + def test_qasm_aux_operators_normalized(self): + """Test VQE with qasm_simulator returns normalized aux_operator eigenvalues.""" + wavefunction = self.ry_wavefunction + vqe = VQE(self.h2_op, wavefunction, quantum_instance=self.qasm_simulator) + + opt_params = [3.50437328, 3.87415376, 0.93684363, 5.92219622, -1.53527887, 1.87941418, + -4.5708326, 0.70187027] + + vqe._ret = {} + vqe._ret['opt_params'] = opt_params + vqe._ret['opt_params_dict'] = \ + dict(zip(sorted(wavefunction.parameters, key=lambda p: p.name), opt_params)) + + optimal_vector = vqe.get_optimal_vector() + self.assertAlmostEqual(sum([v ** 2 for v in optimal_vector.values()]), 1.0, places=4) + def test_with_aer_statevector(self): """Test VQE with Aer's statevector_simulator.""" try: From fc3498896dc9bc0a3d77af1059fe6b2c353c0828 Mon Sep 17 00:00:00 2001 From: molar-volume Date: Mon, 28 Dec 2020 21:26:51 +0100 Subject: [PATCH 9/9] Hessian with respect to vector-like param returns matrix, unit test modified accordingly --- qiskit/opflow/gradients/hessian.py | 6 ++---- test/python/opflow/test_gradients.py | 8 +++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/qiskit/opflow/gradients/hessian.py b/qiskit/opflow/gradients/hessian.py index 17736939ad80..f7ea7ef08493 100644 --- a/qiskit/opflow/gradients/hessian.py +++ b/qiskit/opflow/gradients/hessian.py @@ -62,10 +62,8 @@ def convert(self, if isinstance(params, (ParameterVector, list)): # Case: a list of parameters were given, compute the Hessian for all param pairs if all(isinstance(param, ParameterExpression) for param in params): - return ListOp([self.convert(operator, (param_i, param_j)) - for i, param_i in enumerate(params) - for j, param_j in enumerate(params) - if i <= j]) + return ListOp( + [ListOp([self.convert(operator, (p0, p1)) for p1 in params]) for p0 in params]) # Case: a list was given containing tuples of parameter pairs. # Compute the Hessian entries corresponding to these pairs of parameters. elif all(isinstance(param, tuple) for param in params): diff --git a/test/python/opflow/test_gradients.py b/test/python/opflow/test_gradients.py index c71dcb54eb66..0774cda6bb30 100644 --- a/test/python/opflow/test_gradients.py +++ b/test/python/opflow/test_gradients.py @@ -382,11 +382,9 @@ def test_state_hessian(self, method): state_hess = Hessian(hess_method=method).convert(operator=op, params=params) values_dict = [{params[0]: np.pi / 4, params[1]: np.pi}, - {params[0]: np.pi / 4, params[1]: np.pi / 4}, - {params[0]: np.pi / 2, params[1]: np.pi / 4}] - correct_values = [[-0.5 / np.sqrt(2), 1 / np.sqrt(2), 0], - [-0.5 / np.sqrt(2) + 0.5, -1 / 2., 0.5], - [1 / np.sqrt(2), 0, 1 / np.sqrt(2)]] + {params[0]: np.pi / 4, params[1]: np.pi / 4}] + correct_values = [[[-0.5 / np.sqrt(2), 1 / np.sqrt(2)], [1 / np.sqrt(2), 0]], + [[-0.5 / np.sqrt(2) + 0.5, -1 / 2.], [-1 / 2., 0.5]]] for i, value_dict in enumerate(values_dict): np.testing.assert_array_almost_equal(state_hess.assign_parameters(value_dict).eval(),