def test_missing_attributesquantum_instance(self): """Test appropriate error is raised if the quantum instance is missing.""" pvqd = PVQD( self.ansatz, self.initial_parameters, optimizer=L_BFGS_B(maxiter=1), expectation=self.expectation, ) problem = EvolutionProblem(self.hamiltonian, time=0.01) attrs_to_test = [ ("optimizer", L_BFGS_B(maxiter=1)), ("quantum_instance", self.qasm_backend), ] for attr, value in attrs_to_test: with self.subTest(msg=f"missing: {attr}"): # set attribute to None to invalidate the setup setattr(pvqd, attr, None) with self.assertRaises(ValueError): _ = pvqd.evolve(problem) # set the correct value again setattr(pvqd, attr, value) with self.subTest(msg="all set again"): result = pvqd.evolve(problem) self.assertIsNotNone(result.evolved_state)
def test_zero_parameters(self): """Test passing an ansatz with zero parameters raises an error.""" problem = EvolutionProblem(self.hamiltonian, time=0.02) pvqd = PVQD( QuantumCircuit(2), np.array([]), optimizer=SPSA(maxiter=10, learning_rate=0.1, perturbation=0.01), quantum_instance=self.sv_backend, expectation=self.expectation, ) with self.assertRaises(QiskitError): _ = pvqd.evolve(problem)
def test_gradient_supported(self): """Test the gradient support is correctly determined.""" # gradient supported here wrapped = EfficientSU2(2) # a circuit wrapped into a big instruction plain = wrapped.decompose( ) # a plain circuit with already supported instructions # gradients not supported on the following circuits x = Parameter("x") duplicated = QuantumCircuit(2) duplicated.rx(x, 0) duplicated.rx(x, 1) needs_chainrule = QuantumCircuit(2) needs_chainrule.rx(2 * x, 0) custom_gate = WhatAmI(x) unsupported = QuantumCircuit(2) unsupported.append(custom_gate, [0, 1]) tests = [ (wrapped, True), # tuple: (circuit, gradient support) (plain, True), (duplicated, False), (needs_chainrule, False), (unsupported, False), ] # used to store the info if a gradient callable is passed into the # optimizer of not info = {"has_gradient": None} optimizer = partial(gradient_supplied, info=info) pvqd = PVQD( ansatz=None, initial_parameters=np.array([]), optimizer=optimizer, quantum_instance=self.sv_backend, expectation=self.expectation, ) problem = EvolutionProblem(self.hamiltonian, time=0.01) for circuit, expected_support in tests: with self.subTest(circuit=circuit, expected_support=expected_support): pvqd.ansatz = circuit pvqd.initial_parameters = np.zeros(circuit.num_parameters) _ = pvqd.evolve(problem) self.assertEqual(info["has_gradient"], expected_support)
def test_invalid_num_timestep(self): """Test raises if the num_timestep is not positive.""" pvqd = PVQD( self.ansatz, self.initial_parameters, num_timesteps=0, optimizer=L_BFGS_B(), quantum_instance=self.sv_backend, expectation=self.expectation, ) problem = EvolutionProblem( self.hamiltonian, time=0.01, aux_operators=[self.hamiltonian, self.observable]) with self.assertRaises(ValueError): _ = pvqd.evolve(problem)
def test_pvqd(self, hamiltonian_type, expectation_cls, gradient, backend_type, num_timesteps): """Test a simple evolution.""" time = 0.02 if hamiltonian_type == "ising": hamiltonian = self.hamiltonian elif hamiltonian_type == "ising_matrix": hamiltonian = self.hamiltonian.to_matrix_op() else: # hamiltonian_type == "pauli": hamiltonian = X ^ X # parse input arguments if gradient: optimizer = GradientDescent(maxiter=1) else: optimizer = L_BFGS_B(maxiter=1) backend = self.sv_backend if backend_type == "sv" else self.qasm_backend expectation = expectation_cls() # run pVQD keeping track of the energy and the magnetization pvqd = PVQD( self.ansatz, self.initial_parameters, num_timesteps=num_timesteps, optimizer=optimizer, quantum_instance=backend, expectation=expectation, ) problem = EvolutionProblem( hamiltonian, time, aux_operators=[hamiltonian, self.observable]) result = pvqd.evolve(problem) self.assertTrue(len(result.fidelities) == 3) self.assertTrue(np.all(result.times == [0.0, 0.01, 0.02])) self.assertTrue(np.asarray(result.observables).shape == (3, 2)) num_parameters = self.ansatz.num_parameters self.assertTrue( len(result.parameters) == 3 and np.all([ len(params) == num_parameters for params in result.parameters ]))
def test_initial_state_raises(self): """Test passing an initial state raises an error for now.""" initial_state = QuantumCircuit(2) initial_state.x(0) problem = EvolutionProblem( self.hamiltonian, time=0.02, initial_state=initial_state, ) pvqd = PVQD( self.ansatz, self.initial_parameters, optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), quantum_instance=self.sv_backend, expectation=self.expectation, ) with self.assertRaises(NotImplementedError): _ = pvqd.evolve(problem)
def test_initial_guess_and_observables(self): """Test doing no optimizations stays at initial guess.""" initial_guess = np.zeros(self.ansatz.num_parameters) pvqd = PVQD( self.ansatz, self.initial_parameters, num_timesteps=10, optimizer=SPSA(maxiter=0, learning_rate=0.1, perturbation=0.01), initial_guess=initial_guess, quantum_instance=self.sv_backend, expectation=self.expectation, ) problem = EvolutionProblem( self.hamiltonian, time=0.1, aux_operators=[self.hamiltonian, self.observable]) result = pvqd.evolve(problem) observables = result.aux_ops_evaluated self.assertEqual(observables[0], 0.1) # expected energy self.assertEqual(observables[1], 1) # expected magnetization
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)