def test_bind_parameter_list(self): """bind parameters list test""" thetas = ParameterVector("θ", length=6) op = ( (thetas[1] * I ^ Z) + (thetas[2] * X ^ X) + (thetas[3] * Z ^ I) + (thetas[4] * Y ^ Z) + (thetas[5] * Z ^ Z) ) op = thetas[0] * op evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero evo = evolution.convert(wf) param_list = np.transpose([np.arange(10, 16), np.arange(2, 8), np.arange(30, 36)]).tolist() means = evo.assign_parameters({thetas: param_list}) self.assertIsInstance(means, ListOp) # Check that the no parameters are in the circuit for p in thetas[1:]: for circop in means.oplist: self.assertNotIn(p, circop.to_circuit().parameters) # Check that original circuit is unchanged for p in thetas: self.assertIn(p, evo.to_circuit().parameters)
def test_bind_parameters(self): """bind parameters test""" thetas = ParameterVector("θ", length=6) op = ((thetas[1] * I ^ Z) + (thetas[2] * X ^ X) + (thetas[3] * Z ^ I) + (thetas[4] * Y ^ Z) + (thetas[5] * Z ^ Z)) op = thetas[0] * op evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero wf = wf.assign_parameters({thetas: np.arange(10, 16)}) mean = evolution.convert(wf) circuit_params = mean.to_circuit().parameters # Check that the no parameters are in the circuit for p in thetas[1:]: self.assertNotIn(p, circuit_params)
def test_parameterized_evolution(self): """parameterized evolution test""" thetas = ParameterVector("θ", length=7) op = ((thetas[0] * I ^ I) + (thetas[1] * I ^ Z) + (thetas[2] * X ^ X) + (thetas[3] * Z ^ I) + (thetas[4] * Y ^ Z) + (thetas[5] * Z ^ Z)) op = op * thetas[6] evolution = PauliTrotterEvolution(trotter_mode="trotter", reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero mean = evolution.convert(wf) circuit = mean.to_circuit() # Check that all parameters are in the circuit for p in thetas: self.assertIn(p, circuit.parameters) # Check that the identity-parameters only exist as global phase self.assertNotIn(thetas[0], circuit._parameter_table.get_keys())
def __init__(self, operators: Optional[Union[OperatorBase, List[OperatorBase]]] = None, reps: int = 1, evolution: Optional[EvolutionBase] = None, insert_barriers: bool = False, name: str = 'EvolvedOps', initial_state: Optional[QuantumCircuit] = None): """ Args: operators: The operators to evolve. reps: The number of times to repeat the evolved operators. evolution: An opflow converter object to construct the evolution. Defaults to Trotterization. insert_barriers: Whether to insert barriers in between each evolution. name: The name of the circuit. initial_state: A `QuantumCircuit` object to prepend to the circuit. """ if evolution is None: evolution = PauliTrotterEvolution() super().__init__(name=name) self._operators = None self._evolution = evolution self._reps = reps self._insert_barriers = insert_barriers self._initial_state = initial_state # use setter to set operators self.operators = operators
def __init__(self, qubit_converter: Optional[QubitConverter] = None, num_particles: Optional[Tuple[int, int]] = None, num_spin_orbitals: Optional[int] = None, excitations: Optional[Union[str, int, List[int], Callable[[int, Tuple[int, int]], List[Tuple[Tuple[int, ...], Tuple[int, ...]]]] ]] = None, alpha_spin: bool = True, beta_spin: bool = True, max_spin_excitation: Optional[int] = None, reps: int = 1, initial_state: Optional[QuantumCircuit] = None): """ Args: qubit_converter: the QubitConverter instance which takes care of mapping a :class:`~.SecondQuantizedOp` to a :class:`PauliSumOp` as well as performing all configured symmetry reductions on it. num_particles: the tuple of the number of alpha- and beta-spin particles. num_spin_orbitals: the number of spin orbitals. excitations: this can be any of the following types: :`str`: which contains the types of excitations. Allowed characters are + `s` for singles + `d` for doubles + `t` for triples + `q` for quadruples :`int`: a single, positive integer which denotes the number of excitations (1 == `s`, etc.) :`List[int]`: a list of positive integers generalizing the above :`Callable`: a function which is used to generate the excitations. The callable must take the __keyword__ arguments `num_spin_orbitals` and `num_particles` (with identical types to those explained above) and must return a `List[Tuple[Tuple[int, ...], Tuple[int, ...]]]`. For more information on how to write such a callable refer to the default method :meth:`generate_fermionic_excitations`. alpha_spin: boolean flag whether to include alpha-spin excitations. beta_spin: boolean flag whether to include beta-spin excitations. max_spin_excitation: the largest number of excitations within a spin. E.g. you can set this to 1 and `num_excitations` to 2 in order to obtain only mixed-spin double excitations (alpha,beta) but no pure-spin double excitations (alpha,alpha or beta,beta). reps: The number of times to repeat the evolved operators. initial_state: A `QuantumCircuit` object to prepend to the circuit. """ self._qubit_converter = qubit_converter self._num_particles = num_particles self._num_spin_orbitals = num_spin_orbitals self._excitations = excitations self._alpha_spin = alpha_spin self._beta_spin = beta_spin self._max_spin_excitation = max_spin_excitation super().__init__(reps=reps, evolution=PauliTrotterEvolution(), initial_state=initial_state) # We cache these, because the generation may be quite expensive (depending on the generator) # and the user may want quick access to inspect these. Also, it speeds up testing for the # same reason! self._excitation_ops: List[SecondQuantizedOp] = None
def test_mixed_evolution(self): """ bind parameters test """ thetas = ParameterVector('θ', length=6) op = (thetas[1] * (I ^ Z).to_matrix_op()) + \ (thetas[2] * (X ^ X)).to_matrix_op() + \ (thetas[3] * Z ^ I) + \ (thetas[4] * Y ^ Z).to_circuit_op() + \ (thetas[5] * (Z ^ I).to_circuit_op()) op = thetas[0] * op evolution = PauliTrotterEvolution(trotter_mode='trotter', reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero wf = wf.assign_parameters({thetas: np.arange(10, 16)}) mean = evolution.convert(wf) circuit_params = mean.to_circuit().parameters # Check that the no parameters are in the circuit for p in thetas[1:]: self.assertNotIn(p, circuit_params)
def test_parameterized_evolution(self): """ parameterized evolution test """ thetas = ParameterVector('θ', length=7) op = (thetas[0] * I ^ I) + \ (thetas[1] * I ^ Z) + \ (thetas[2] * X ^ X) + \ (thetas[3] * Z ^ I) + \ (thetas[4] * Y ^ Z) + \ (thetas[5] * Z ^ Z) op = op * thetas[6] evolution = PauliTrotterEvolution(trotter_mode='trotter', reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero mean = evolution.convert(wf) circuit_params = mean.to_circuit().parameters # Check that the non-identity parameters are in the circuit for p in thetas[1:]: self.assertIn(p, circuit_params) self.assertNotIn(thetas[0], circuit_params)
def test_bind_circuit_parameters(self): """ bind circuit parameters test """ thetas = ParameterVector('θ', length=6) op = (thetas[1] * I ^ Z) + \ (thetas[2] * X ^ X) + \ (thetas[3] * Z ^ I) + \ (thetas[4] * Y ^ Z) + \ (thetas[5] * Z ^ Z) op = thetas[0] * op evolution = PauliTrotterEvolution(trotter_mode='trotter', reps=1) # wf = (Pl^Pl) + (Ze^Ze) wf = (op).exp_i() @ CX @ (H ^ I) @ Zero evo = evolution.convert(wf) mean = evo.assign_parameters({thetas: np.arange(10, 16)}) # Check that the no parameters are in the circuit for p in thetas[1:]: self.assertNotIn(p, mean.to_circuit().parameters) # Check that original circuit is unchanged for p in thetas: self.assertIn(p, evo.to_circuit().parameters)
def test_trotter_with_identity(self): """ trotterization of operator with identity term """ op = (2.0 * I ^ I) + (Z ^ Y) exact_matrix = scipy.linalg.expm(-1j * op.to_matrix()) evo = PauliTrotterEvolution(trotter_mode='suzuki', reps=2) with self.subTest('all PauliOp terms'): circ_op = evo.convert(EvolvedOp(op)) circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) with self.subTest('MatrixOp identity term'): op = (2.0 * I ^ I).to_matrix_op() + (Z ^ Y) circ_op = evo.convert(EvolvedOp(op)) circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix) with self.subTest('CircuitOp identity term'): op = (2.0 * I ^ I).to_circuit_op() + (Z ^ Y) circ_op = evo.convert(EvolvedOp(op)) circuit_matrix = qiskit.quantum_info.Operator(circ_op.to_circuit()).data np.testing.assert_array_almost_equal(exact_matrix, circuit_matrix)
def __init__( self, qubit_converter: Optional[QubitConverter] = None, num_modals: Optional[List[int]] = None, excitations: Optional[Union[str, int, List[int], Callable[ [int, Tuple[int, int]], List[Tuple[Tuple[int, ...], Tuple[int, ...]]], ], ]] = None, reps: int = 1, initial_state: Optional[QuantumCircuit] = None, ): """ Args: qubit_converter: the QubitConverter instance which takes care of mapping a :class:`~.SecondQuantizedOp` to a :class:`PauliSumOp` as well as performing all configured symmetry reductions on it. num_modals: Is a list defining the number of modals per mode. E.g. for a 3 modes system with 4 modals per mode num_modals = [4,4,4] excitations: this can be any of the following types: :`str`: which contains the types of excitations. Allowed characters are + `s` for singles + `d` for doubles + `t` for triples + `q` for quadruples :`int`: a single, positive integer which denotes the number of excitations (1 == `s`, etc.) :`List[int]`: a list of positive integers generalizing the above :`Callable`: a function which is used to generate the excitations. The callable must take the __keyword__ argument `num_modals` `num_particles` (with identical types to those explained above) and must return a `List[Tuple[Tuple[int, ...], Tuple[int, ...]]]`. For more information on how to write such a callable refer to the default method :meth:`~qiskit_nature.circuit.library.ansatzes.utils.generate_vibration_excitations`. reps: number of repetitions of basic module initial_state: A `QuantumCircuit` object to prepend to the circuit. """ self._qubit_converter = qubit_converter self._num_modals = num_modals self._excitations = excitations super().__init__(reps=reps, evolution=PauliTrotterEvolution(), initial_state=initial_state) # We cache these, because the generation may be quite expensive (depending on the generator) # and the user may want quick access to inspect these. Also, it speeds up testing for the # same reason! self._excitation_ops: Optional[List[SecondQuantizedOp]] = None
def __init__( self, operators=None, reps: int = 1, evolution=None, insert_barriers: bool = False, name: str = "EvolvedOps", parameter_prefix: Union[str, List[str]] = "t", initial_state: Optional[QuantumCircuit] = None, ): """ Args: operators (Optional[Union[OperatorBase, QuantumCircuit, list]): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). reps: The number of times to repeat the evolved operators. evolution (Optional[EvolutionBase]): An opflow converter object to construct the evolution. Defaults to Trotterization. insert_barriers: Whether to insert barriers in between each evolution. name: The name of the circuit. parameter_prefix: Set the names of the circuit parameters. If a string, the same prefix will be used for each parameters. Can also be a list to specify a prefix per operator. initial_state: A `QuantumCircuit` object to prepend to the circuit. """ if evolution is None: # pylint: disable=cyclic-import from qiskit.opflow import PauliTrotterEvolution evolution = PauliTrotterEvolution() super().__init__( initial_state=initial_state, parameter_prefix=parameter_prefix, reps=reps, insert_barriers=insert_barriers, name=name, ) self._operators = None if operators is not None: self.operators = operators self._evolution = evolution # a list of which operators are parameterized, used for internal settings self._ops_are_parameterized = None
def __init__( self, operators=None, reps: int = 1, evolution=None, insert_barriers: bool = False, name: str = "EvolvedOps", initial_state: Optional[QuantumCircuit] = None, ): """ Args: operators (Optional[Union[OperatorBase, QuantumCircuit, list]): The operators to evolve. If a circuit is passed, we assume it implements an already evolved operator and thus the circuit is not evolved again. Can be a single operator (circuit) or a list of operators (and circuits). reps: The number of times to repeat the evolved operators. evolution (Optional[EvolutionBase]): An opflow converter object to construct the evolution. Defaults to Trotterization. insert_barriers: Whether to insert barriers in between each evolution. name: The name of the circuit. initial_state: A `QuantumCircuit` object to prepend to the circuit. """ if evolution is None: # pylint: disable=cyclic-import from qiskit.opflow import PauliTrotterEvolution evolution = PauliTrotterEvolution() if operators is not None: operators = _validate_operators(operators) super().__init__(name=name) self._operators = operators self._evolution = evolution self._reps = reps self._insert_barriers = insert_barriers self._initial_state = initial_state
state_prep_circ.data = [ datum for datum in state_prep_circ.data if datum[0].name != "reset" ] print(state_prep_circ) # We can now evolve this state under an operator for a given duration. from qiskit.opflow import PauliTrotterEvolution from qiskit.opflow.primitive_ops import PauliSumOp from qiskit.quantum_info import Pauli duration = 1.2 op = PauliSumOp.from_list([("XXI", 0.3), ("YYI", 0.5 + 1j * 0.2), ("ZZZ", -0.4)]) evolved_op = (duration * op).exp_i() evolution_circ = PauliTrotterEvolution(reps=1).convert(evolved_op).to_circuit() print(evolution_circ) state_prep_circ += evolution_circ # Now that we have a circuit, `pytket` can take this and start operating on it directly. For example, we can apply some basic compilation passes to simplify it. from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit tk_circ = qiskit_to_tk(state_prep_circ) from pytket.passes import ( SequencePass, CliffordSimp, DecomposeBoxes, KAKDecomposition,
def estimate( self, hamiltonian: OperatorBase, state_preparation: Optional[StateFn] = None, evolution: Optional[EvolutionBase] = None, bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: """Run the Hamiltonian phase estimation algorithm. Args: hamiltonian: A Hermitian operator. state_preparation: The ``StateFn`` to be prepared, whose eigenphase will be measured. If this parameter is omitted, no preparation circuit will be run and input state will be the all-zero state in the computational basis. evolution: An evolution converter that generates a unitary from ``hamiltonian``. If ``None``, then the default ``PauliTrotterEvolution`` is used. bound: An upper bound on the absolute value of the eigenvalues of ``hamiltonian``. If omitted, then ``hamiltonian`` must be a Pauli sum, or a ``PauliOp``, in which case a bound will be computed. If ``hamiltonian`` is a ``MatrixOp``, then ``bound`` may not be ``None``. The tighter the bound, the higher the resolution of computed phases. Returns: HamiltonianPhaseEstimationResult instance containing the result of the estimation and diagnostic information. Raises: ValueError: If ``bound`` is ``None`` and ``hamiltonian`` is not a Pauli sum, i.e. a ``PauliSumOp`` or a ``SummedOp`` whose terms are of type ``PauliOp``. TypeError: If ``evolution`` is not of type ``EvolutionBase``. """ if evolution is None: evolution = PauliTrotterEvolution() elif not isinstance(evolution, EvolutionBase): raise TypeError( f'Expecting type EvolutionBase, got {type(evolution)}') if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() elif isinstance(hamiltonian, PauliOp): hamiltonian = SummedOp([hamiltonian]) if isinstance(hamiltonian, SummedOp): # remove identitiy terms # The term propto the identity is removed from hamiltonian. # This is done for three reasons: # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some # cases. # 2. Allow working with a simpler Hamiltonian, one with fewer terms. # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. # occupies more of the range of values representable by the qubit register. # The coefficient of this term will be added to the eigenvalues. id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) # get the rescaling object pe_scale = self._get_scale(hamiltonian_no_id, bound) # get the unitary unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) elif isinstance(hamiltonian, MatrixOp): if bound is None: raise ValueError( 'bound must be specified if Hermitian operator is MatrixOp' ) # Do not subtract an identity term from the matrix, so do not compensate. id_coefficient = 0.0 pe_scale = self._get_scale(hamiltonian, bound) unitary = self._get_unitary(hamiltonian, pe_scale, evolution) else: raise TypeError( f'Hermitian operator of type {type(hamiltonian)} not supported.' ) if state_preparation is not None: state_preparation = state_preparation.to_circuit_op().to_circuit() # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) return HamiltonianPhaseEstimationResult( phase_estimation_result=phase_estimation_result, id_coefficient=id_coefficient, phase_estimation_scale=pe_scale)
def __init__( self, qubit_converter: QubitConverter | None = None, num_particles: tuple[int, int] | None = None, num_spin_orbitals: int | None = None, excitations: str | int | list[int] | Callable[[int, tuple[int, int]], list[tuple[tuple[int, ...], tuple[int, ...]]], ] | None = None, alpha_spin: bool = True, beta_spin: bool = True, max_spin_excitation: int | None = None, generalized: bool = False, preserve_spin: bool = True, reps: int = 1, initial_state: QuantumCircuit | None = None, ): """ Args: qubit_converter: the QubitConverter instance which takes care of mapping a :class:`~.SecondQuantizedOp` to a :class:`PauliSumOp` as well as performing all configured symmetry reductions on it. num_particles: the tuple of the number of alpha- and beta-spin particles. num_spin_orbitals: the number of spin orbitals. excitations: this can be any of the following types: :`str`: which contains the types of excitations. Allowed characters are + `s` for singles + `d` for doubles + `t` for triples + `q` for quadruples :`int`: a single, positive integer which denotes the number of excitations (1 == `s`, 2 == `d`, etc.) :`list[int]`: a list of positive integers generalizing the above to multiple numbers of excitations ([1, 2] == `sd`, etc.) :`Callable`: a function which is used to generate the excitations. The callable must take the __keyword__ arguments `num_spin_orbitals` and `num_particles` (with identical types to those explained above) and must return a `list[tuple[tuple[int, ...], tuple[int, ...]]]`. For more information on how to write such a callable refer to the default method :meth:`~qiskit_nature.second_q.circuit.library.ansatzes.utils.\ generate_fermionic_excitations`. alpha_spin: boolean flag whether to include alpha-spin excitations. beta_spin: boolean flag whether to include beta-spin excitations. max_spin_excitation: the largest number of excitations within a spin. E.g. you can set this to 1 and `num_excitations` to 2 in order to obtain only mixed-spin double excitations (alpha,beta) but no pure-spin double excitations (alpha,alpha or beta,beta). generalized: boolean flag whether or not to use generalized excitations, which ignore the occupation of the spin orbitals. As such, the set of generalized excitations is only determined from the number of spin orbitals and independent from the number of particles. preserve_spin: boolean flag whether or not to preserve the particle spins. reps: The number of times to repeat the evolved operators. initial_state: A `QuantumCircuit` object to prepend to the circuit. Note that this setting does _not_ influence the `excitations`. When relying on the default generation method (i.e. not providing a `Callable` to `excitations`), these will always be constructed with respect to a `HartreeFock` reference state. """ self._qubit_converter = qubit_converter self._num_particles = num_particles self._num_spin_orbitals = num_spin_orbitals self._excitations = excitations self._alpha_spin = alpha_spin self._beta_spin = beta_spin self._max_spin_excitation = max_spin_excitation self._generalized = generalized self._preserve_spin = preserve_spin super().__init__(reps=reps, evolution=PauliTrotterEvolution(), initial_state=initial_state) # To give read access to the actual excitation list that UCC is using. self._excitation_list: list[tuple[tuple[int, ...], tuple[int, ...]]] | None = None # We cache these, because the generation may be quite expensive (depending on the generator) # and the user may want quick access to inspect these. Also, it speeds up testing for the # same reason! self._excitation_ops: list[SecondQuantizedOp] = None # Our parent, EvolvedOperatorAnsatz, sets qregs when it knows the # number of qubits, which it gets from the operators. Getting the # operators here will build them if configuration already allows. # This will allow the circuit to be fully built/valid when it's # possible at this stage. _ = self.operators
def __init__( self, qubit_converter: QubitConverter | None = None, num_modals: list[int] | None = None, excitations: str | int | list[int] | Callable[ [int, tuple[int, int]], list[tuple[tuple[int, ...], tuple[int, ...]]], ] | None = None, reps: int = 1, initial_state: QuantumCircuit | None = None, ): """ Args: qubit_converter: the QubitConverter instance which takes care of mapping a :class:`~.SecondQuantizedOp` to a :class:`PauliSumOp` as well as performing all configured symmetry reductions on it. num_modals: Is a list defining the number of modals per mode. E.g. for a 3 modes system with 4 modals per mode num_modals = [4,4,4] excitations: this can be any of the following types: :`str`: which contains the types of excitations. Allowed characters are + `s` for singles + `d` for doubles + `t` for triples + `q` for quadruples :`int`: a single, positive integer which denotes the number of excitations (1 == `s`, 2 == `d`, etc.) :`list[int]`: a list of positive integers generalizing the above to multiple numbers of excitations ([1, 2] == `sd`, etc.) :`Callable`: a function which is used to generate the excitations. The callable must take the __keyword__ argument `num_modals` `num_particles` (with identical types to those explained above) and must return a `list[tuple[tuple[int, ...], tuple[int, ...]]]`. For more information on how to write such a callable refer to the default method :meth:`~qiskit_nature.second_q.circuit.library.ansatzes.utils.\ generate_vibration_excitations`. reps: number of repetitions of basic module initial_state: A `QuantumCircuit` object to prepend to the circuit. """ self._qubit_converter = qubit_converter self._num_modals = num_modals self._excitations = excitations super().__init__(reps=reps, evolution=PauliTrotterEvolution(), initial_state=initial_state) # To give read access to the actual excitation list that UVCC is using. self._excitation_list: list[tuple[tuple[int, ...], tuple[int, ...]]] | None = None # We cache these, because the generation may be quite expensive (depending on the generator) # and the user may want quick access to inspect these. Also, it speeds up testing for the # same reason! self._excitation_ops: list[SecondQuantizedOp] | None = None # Our parent, EvolvedOperatorAnsatz, sets qregs when it knows the # number of qubits, which it gets from the operators. Getting the # operators here will build them if configuration already allows. # This will allow the circuit to be fully built/valid when it's # possible at this stage. _ = self.operators