From 6d6da72b6006441a9f3b1ab58a0b46e172ad7a7c Mon Sep 17 00:00:00 2001 From: Massimiliano Incudini Date: Sat, 3 Oct 2020 14:04:22 +0200 Subject: [PATCH 1/6] fixed issue 4526 by adding InitializeGate --- qiskit/extensions/__init__.py | 2 +- .../quantum_initializer/initializer.py | 44 +++++++++++++++++-- test/python/circuit/test_gate_definitions.py | 2 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/qiskit/extensions/__init__.py b/qiskit/extensions/__init__.py index ae6ac1da470a..6474b94f3c6f 100644 --- a/qiskit/extensions/__init__.py +++ b/qiskit/extensions/__init__.py @@ -47,7 +47,7 @@ from qiskit.circuit.library.standard_gates import * from qiskit.circuit.barrier import Barrier -from .quantum_initializer.initializer import Initialize +from .quantum_initializer.initializer import Initialize, InitializeGate from .unitary import UnitaryGate from .hamiltonian_gate import HamiltonianGate from .simulator import Snapshot diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 86958659b900..915b7c0baa02 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -21,6 +21,7 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister from qiskit.circuit import Instruction +from qiskit.circuit.gate import Gate from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.library.standard_gates.x import CXGate from qiskit.circuit.library.standard_gates.ry import RYGate @@ -40,7 +41,7 @@ class Initialize(Instruction): which is not unitary. """ - def __init__(self, params): + def __init__(self, params, reset=True): """Create new initialize composite. params (list): vector of complex amplitudes to initialize to @@ -59,6 +60,7 @@ def __init__(self, params): num_qubits = int(num_qubits) super().__init__("initialize", num_qubits, 0, params) + self.reset = reset def _define(self): """Calculate a subcircuit that implements this initialization @@ -79,8 +81,9 @@ def _define(self): q = QuantumRegister(self.num_qubits, 'q') initialize_circuit = QuantumCircuit(q, name='init_def') - for qubit in q: - initialize_circuit.append(Reset(), [qubit]) + if self.reset: + for qubit in q: + initialize_circuit.append(Reset(), [qubit]) initialize_circuit.append(initialize_instr, q[:]) self.definition = initialize_circuit @@ -267,6 +270,41 @@ def validate_parameter(self, parameter): "{1}".format(type(parameter), self.name)) +class InitializeGate(Gate): + """Complex amplitude initialization + + Class that implements the (complex amplitude) initialization of some + flexible collection of qubit registers (assuming the qubits are in the + zero state). + Note that InitializeGate is an Gate and not an Instruction since it does not contains + a reset instruction, contained in Initialize + """ + + def __init__(self, params, label=None): + """Create new initialize gate. + + params (list): vector of complex amplitudes to initialize to + label: An optional label for the gate. + """ + + if not isinstance(params, list): + raise QiskitError("Given param is not a statevector") + + num_qubits = math.log2(len(params)) + + if num_qubits == 0 or not num_qubits.is_integer(): + raise QiskitError("Desired statevector length not a positive power of 2.") + + super().__init__('init', int(num_qubits), params, label=label) + + def _define(self): + desired_vector = self.params + q = QuantumRegister(self.num_qubits, 'q') + circ = QuantumCircuit(q, name='init_def') + circ.append(Initialize(desired_vector, reset=False), q[:]) + self.definition = circ + + def initialize(self, params, qubits): """Apply initialize to circuit.""" if not isinstance(qubits, list): diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 18c0478d5640..728337bc6b1c 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -198,7 +198,7 @@ def setUpClass(cls): exclude = {'ControlledGate', 'DiagonalGate', 'UCGate', 'MCGupDiag', 'MCU1Gate', 'UnitaryGate', 'HamiltonianGate', 'MCPhaseGate', 'UCPauliRotGate', 'SingleQubitUnitary', 'MCXGate', - 'VariadicZeroParamGate', 'ClassicalFunction'} + 'VariadicZeroParamGate', 'ClassicalFunction', 'InitializeGate'} cls._gate_classes = [] for aclass in class_list: if aclass.__name__ not in exclude: From a5e8f78d23bf0f7d0821ac48cc6fcca456ae2cdd Mon Sep 17 00:00:00 2001 From: Massimiliano Incudini Date: Thu, 29 Oct 2020 22:42:08 +0100 Subject: [PATCH 2/6] code refactoring: logic in gate --- .../quantum_initializer/initializer.py | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 915b7c0baa02..ebac6d0588dd 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -31,22 +31,23 @@ _EPS = 1e-10 # global variable used to chop very small numbers to zero -class Initialize(Instruction): +class InitializeGate(Gate): """Complex amplitude initialization. Class that implements the (complex amplitude) initialization of some flexible collection of qubit registers (assuming the qubits are in the zero state). - Note that Initialize is an Instruction and not a Gate since it contains a reset instruction, - which is not unitary. """ - def __init__(self, params, reset=True): + def __init__(self, params, label=None): """Create new initialize composite. params (list): vector of complex amplitudes to initialize to """ - num_qubits = math.log2(len(params)) + try: + num_qubits = math.log2(len(params)) + except: + raise QiskitError("Given params are not a statevector") # Check if param is a power of 2 if num_qubits == 0 or not num_qubits.is_integer(): @@ -59,8 +60,7 @@ def __init__(self, params, reset=True): num_qubits = int(num_qubits) - super().__init__("initialize", num_qubits, 0, params) - self.reset = reset + super().__init__('init', int(num_qubits), params, label=label) def _define(self): """Calculate a subcircuit that implements this initialization @@ -81,9 +81,6 @@ def _define(self): q = QuantumRegister(self.num_qubits, 'q') initialize_circuit = QuantumCircuit(q, name='init_def') - if self.reset: - for qubit in q: - initialize_circuit.append(Reset(), [qubit]) initialize_circuit.append(initialize_instr, q[:]) self.definition = initialize_circuit @@ -105,7 +102,7 @@ def gates_to_uncompute(self): # qubit (we peel away one qubit at a time) (remaining_param, thetas, - phis) = Initialize._rotations_to_disentangle(remaining_param) + phis) = InitializeGate._rotations_to_disentangle(remaining_param) # perform the required rotations to decouple the LSB qubit (so that # it can be "factored" out, leaving a shorter amplitude vector to peel away) @@ -152,7 +149,7 @@ def _rotations_to_disentangle(local_param): # multiplexor being in state |i>) (remains, add_theta, - add_phi) = Initialize._bloch_angles(local_param[2 * i: 2 * (i + 1)]) + add_phi) = InitializeGate._bloch_angles(local_param[2 * i: 2 * (i + 1)]) remaining_vector.append(remains) @@ -270,39 +267,76 @@ def validate_parameter(self, parameter): "{1}".format(type(parameter), self.name)) -class InitializeGate(Gate): - """Complex amplitude initialization +class Initialize(Instruction): + """Complex amplitude initialization. Class that implements the (complex amplitude) initialization of some flexible collection of qubit registers (assuming the qubits are in the zero state). - Note that InitializeGate is an Gate and not an Instruction since it does not contains - a reset instruction, contained in Initialize + Note that Initialize is an Instruction and not a Gate since it contains a reset instruction, + which is not unitary. """ - def __init__(self, params, label=None): - """Create new initialize gate. + def __init__(self, params): + """Create new initialize composite. params (list): vector of complex amplitudes to initialize to - label: An optional label for the gate. """ + try: + num_qubits = math.log2(len(params)) + except: + raise QiskitError("Given params are not a statevector") - if not isinstance(params, list): - raise QiskitError("Given param is not a statevector") - - num_qubits = math.log2(len(params)) - + # Check if param is a power of 2 if num_qubits == 0 or not num_qubits.is_integer(): raise QiskitError("Desired statevector length not a positive power of 2.") - super().__init__('init', int(num_qubits), params, label=label) + # Check if probabilities (amplitudes squared) sum to 1 + if not math.isclose(sum(np.absolute(params) ** 2), 1.0, + abs_tol=_EPS): + raise QiskitError("Sum of amplitudes-squared does not equal one.") + + num_qubits = int(num_qubits) + + super().__init__("initialize", num_qubits, 0, params) def _define(self): - desired_vector = self.params + """Calculate a subcircuit that implements this initialization + + Implements a recursive initialization algorithm, including optimizations, + from "Synthesis of Quantum Logic Circuits" Shende, Bullock, Markov + https://arxiv.org/abs/quant-ph/0406176v5 + + Additionally implements some extra optimizations: remove zero rotations and + double cnots. + """ + InitializeGate(self.params) q = QuantumRegister(self.num_qubits, 'q') - circ = QuantumCircuit(q, name='init_def') - circ.append(Initialize(desired_vector, reset=False), q[:]) - self.definition = circ + initialize_circuit = QuantumCircuit(q, name='init_def') + for qubit in q: + initialize_circuit.append(Reset(), [qubit]) + initialize_circuit.append(InitializeGate(self.params), [q[:]]) + + self.definition = initialize_circuit + + def broadcast_arguments(self, qargs, cargs): + flat_qargs = [qarg for sublist in qargs for qarg in sublist] + + if self.num_qubits != len(flat_qargs): + raise QiskitError("Initialize parameter vector has %d elements, therefore expects %s " + "qubits. However, %s were provided." % + (2**self.num_qubits, self.num_qubits, len(flat_qargs))) + yield flat_qargs, [] + + def validate_parameter(self, parameter): + """Initialize instruction parameter can be int, float, and complex.""" + if isinstance(parameter, (int, float, complex)): + return complex(parameter) + elif isinstance(parameter, np.number): + return complex(parameter.item()) + else: + raise CircuitError("invalid param type {0} for instruction " + "{1}".format(type(parameter), self.name)) def initialize(self, params, qubits): From f90b5f03cf3b3c8f6da7e934eda8931a7f47f984 Mon Sep 17 00:00:00 2001 From: incud Date: Fri, 30 Oct 2020 11:41:17 +0100 Subject: [PATCH 3/6] Update qiskit/extensions/quantum_initializer/initializer.py Co-authored-by: Julien Gacon --- qiskit/extensions/quantum_initializer/initializer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 13884063ff1f..457d79e7bed2 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -358,7 +358,6 @@ def _define(self): Additionally implements some extra optimizations: remove zero rotations and double cnots. """ - InitializeGate(self.params) q = QuantumRegister(self.num_qubits, 'q') initialize_circuit = QuantumCircuit(q, name='init_def') for qubit in q: From 586c913b5d92dab2be3d52cbffe26a4462390a95 Mon Sep 17 00:00:00 2001 From: Massimiliano Incudini Date: Fri, 30 Oct 2020 14:44:54 +0100 Subject: [PATCH 4/6] merged with Initialize with labels --- .../probability_distributions/lognormal.py | 4 +- .../probability_distributions/normal.py | 4 +- .../quantum_initializer/initializer.py | 60 +++++++++++-------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/qiskit/circuit/library/probability_distributions/lognormal.py b/qiskit/circuit/library/probability_distributions/lognormal.py index 64e65bda4ee7..24c5c38c4030 100644 --- a/qiskit/circuit/library/probability_distributions/lognormal.py +++ b/qiskit/circuit/library/probability_distributions/lognormal.py @@ -156,8 +156,8 @@ def __init__(self, if upto_diag: self.isometry(np.sqrt(normalized_probabilities), self.qubits, None) else: - from qiskit.extensions import Initialize # pylint: disable=cyclic-import - initialize = Initialize(np.sqrt(normalized_probabilities)) + from qiskit.extensions import InitializeGate # pylint: disable=cyclic-import + initialize = InitializeGate(np.sqrt(normalized_probabilities)) circuit = initialize.gates_to_uncompute().inverse() self.compose(circuit, inplace=True) diff --git a/qiskit/circuit/library/probability_distributions/normal.py b/qiskit/circuit/library/probability_distributions/normal.py index 22b01849628f..78564f0f2e11 100644 --- a/qiskit/circuit/library/probability_distributions/normal.py +++ b/qiskit/circuit/library/probability_distributions/normal.py @@ -193,8 +193,8 @@ def __init__(self, if upto_diag: self.isometry(np.sqrt(normalized_probabilities), self.qubits, None) else: - from qiskit.extensions import Initialize # pylint: disable=cyclic-import - initialize = Initialize(np.sqrt(normalized_probabilities)) + from qiskit.extensions import InitializeGate # pylint: disable=cyclic-import + initialize = InitializeGate(np.sqrt(normalized_probabilities)) circuit = initialize.gates_to_uncompute().inverse() self.compose(circuit, inplace=True) diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 457d79e7bed2..49e40132286b 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -55,16 +55,20 @@ def __init__(self, params, label=None): if isinstance(params, str): self._fromlabel = True num_qubits = len(params) + elif isinstance(params, list) and isinstance(params[0], str): + self._fromlabel = True + params = "".join(params) + num_qubits = len(params) else: self._fromlabel = False try: - num_qubits = math.log2(len(params)) - except: - raise QiskitError("Given params are not a statevector") + num_qubits = math.log2(len(params)) + except: + raise QiskitError("Given params are not a statevector") # Check if param is a power of 2 if num_qubits == 0 or not num_qubits.is_integer(): - raise QiskitError("Desired statevector length not a positive power of 2.") + raise QiskitError("Desired statevector length is {} which is not a positive power of 2.".format(params)) # Check if probabilities (amplitudes squared) sum to 1 if not math.isclose(sum(np.absolute(params) ** 2), 1.0, @@ -316,13 +320,7 @@ def validate_parameter(self, parameter): class Initialize(Instruction): - """Complex amplitude initialization. - - Class that implements the (complex amplitude) initialization of some - flexible collection of qubit registers (assuming the qubits are in the - zero state). - Note that Initialize is an Instruction and not a Gate since it contains a reset instruction, - which is not unitary. + """Apply reset then the complex amplitude initialization. """ def __init__(self, params): @@ -330,21 +328,26 @@ def __init__(self, params): params (list): vector of complex amplitudes to initialize to """ - try: - num_qubits = math.log2(len(params)) - except: - raise QiskitError("Given params are not a statevector") + if isinstance(params, str): + self._fromlabel = True + num_qubits = len(params) + else: + self._fromlabel = False + try: + num_qubits = math.log2(len(params)) + except: + raise QiskitError("Given params are not a statevector") - # Check if param is a power of 2 - if num_qubits == 0 or not num_qubits.is_integer(): - raise QiskitError("Desired statevector length not a positive power of 2.") + # Check if param is a power of 2 + if num_qubits == 0 or not num_qubits.is_integer(): + raise QiskitError("Desired statevector length not a positive power of 2.") - # Check if probabilities (amplitudes squared) sum to 1 - if not math.isclose(sum(np.absolute(params) ** 2), 1.0, - abs_tol=_EPS): - raise QiskitError("Sum of amplitudes-squared does not equal one.") + # Check if probabilities (amplitudes squared) sum to 1 + if not math.isclose(sum(np.absolute(params) ** 2), 1.0, + abs_tol=_EPS): + raise QiskitError("Sum of amplitudes-squared does not equal one.") - num_qubits = int(num_qubits) + num_qubits = int(num_qubits) super().__init__("initialize", num_qubits, 0, params) @@ -376,7 +379,16 @@ def broadcast_arguments(self, qargs, cargs): yield flat_qargs, [] def validate_parameter(self, parameter): - """Initialize instruction parameter can be int, float, and complex.""" + """Initialize instruction parameter can be str, int, float, and complex.""" + + # Initialize instruction parameter can be str + if self._fromlabel: + if parameter in ['0', '1', '+', '-', 'l', 'r']: + return parameter + raise CircuitError("invalid param label {0} for instruction {1}. Label should be " + "0, 1, +, -, l, or r ".format(type(parameter), self.name)) + + # Initialize instruction parameter can be int, float, and complex. if isinstance(parameter, (int, float, complex)): return complex(parameter) elif isinstance(parameter, np.number): From 296b7fadd0a0c5fe390f4daf6ff68d168df5a905 Mon Sep 17 00:00:00 2001 From: Massimiliano Incudini Date: Fri, 30 Oct 2020 15:25:34 +0100 Subject: [PATCH 5/6] fixed style --- qiskit/extensions/quantum_initializer/initializer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 49e40132286b..10a7098b20ba 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -63,12 +63,12 @@ def __init__(self, params, label=None): self._fromlabel = False try: num_qubits = math.log2(len(params)) - except: + except ValueError: raise QiskitError("Given params are not a statevector") # Check if param is a power of 2 if num_qubits == 0 or not num_qubits.is_integer(): - raise QiskitError("Desired statevector length is {} which is not a positive power of 2.".format(params)) + raise QiskitError("Desired statevector length is not a power of 2.") # Check if probabilities (amplitudes squared) sum to 1 if not math.isclose(sum(np.absolute(params) ** 2), 1.0, @@ -335,7 +335,7 @@ def __init__(self, params): self._fromlabel = False try: num_qubits = math.log2(len(params)) - except: + except ValueError: raise QiskitError("Given params are not a statevector") # Check if param is a power of 2 From 7901fb19f67d00eeec50759a469bffc5d48179b3 Mon Sep 17 00:00:00 2001 From: Massimiliano Incudini Date: Fri, 30 Oct 2020 17:37:27 +0100 Subject: [PATCH 6/6] fixed exception with non string, non list parameters --- qiskit/extensions/quantum_initializer/initializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/extensions/quantum_initializer/initializer.py b/qiskit/extensions/quantum_initializer/initializer.py index 10a7098b20ba..92ddfad4df2a 100644 --- a/qiskit/extensions/quantum_initializer/initializer.py +++ b/qiskit/extensions/quantum_initializer/initializer.py @@ -63,7 +63,7 @@ def __init__(self, params, label=None): self._fromlabel = False try: num_qubits = math.log2(len(params)) - except ValueError: + except (ValueError, TypeError): raise QiskitError("Given params are not a statevector") # Check if param is a power of 2 @@ -335,7 +335,7 @@ def __init__(self, params): self._fromlabel = False try: num_qubits = math.log2(len(params)) - except ValueError: + except (ValueError, TypeError): raise QiskitError("Given params are not a statevector") # Check if param is a power of 2