예제 #1
0
    def test_lie_trotter_two_qubit_correct_order(self):
        """Test that evolutions on two qubit operators are in the right order.

        Regression test of Qiskit/qiskit-terra#7544.
        """
        operator = I ^ Z ^ Z
        time = 0.5
        exact = scipy.linalg.expm(-1j * time * operator.to_matrix())
        lie_trotter = PauliEvolutionGate(operator,
                                         time,
                                         synthesis=LieTrotter())

        self.assertTrue(Operator(lie_trotter).equiv(exact))
    def test_qdrift_evolution(self):
        """Test QDrift on an example."""
        op = 0.1 * (Z ^ Z) + (X ^ I) + (I ^ X) + 0.2 * (X ^ X)
        reps = 20
        qdrift = PauliEvolutionGate(op,
                                    time=0.5 / reps,
                                    synthesis=QDrift(reps=reps)).definition
        exact = scipy.linalg.expm(-0.5j * op.to_matrix()).dot(np.eye(4)[0, :])

        def energy(evo):
            return Statevector(evo).expectation_value(op.to_matrix())

        self.assertAlmostEqual(energy(exact), energy(qdrift), places=2)
예제 #3
0
    def test_basic_xx(self):
        """Test to route an XX-based evolution op.

        The op is :code:`[("XXII", -1), ("IIXX", 1), ("XIIX", -2), ("IXIX", 2)]`.

        The expected circuit is:

        ..parsed-literal::

                  ┌────────────────┐
            q_0: ─┤0               ├─X─────────────────────────────────────────
                  │  exp(-i XX)(3) │ │ ┌─────────────────┐
            q_1: ─┤1               ├─X─┤0                ├─X───────────────────
                 ┌┴────────────────┤   │  exp(-i XX)(-6) │ │ ┌────────────────┐
            q_2: ┤0                ├─X─┤1                ├─X─┤0               ├
                 │  exp(-i XX)(-3) │ │ └─────────────────┘   │  exp(-i XX)(6) │
            q_3: ┤1                ├─X───────────────────────┤1               ├
                 └─────────────────┘                         └────────────────┘
        """

        op = PauliSumOp.from_list([("XXII", -1), ("IIXX", 1), ("XIIX", -2),
                                   ("IXIX", 2)])

        circ = QuantumCircuit(4)
        circ.append(PauliEvolutionGate(op, 3), range(4))

        swapped = self.pm_.run(circ)

        expected = QuantumCircuit(4)
        expected.append(PauliEvolutionGate(Pauli("XX"), 3), (0, 1))
        expected.append(PauliEvolutionGate(Pauli("XX"), -3), (2, 3))
        expected.swap(0, 1)
        expected.swap(2, 3)
        expected.append(PauliEvolutionGate(Pauli("XX"), -6), (1, 2))
        expected.swap(1, 2)
        expected.append(PauliEvolutionGate(Pauli("XX"), 6), (2, 3))

        self.assertEqual(swapped, expected)
예제 #4
0
    def test_rzx_order(self):
        """Test ZX is mapped onto the correct qubits."""
        evo_gate = PauliEvolutionGate(X ^ Z)
        decomposed = evo_gate.definition.decompose()

        ref = QuantumCircuit(2)
        ref.h(1)
        ref.cx(1, 0)
        ref.rz(2.0, 0)
        ref.cx(1, 0)
        ref.h(1)

        # don't use circuit equality since RZX here decomposes with RZ on the bottom
        self.assertTrue(Operator(decomposed).equiv(ref))
예제 #5
0
    def test_qdrift_manual(self, op, time, reps, sampled_ops):
        """Test the evolution circuit of Suzuki Trotter against a manually constructed circuit."""
        qdrift = QDrift(reps=reps)
        evo_gate = PauliEvolutionGate(op, time, synthesis=qdrift)
        evo_gate.definition.decompose()

        # manually construct expected evolution
        expected = QuantumCircuit(1)
        for pauli in sampled_ops:
            if pauli[0].to_label() == "X":
                expected.rx(2 * pauli[1], 0)
            elif pauli[0].to_label() == "Y":
                expected.ry(2 * pauli[1], 0)

        self.assertTrue(Operator(evo_gate.definition).equiv(expected))
예제 #6
0
    def setUp(self):
        """Setup useful variables."""
        super().setUp()

        # A fully connected problem.
        op = PauliSumOp.from_list([("IIZZ", 1), ("IZIZ", 1), ("ZIIZ", 1),
                                   ("IZZI", 1), ("ZIZI", 1), ("ZZII", 1)])
        self.circ = QuantumCircuit(4)
        self.circ.append(PauliEvolutionGate(op, 1), range(4))

        self.swap_cmap = CouplingMap(couplinglist=[(0, 1), (1, 2), (2, 3)])

        # This swap strategy does not reach full connectivity.
        self.swap_strat = SwapStrategy(self.swap_cmap,
                                       swap_layers=(((0, 1), (2, 3)), ))
    def test_op_list_evolutiongate(self):
        """Test loading a circuit with evolution gate works."""
        evo = PauliEvolutionGate([(Z ^ I) + (I ^ Z)] * 5, time=0.2, synthesis=None)
        qc = QuantumCircuit(2)
        qc.append(evo, range(2))
        qpy_file = io.BytesIO()
        dump(qc, qpy_file)
        qpy_file.seek(0)
        new_circ = load(qpy_file)[0]

        self.assertEqual(qc, new_circ)
        self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])

        new_evo = new_circ.data[0][0]
        self.assertIsInstance(new_evo, PauliEvolutionGate)
예제 #8
0
    def test_dag_conversion(self):
        """Test constructing a circuit with evolutions yields a DAG with evolution blocks."""
        time = Parameter("t")
        evo = PauliEvolutionGate((Z ^ 2) + (X ^ 2), time=time)

        circuit = QuantumCircuit(2)
        circuit.h(circuit.qubits)
        circuit.append(evo, circuit.qubits)
        circuit.cx(0, 1)

        dag = circuit_to_dag(circuit)

        expected_ops = {"HGate", "CXGate", "PauliEvolutionGate"}
        ops = set(node.op.__class__.__name__ for node in dag.op_nodes())

        self.assertEqual(ops, expected_ops)
    def test_op_evolution_gate_suzuki_trotter(self):
        """Test qpy path with a suzuki trotter synthesis method on an evolution gate."""
        synthesis = SuzukiTrotter()
        evo = PauliEvolutionGate((Z ^ I) + (I ^ Z), time=0.2, synthesis=synthesis)
        qc = QuantumCircuit(2)
        qc.append(evo, range(2))
        qpy_file = io.BytesIO()
        dump(qc, qpy_file)
        qpy_file.seek(0)
        new_circ = load(qpy_file)[0]

        self.assertEqual(qc, new_circ)
        self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])

        new_evo = new_circ.data[0][0]
        self.assertIsInstance(new_evo, PauliEvolutionGate)
    def test_evolutiongate_param_vec_time(self):
        """Test loading a an evolution gate that has a param vector element for time."""
        synthesis = LieTrotter(reps=2)
        time = ParameterVector("TimeVec", 1)
        evo = PauliEvolutionGate((Z ^ I) + (I ^ Z), time=time[0], synthesis=synthesis)
        qc = QuantumCircuit(2)
        qc.append(evo, range(2))
        qpy_file = io.BytesIO()
        dump(qc, qpy_file)
        qpy_file.seek(0)
        new_circ = load(qpy_file)[0]

        self.assertEqual(qc, new_circ)
        self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])

        new_evo = new_circ.data[0][0]
        self.assertIsInstance(new_evo, PauliEvolutionGate)
예제 #11
0
    def test_list_from_grouped_paulis(self):
        """Test getting a string representation from grouped Paulis."""
        grouped_ops = [(X ^ Y) + (Y ^ X), (Z ^ I) + (Z ^ Z) + (I ^ Z), (X ^ X)]
        evo_gate = PauliEvolutionGate(grouped_ops, time=0.12, synthesis=LieTrotter())

        pauli_strings = []
        for op in evo_gate.operator:
            if isinstance(op, SparsePauliOp):
                pauli_strings.append(op.to_list())
            else:
                pauli_strings.append([(str(op), 1 + 0j)])

        expected = [
            [("XY", 1 + 0j), ("YX", 1 + 0j)],
            [("ZI", 1 + 0j), ("ZZ", 1 + 0j), ("IZ", 1 + 0j)],
            [("XX", 1 + 0j)],
        ]
        self.assertListEqual(pauli_strings, expected)
    def test_evolutiongate(self):
        """Test loading a circuit with evolution gate works."""
        synthesis = LieTrotter(reps=2)
        evo = PauliEvolutionGate((Z ^ I) + (I ^ Z),
                                 time=2,
                                 synthesis=synthesis)
        qc = QuantumCircuit(2)
        qc.append(evo, range(2))
        qpy_file = io.BytesIO()
        dump(qc, qpy_file)
        qpy_file.seek(0)
        new_circ = load(qpy_file)[0]

        self.assertEqual(qc, new_circ)
        self.assertEqual([x.operation.label for x in qc.data],
                         [x.operation.label for x in new_circ.data])

        new_evo = new_circ.data[0].operation
        self.assertIsInstance(new_evo, PauliEvolutionGate)
예제 #13
0
    def test_suzuki_trotter(self):
        """Test constructing the circuit with Lie Trotter decomposition."""
        op = (X ^ 3) + (Y ^ 3) + (Z ^ 3)
        time = 0.123
        reps = 4
        for order in [2, 4, 5]:
            if order == 2:
                expected_cx = reps * 5 * 4
            elif order % 2 == 0:
                # recurse (order - 2) / 2 times, base case has 5 blocks with 4 CX each
                expected_cx = reps * 5 ** ((order - 2) / 2) * 5 * 4
            else:
                # recurse (order - 1) / 2 times, base case has 3 blocks with 4 CX each
                expected_cx = reps * 5 ** ((order - 1) / 2) * 3 * 4

            with self.subTest(order=order):
                evo_gate = PauliEvolutionGate(
                    op, time, synthesis=SuzukiTrotter(order=order, reps=reps)
                )
                decomposed = evo_gate.definition.decompose()
                self.assertEqual(decomposed.count_ops()["cx"], expected_cx)
예제 #14
0
    def test_rzx_order(self):
        """Test ZX and XZ is mapped onto the correct qubits."""
        for op, indices in zip([X ^ Z, Z ^ X], [(0, 1), (1, 0)]):
            with self.subTest(op=op, indices=indices):
                evo_gate = PauliEvolutionGate(op)
                decomposed = evo_gate.definition.decompose()

                #           ┌───┐┌───────┐┌───┐
                # q_0: ─────┤ X ├┤ Rz(2) ├┤ X ├─────
                #      ┌───┐└─┬─┘└───────┘└─┬─┘┌───┐
                # q_1: ┤ H ├──■─────────────■──┤ H ├
                #      └───┘                   └───┘
                ref = QuantumCircuit(2)
                ref.h(indices[1])
                ref.cx(indices[1], indices[0])
                ref.rz(2.0, indices[0])
                ref.cx(indices[1], indices[0])
                ref.h(indices[1])

                # don't use circuit equality since RZX here decomposes with RZ on the bottom
                self.assertTrue(Operator(decomposed).equiv(ref))
예제 #15
0
    def test_qaoa(self):
        """Test the QAOA with a custom mixer.

        This test ensures that single-qubit gates end up on the correct qubits. The mixer
        uses Ry gates and the operator is :code:`[("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)]`.

        ..parsed-literal:

                 ┌───┐                                              ┌──────────────────┐»
            q_0: ┤ H ├─────────────────────X────────────────────────┤0                 ├»
                 ├───┤┌──────────────────┐ │ ┌──────────────────┐   │  exp(-i ZZ)(3.0) │»
            q_1: ┤ H ├┤0                 ├─X─┤0                 ├─X─┤1                 ├»
                 ├───┤│  exp(-i ZZ)(1.0) │   │  exp(-i ZZ)(2.0) │ │ └──────────────────┘»
            q_2: ┤ H ├┤1                 ├─X─┤1                 ├─X─────────────────────»
                 ├───┤└──────────────────┘ │ └──────────────────┘                       »
            q_3: ┤ H ├─────────────────────X────────────────────────────────────────────»
                 └───┘                                                                  »
            «     ┌────────┐                    ┌──────────────────┐         »
            «q_0: ┤ Ry(-1) ├────────────────────┤0                 ├────X────»
            «     ├────────┤┌──────────────────┐│  exp(-i ZZ)(6.0) │    │    »
            «q_1: ┤ Ry(-3) ├┤0                 ├┤1                 ├────X────»
            «     ├────────┤│  exp(-i ZZ)(4.0) │└──────────────────┘         »
            «q_2: ┤ Ry(0)  ├┤1                 ├─────────X───────────────────»
            «     ├────────┤└──────────────────┘         │                   »
            «q_3: ┤ Ry(-2) ├─────────────────────────────X───────────────────»
            «     └────────┘                                                 »
            «                         ┌────────┐
            «q_0: ────────────────────┤ Ry(-3) ├
            «     ┌──────────────────┐├────────┤
            «q_1: ┤0                 ├┤ Ry(-1) ├
            «     │  exp(-i ZZ)(2.0) │├────────┤
            «q_2: ┤1                 ├┤ Ry(-2) ├
            «     └──────────────────┘├────────┤
            «q_3: ────────────────────┤ Ry(0)  ├
            «                         └────────┘
        """

        mixer = QuantumCircuit(4)
        for idx in range(4):
            mixer.ry(-idx, idx)

        op = PauliSumOp.from_list([("IZZI", 1), ("ZIIZ", 2), ("ZIZI", 3)])
        circ = QAOAAnsatz(op, reps=2, mixer_operator=mixer)

        swapped = self.pm_.run(circ.decompose())
        param_dict = {p: idx + 1 for idx, p in enumerate(swapped.parameters)}
        swapped.assign_parameters(param_dict, inplace=True)

        # There is some optionality in the order of two instructions. Both are valid.
        valid_expected = []
        for order in [0, 1]:
            expected = QuantumCircuit(4)
            expected.h(range(4))
            expected.append(PauliEvolutionGate(Pauli("ZZ"), 1), (1, 2))
            expected.swap(0, 1)
            expected.swap(2, 3)
            expected.append(PauliEvolutionGate(Pauli("ZZ"), 2), (1, 2))
            expected.swap(1, 2)
            expected.append(PauliEvolutionGate(Pauli("ZZ"), 3), (0, 1))
            expected.ry(-1, 0)
            expected.ry(-3, 1)
            expected.ry(0, 2)
            expected.ry(-2, 3)
            if order == 0:
                expected.append(PauliEvolutionGate(Pauli("ZZ"), 6), (0, 1))
                expected.append(PauliEvolutionGate(Pauli("ZZ"), 4), (2, 1))
            else:
                expected.append(PauliEvolutionGate(Pauli("ZZ"), 4), (2, 1))
                expected.append(PauliEvolutionGate(Pauli("ZZ"), 6), (0, 1))

            expected.swap(0, 1)
            expected.swap(2, 3)
            expected.append(PauliEvolutionGate(Pauli("ZZ"), 2), (1, 2))
            expected.ry(-3, 0)
            expected.ry(-1, 1)
            expected.ry(-2, 2)
            expected.ry(0, 3)

            valid_expected.append(expected == swapped)

        self.assertEqual(set(valid_expected), {True, False})
