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/__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 99dc3a8ab8e3..92ddfad4df2a 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, XGate from qiskit.circuit.library.standard_gates.h import HGate @@ -32,17 +33,15 @@ _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): + def __init__(self, params, label=None): """Create new initialize composite. params (str or list): @@ -56,13 +55,20 @@ def __init__(self, params): 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 - num_qubits = math.log2(len(params)) + try: + num_qubits = math.log2(len(params)) + except (ValueError, TypeError): + 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 not a power of 2.") # Check if probabilities (amplitudes squared) sum to 1 if not math.isclose(sum(np.absolute(params) ** 2), 1.0, @@ -71,7 +77,7 @@ def __init__(self, params): num_qubits = int(num_qubits) - super().__init__("initialize", num_qubits, 0, params) + super().__init__('init', int(num_qubits), params, label=label) def _define(self): self.definition = self._define_fromlabel() if self._fromlabel else self._define_synthesis() @@ -118,8 +124,6 @@ def _define_synthesis(self): q = QuantumRegister(self.num_qubits, 'q') initialize_circuit = QuantumCircuit(q, name='init_def') - for qubit in q: - initialize_circuit.append(Reset(), [qubit]) initialize_circuit.append(initialize_instr, q[:]) return initialize_circuit @@ -141,7 +145,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) @@ -188,7 +192,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) @@ -315,6 +319,85 @@ def validate_parameter(self, parameter): "{1}".format(type(parameter), self.name)) +class Initialize(Instruction): + """Apply reset then the complex amplitude initialization. + """ + + def __init__(self, params): + """Create new initialize composite. + + params (list): vector of complex amplitudes to initialize to + """ + if isinstance(params, str): + self._fromlabel = True + num_qubits = len(params) + else: + self._fromlabel = False + try: + num_qubits = math.log2(len(params)) + except (ValueError, TypeError): + 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 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): + """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. + """ + q = QuantumRegister(self.num_qubits, 'q') + 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 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): + return complex(parameter.item()) + else: + raise CircuitError("invalid param type {0} for instruction " + "{1}".format(type(parameter), self.name)) + + 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 a18ee4da8666..82fcc1847da5 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -201,7 +201,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: