예제 #1
0
 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)
예제 #2
0
 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)
예제 #3
0
 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
예제 #5
0
    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
예제 #6
0
 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)
예제 #7
0
 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)
예제 #8
0
 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)
예제 #9
0
    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)
예제 #10
0
    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
예제 #11
0
    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
예제 #13
0
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,
예제 #14
0
    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)
예제 #15
0
    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
예제 #16
0
    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