예제 #16
0
    def get_loss(
        self,
        hamiltonian: OperatorBase,
        ansatz: QuantumCircuit,
        dt: float,
        current_parameters: np.ndarray,
    ) -> Tuple[Callable[[np.ndarray], float], Optional[Callable[[np.ndarray], np.ndarray]]]:

        """Get a function to evaluate the infidelity between Trotter step and ansatz.

        Args:
            hamiltonian: The Hamiltonian under which to evolve.
            ansatz: The parameterized quantum circuit which attempts to approximate the
                time-evolved state.
            dt: The time step.
            current_parameters: The current parameters.

        Returns:
            A callable to evaluate the infidelity and, if gradients are supported and required,
                a second callable to evaluate the gradient of the infidelity.
        """
        self._validate_setup(skip={"optimizer"})

        # use Trotterization to evolve the current state
        trotterized = ansatz.bind_parameters(current_parameters)

        if isinstance(hamiltonian, MatrixOp):
            evolution_gate = HamiltonianGate(hamiltonian.primitive, time=dt)
        else:
            evolution_gate = PauliEvolutionGate(hamiltonian, time=dt, synthesis=self.evolution)

        trotterized.append(evolution_gate, ansatz.qubits)

        # define the overlap of the Trotterized state and the ansatz
        x = ParameterVector("w", ansatz.num_parameters)
        shifted = ansatz.assign_parameters(current_parameters + x)
        overlap = StateFn(trotterized).adjoint() @ StateFn(shifted)

        converted = self.expectation.convert(overlap)

        def evaluate_loss(
            displacement: Union[np.ndarray, List[np.ndarray]]
        ) -> Union[float, List[float]]:
            """Evaluate the overlap of the ansatz with the Trotterized evolution.

            Args:
                displacement: The parameters for the ansatz.

            Returns:
                The fidelity of the ansatz with parameters ``theta`` and the Trotterized evolution.
            """
            if isinstance(displacement, list):
                displacement = np.asarray(displacement)
                value_dict = {x_i: displacement[:, i].tolist() for i, x_i in enumerate(x)}
            else:
                value_dict = dict(zip(x, displacement))

            sampled = self._sampler.convert(converted, params=value_dict)

            # in principle we could add different loss functions here, but we're currently
            # not aware of a use-case for a different one than in the paper
            return 1 - np.abs(sampled.eval()) ** 2

        if _is_gradient_supported(ansatz) and self.use_parameter_shift:

            def evaluate_gradient(displacement: np.ndarray) -> np.ndarray:
                """Evaluate the gradient with the parameter-shift rule.

                This is hardcoded here since the gradient framework does not support computing
                gradients for overlaps.

                Args:
                    displacement: The parameters for the ansatz.

                Returns:
                    The gradient.
                """
                # construct lists where each element is shifted by plus (or minus) pi/2
                dim = displacement.size
                plus_shifts = (displacement + np.pi / 2 * np.identity(dim)).tolist()
                minus_shifts = (displacement - np.pi / 2 * np.identity(dim)).tolist()

                evaluated = evaluate_loss(plus_shifts + minus_shifts)

                gradient = (evaluated[:dim] - evaluated[dim:]) / 2

                return gradient

        else:
            evaluate_gradient = None

        return evaluate_loss, evaluate_gradient
예제 #17
0
    def test_t_device(self):
        """Test the swap strategy to route a complete problem on a T device.

        The coupling map in this test corresponds to

        .. parsed-literal::

            0 -- 1 -- 2
                 |
                 3
                 |
                 4

        The problem being routed is a fully connect ZZ graph. It has 10 terms since there are
        five qubits in the coupling map. This test checks that the circuit produced by the
        commuting gate router has the instructions we expect. There are several circuits that are
        valid since some of the Rzz gates commute.
        """
        swaps = (
            ((1, 3), ),
            ((0, 1), (3, 4)),
            ((1, 3), ),
        )

        cmap = CouplingMap([[0, 1], [1, 2], [1, 3], [3, 4]])
        cmap.make_symmetric()

        swap_strat = SwapStrategy(cmap, swaps)

        # A dense Pauli op.
        op = PauliSumOp.from_list([
            ("IIIZZ", 1),
            ("IIZIZ", 2),
            ("IZIIZ", 3),
            ("ZIIIZ", 4),
            ("IIZZI", 5),
            ("IZIZI", 6),
            ("ZIIZI", 7),
            ("IZZII", 8),
            ("ZIZII", 9),
            ("ZZIII", 10),
        ])

        circ = QuantumCircuit(5)
        circ.append(PauliEvolutionGate(op, 1), range(5))

        pm_ = PassManager([
            FindCommutingPauliEvolutions(),
            Commuting2qGateRouter(swap_strat),
        ])

        swapped = circuit_to_dag(pm_.run(circ))

        # The swapped circuit can take on several forms as some of the gates commute.
        # We test that sets of gates are where we expected them in the circuit data
        def inst_info(op, qargs, qreg):
            """Get a tuple we can easily test."""
            param = None
            if len(op.params) > 0:
                param = op.params[0]

            return op.name, param, qreg.index(qargs[0]), qreg.index(qargs[1])

        qreg = swapped.qregs["q"]
        inst_list = list(
            inst_info(node.op, node.qargs, qreg)
            for node in swapped.op_nodes())

        # First block has the Rzz gates ("IIIZZ", 1), ("IIZZI", 5), ("IZIZI", 6), ("ZZIII", 10)
        expected = {
            ("PauliEvolution", 1.0, 0, 1),
            ("PauliEvolution", 5.0, 1, 2),
            ("PauliEvolution", 6.0, 1, 3),
            ("PauliEvolution", 10.0, 3, 4),
        }
        self.assertSetEqual(set(inst_list[0:4]), expected)

        # Block 2 is a swap
        self.assertSetEqual({inst_list[4]}, {("swap", None, 1, 3)})

        # Block 3 This combines a swap layer and two layers of Rzz gates.
        expected = {
            ("PauliEvolution", 8.0, 2, 1),
            ("PauliEvolution", 7.0, 3, 4),
            ("PauliEvolution", 3.0, 0, 1),
            ("swap", None, 0, 1),
            ("PauliEvolution", 2.0, 1, 2),
            ("PauliEvolution", 4.0, 1, 3),
            ("swap", None, 3, 4),
        }
        self.assertSetEqual(set(inst_list[5:12]), expected)

        # Test the remaining instructions.
        self.assertSetEqual({inst_list[12]}, {("swap", None, 1, 3)})
        self.assertSetEqual({inst_list[13]}, {("PauliEvolution", 9.0, 2, 1)})
예제 #18
0
 def test_complex_op_raises(self):
     """Test an operator with complex coefficient raises an error."""
     with self.assertRaises(ValueError):
         _ = PauliEvolutionGate(Pauli("iZ"))
예제 #19
0
 def test_pauliop_coefficients_respected(self):
     """Test that global ``PauliOp`` coefficients are being taken care of."""
     evo = PauliEvolutionGate(5 * (Z ^ I), time=1, synthesis=LieTrotter())
     circuit = evo.definition.decompose()
     rz_angle = circuit.data[0][0].params[0]
     self.assertEqual(rz_angle, 10)
예제 #20
0
    def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult:
        """
        Evolves a quantum state for a given time using the Trotterization method
        based on a product formula provided. The result is provided in the form of a quantum
        circuit. If auxiliary operators are included in the ``evolution_problem``, they are
        evaluated on an evolved state using a backend provided.

        .. note::
            Time-dependent Hamiltonians are not yet supported.

        Args:
            evolution_problem: Instance defining evolution problem. For the included Hamiltonian,
                ``PauliOp``, ``SummedOp`` or ``PauliSumOp`` are supported by TrotterQRTE.

        Returns:
            Evolution result that includes an evolved state as a quantum circuit and, optionally,
            auxiliary operators evaluated for a resulting state on a backend.

        Raises:
            ValueError: If ``t_param`` is not set to None in the EvolutionProblem (feature not
                currently supported).
            ValueError: If the ``initial_state`` is not provided in the EvolutionProblem.
        """
        evolution_problem.validate_params()
        if evolution_problem.t_param is not None:
            raise ValueError(
                "TrotterQRTE does not accept a time dependent hamiltonian,"
                "``t_param`` from the EvolutionProblem should be set to None.")

        if evolution_problem.aux_operators is not None and (
                self._quantum_instance is None or self._expectation is None):
            raise ValueError(
                "aux_operators were provided for evaluations but no ``expectation`` or "
                "``quantum_instance`` was provided.")
        hamiltonian = evolution_problem.hamiltonian
        if not isinstance(hamiltonian, (PauliOp, PauliSumOp, SummedOp)):
            raise ValueError(
                "TrotterQRTE only accepts PauliOp | "
                f"PauliSumOp | SummedOp, {type(hamiltonian)} provided.")
        if isinstance(hamiltonian, OperatorBase):
            hamiltonian = hamiltonian.bind_parameters(
                evolution_problem.hamiltonian_value_dict)
        if isinstance(hamiltonian, SummedOp):
            hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian)
        # the evolution gate
        evolution_gate = CircuitOp(
            PauliEvolutionGate(hamiltonian,
                               evolution_problem.time,
                               synthesis=self._product_formula))

        if evolution_problem.initial_state is not None:
            initial_state = evolution_problem.initial_state
            if isinstance(initial_state, QuantumCircuit):
                initial_state = StateFn(initial_state)
            evolved_state = evolution_gate @ initial_state

        else:
            raise ValueError(
                "``initial_state`` must be provided in the EvolutionProblem.")

        evaluated_aux_ops = None
        if evolution_problem.aux_operators is not None:
            evaluated_aux_ops = eval_observables(
                self._quantum_instance,
                evolved_state.primitive,
                evolution_problem.aux_operators,
                self._expectation,
                evolution_problem.truncation_threshold,
            )

        return EvolutionResult(evolved_state, evaluated_aux_ops)