def test_optimize_grad(self): """Test that the gradient of VQECost is accessible and correct when using observable optimization and the autograd interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.VQECost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True) cost2 = qml.VQECost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=False) w = qml.init.strong_ent_layers_uniform(2, 4, seed=1967) dc = qml.grad(cost)(w) exec_opt = dev.num_executions dev._num_executions = 0 dc2 = qml.grad(cost2)(w) exec_no_opt = dev.num_executions assert exec_no_opt > exec_opt assert np.allclose(dc, big_hamiltonian_grad) assert np.allclose(dc2, big_hamiltonian_grad)
def test_integration_hamiltonian_to_vqe_cost(monkeypatch, mol_name, terms_ref, expected_cost, tol): r"""Test if `convert_hamiltonian()` in qchem integrates with `VQECost()` in pennylane""" qOp = QubitOperator() if terms_ref is not None: monkeypatch.setattr(qOp, "terms", terms_ref) vqe_hamiltonian = qchem.convert_hamiltonian(qOp) # maybe make num_qubits a @property of the Hamiltonian class? num_qubits = max( 1, len(set([w for op in vqe_hamiltonian.ops for w in op.wires]))) dev = qml.device("default.qubit", wires=num_qubits) print(vqe_hamiltonian.terms) # can replace the ansatz with more suitable ones later. def dummy_ansatz(phis, wires): for phi, w in zip(phis, wires): qml.RX(phi, wires=w) dummy_cost = qml.VQECost(dummy_ansatz, vqe_hamiltonian, dev) params = [0.1 * i for i in range(num_qubits)] res = dummy_cost(params) assert np.allclose(res, expected_cost, **tol)
def test_module_example(self, tol): """Test the example in the QAOA module docstring""" # Defines the wires and the graph on which MaxCut is being performed wires = range(3) graph = Graph([(0, 1), (1, 2), (2, 0)]) # Defines the QAOA cost and mixer Hamiltonians cost_h, mixer_h = qaoa.maxcut(graph) # Defines a layer of the QAOA ansatz from the cost and mixer Hamiltonians def qaoa_layer(gamma, alpha): qaoa.cost_layer(gamma, cost_h) qaoa.mixer_layer(alpha, mixer_h) # Repeatedly applies layers of the QAOA ansatz def circuit(params, **kwargs): for w in wires: qml.Hadamard(wires=w) qml.layer(qaoa_layer, 2, params[0], params[1]) # Defines the device and the QAOA cost function dev = qml.device('default.qubit', wires=len(wires)) cost_function = qml.VQECost(circuit, cost_h, dev) res = cost_function([[1, 1], [1, 1]]) expected = -1.8260274380964299 assert np.allclose(res, expected, atol=tol, rtol=0)
def test_integration_observable_to_vqe_cost(monkeypatch, mol_name, terms_ref, expected_cost, custom_wires, tol): r"""Test if `convert_observable()` in qchem integrates with `VQECost()` in pennylane""" qOp = QubitOperator() if terms_ref is not None: monkeypatch.setattr(qOp, "terms", terms_ref) vqe_observable = qchem.convert_observable(qOp, custom_wires) num_qubits = len(vqe_observable.wires) assert vqe_observable.terms.__repr__() # just to satisfy codecov if custom_wires is None: wires = num_qubits elif isinstance(custom_wires, dict): wires = qchem.structure._process_wires(custom_wires) else: wires = custom_wires[:num_qubits] dev = qml.device("default.qubit", wires=wires) # can replace the ansatz with more suitable ones later. def dummy_ansatz(phis, wires): for phi, w in zip(phis, wires): qml.RX(phi, wires=w) dummy_cost = qml.VQECost(dummy_ansatz, vqe_observable, dev) params = [0.1 * i for i in range(num_qubits)] res = dummy_cost(params) assert np.allclose(res, expected_cost, **tol)
def test_optimize_grad_tf(self, tf_support): """Test that the gradient of VQECost is accessible and correct when using observable optimization and the TensorFlow interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") if not tf_support: pytest.skip("This test requires TensorFlow") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.VQECost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True, interface="tf") w = tf.Variable(qml.init.strong_ent_layers_uniform(2, 4, seed=1967)) with tf.GradientTape() as tape: res = cost(w) dc = tape.gradient(res, w).numpy() assert np.allclose(dc, big_hamiltonian_grad)
def test_optimize_grad_torch(self, torch_support): """Test that the gradient of VQECost is accessible and correct when using observable optimization and the Torch interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") if not torch_support: pytest.skip("This test requires Torch") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.VQECost( qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True, interface="torch", ) w = torch.tensor(qml.init.strong_ent_layers_uniform(2, 4, seed=1967), requires_grad=True) res = cost(w) res.backward() dc = w.grad.detach().numpy() assert np.allclose(dc, big_hamiltonian_grad)
def test_gradient(self, tol): """Test differentiation works""" dev = qml.device("default.qubit", wires=1) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) coeffs = [0.2, 0.5] observables = [qml.PauliX(0), qml.PauliY(0)] H = qml.vqe.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = Variable([a, b], dtype=tf.float64) cost = qml.VQECost(ansatz, H, dev, interface="tf") with tf.GradientTape() as tape: loss = cost(params) res = np.array(tape.gradient(loss, params)) expected = [ -coeffs[0] * np.sin(a) * np.sin(b) - coeffs[1] * np.cos(a), coeffs[0] * np.cos(a) * np.cos(b) ] assert np.allclose(res, expected, atol=tol, rtol=0)
def test_cost_evaluate(self, params, ansatz, coeffs, observables): """Tests that the cost function evaluates properly""" hamiltonian = qml.vqe.Hamiltonian(coeffs, observables) dev = qml.device("default.qubit", wires=3) expval = qml.VQECost(ansatz, hamiltonian, dev) assert type(expval(params)) == np.float64 assert np.shape(expval(params)) == () # expval should be scalar
def test_gradient(self, tol): """Test differentiation works""" dev = qml.device("default.qubit", wires=1) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) coeffs = [0.2, 0.5] observables = [qml.PauliX(0), qml.PauliY(0)] H = qml.vqe.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = torch.autograd.Variable(torch.tensor([a, b]), requires_grad=True) cost = qml.VQECost(ansatz, H, dev, interface="torch") loss = cost(params) loss.backward() res = params.grad.numpy() expected = [ -coeffs[0] * np.sin(a) * np.sin(b) - coeffs[1] * np.cos(a), coeffs[0] * np.cos(a) * np.cos(b) ] assert np.allclose(res, expected, atol=tol, rtol=0)
def test_gradient(self, tol, interface): """Test differentiation works""" dev = qml.device("default.qubit", wires=1) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) coeffs = [0.2, 0.5] observables = [qml.PauliX(0), qml.PauliY(0)] H = qml.vqe.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = np.array([a, b]) cost = qml.VQECost(ansatz, H, dev, interface=interface) dcost = qml.grad(cost, argnum=[0]) res = dcost(params) expected = [ -coeffs[0] * np.sin(a) * np.sin(b) - coeffs[1] * np.cos(a), coeffs[0] * np.cos(a) * np.cos(b) ] assert np.allclose(res, expected, atol=tol, rtol=0)
def test_all_interfaces_gradient_agree(self, tol): """Test the gradient agrees across all interfaces""" dev = qml.device("default.qubit", wires=2) coeffs = [0.2, 0.5] observables = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0)] H = qml.vqe.Hamiltonian(coeffs, observables) # TensorFlow interface params = Variable( qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2, seed=1)) ansatz = qml.templates.layers.StronglyEntanglingLayers cost = qml.VQECost(ansatz, H, dev, interface="tf") with tf.GradientTape() as tape: loss = cost(params) res_tf = np.array(tape.gradient(loss, params)) # Torch interface params = torch.tensor( qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2, seed=1)) params = torch.autograd.Variable(params, requires_grad=True) ansatz = qml.templates.layers.StronglyEntanglingLayers cost = qml.VQECost(ansatz, H, dev, interface="torch") loss = cost(params) loss.backward() res_torch = params.grad.numpy() # NumPy interface params = qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2, seed=1) ansatz = qml.templates.layers.StronglyEntanglingLayers cost = qml.VQECost(ansatz, H, dev, interface="numpy") dcost = qml.grad(cost, argnum=[0]) res = dcost(params) assert np.allclose(res, res_tf, atol=tol, rtol=0) assert np.allclose(res, res_torch, atol=tol, rtol=0)
def test_vqe_cost(): """Tests that VQECost raises a UserWarning but otherwise behaves as ExpvalCost""" h = qml.Hamiltonian([1], [qml.PauliZ(0)]) dev = qml.device("default.qubit", wires=1) ansatz = qml.templates.StronglyEntanglingLayers with pytest.warns(UserWarning, match="Use of VQECost is deprecated"): cost = qml.VQECost(ansatz, h, dev) assert isinstance(cost, qml.ExpvalCost)
def test_optimize(self, interface, tf_support, torch_support): """Test that a VQECost with observable optimization gives the same result as another VQECost without observable optimization.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") if interface == "tf" and not tf_support: pytest.skip("This test requires TensorFlow") if interface == "torch" and not torch_support: pytest.skip("This test requires Torch") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.VQECost( qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True, interface=interface, ) cost2 = qml.VQECost( qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=False, interface=interface, ) w = qml.init.strong_ent_layers_uniform(2, 4, seed=1967) c1 = cost(w) exec_opt = dev.num_executions dev._num_executions = 0 c2 = cost2(w) exec_no_opt = dev.num_executions assert exec_opt == 5 # Number of groups in the Hamiltonian assert exec_no_opt == 15 assert np.allclose(c1, c2)
def test_single_qubit_vqe_using_vqecost(self, tol): """Test single-qubit VQE using VQECost has the correct QNG value every step, the correct parameter updates, and correct cost after 200 steps""" dev = qml.device("default.qubit", wires=1) def circuit(params, wires=0): qml.RX(params[0], wires=wires) qml.RY(params[1], wires=wires) coeffs = [1, 1] obs_list = [ qml.PauliX(0), qml.PauliZ(0) ] h = qml.Hamiltonian(coeffs=coeffs, observables=obs_list) cost_fn = qml.VQECost(ansatz=circuit, hamiltonian=h, device=dev) def gradient(params): """Returns the gradient""" da = -np.sin(params[0]) * (np.cos(params[1]) + np.sin(params[1])) db = np.cos(params[0]) * (np.cos(params[1]) - np.sin(params[1])) return np.array([da, db]) eta = 0.01 init_params = np.array([0.011, 0.012]) num_steps = 200 opt = qml.QNGOptimizer(eta) theta = init_params # optimization for 200 steps total for t in range(num_steps): theta_new = opt.step(cost_fn, theta) # check metric tensor res = opt.metric_tensor exp = np.diag([0.25, (np.cos(theta[0]) ** 2)/4]) assert np.allclose(res, exp, atol=tol, rtol=0) # check parameter update dtheta = eta * sp.linalg.pinvh(exp) @ gradient(theta) assert np.allclose(dtheta, theta - theta_new, atol=tol, rtol=0) theta = theta_new # check final cost assert np.allclose(cost_fn(theta), -1.41421356, atol=tol, rtol=0)
def test_optimize_outside_tape_mode(self): """Test that an error is raised if observable optimization is requested outside of tape mode.""" if qml.tape_mode_active(): pytest.skip("This test is only intended for non-tape mode") dev = qml.device("default.qubit", wires=2) hamiltonian = qml.vqe.Hamiltonian([1], [qml.PauliZ(0)]) with pytest.raises( ValueError, match="Observable optimization is only supported in tape"): qml.VQECost(lambda params, **kwargs: None, hamiltonian, dev, optimize=True)
def test_passing_kwargs(self, coeffs, observables, expected): """Test that the step size and order used for the finite differences differentiation method were passed to the QNode instances using the keyword arguments.""" dev = qml.device("default.qubit", wires=2) hamiltonian = qml.vqe.Hamiltonian(coeffs, observables) cost = qml.VQECost(lambda params, **kwargs: None, hamiltonian, dev, h=123, order=2) # Checking that the qnodes contain the step size and order for qnode in cost.qnodes: assert qnode.h == 123 assert qnode.order == 2
def test_metric_tensor(self): """Test that an error is raised if the metric tensor is requested in optimize=True mode.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.VQECost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True) with pytest.raises( ValueError, match="Evaluation of the metric tensor is not supported"): cost.metric_tensor(None)
def test_integration_mol_file_to_vqe_cost(name, core, active, mapping, expected_cost, custom_wires, tol): r"""Test if the output of `decompose()` works with `convert_observable()` to generate `VQECost()`""" ref_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_ref_files") hf_file = os.path.join(ref_dir, name) qubit_hamiltonian = qchem.decompose( hf_file, mapping=mapping, core=core, active=active, ) vqe_hamiltonian = qchem.convert_observable(qubit_hamiltonian, custom_wires) assert len(vqe_hamiltonian.ops) > 1 # just to check if this runs num_qubits = len(vqe_hamiltonian.wires) assert num_qubits == 2 * len(active) if custom_wires is None: wires = num_qubits elif isinstance(custom_wires, dict): wires = qchem.structure._process_wires(custom_wires) else: wires = custom_wires[:num_qubits] dev = qml.device("default.qubit", wires=wires) # can replace the ansatz with more suitable ones later. def dummy_ansatz(phis, wires): for phi, w in zip(phis, wires): qml.RX(phi, wires=w) phis = np.load(os.path.join(ref_dir, "dummy_ansatz_parameters.npy")) dummy_cost = qml.VQECost(dummy_ansatz, vqe_hamiltonian, dev) res = dummy_cost(phis) assert np.abs(res - expected_cost) < tol["atol"]
def test_integration_mol_file_to_vqe_cost(hf_filename, docc_mo, act_mo, type_of_transformation, expected_cost, tol): r"""Test if the output of `decompose_hamiltonian()` works with `convert_hamiltonian()` to generate `VQECost()`""" ref_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_ref_files") transformed_hamiltonian = qchem.decompose_hamiltonian( hf_filename, ref_dir, mapping=type_of_transformation, docc_mo_indices=docc_mo, active_mo_indices=act_mo, ) vqe_hamiltonian = qchem.convert_hamiltonian(transformed_hamiltonian) assert len(vqe_hamiltonian.ops) > 1 # just to check if this runs num_qubits = max( 1, len( set([ w for op in vqe_hamiltonian.ops for ws in op.wires for w in ws ]))) assert num_qubits == 2 * len(act_mo) dev = qml.device("default.qubit", wires=num_qubits) # can replace the ansatz with more suitable ones later. def dummy_ansatz(phis, wires): for phi, w in zip(phis, wires): qml.RX(phi, wires=w) phis = np.load(os.path.join(ref_dir, "dummy_ansatz_parameters.npy")) dummy_cost = qml.VQECost(dummy_ansatz, vqe_hamiltonian, dev) res = dummy_cost(phis) assert np.abs(res - expected_cost) < tol["atol"]
############################################################################## # The ground state for each inter-atomic distance is characterized by a different Y-rotation angle. # The values of these Y-rotations can be found by minimizing the ground state energy as outlined in # :doc:`tutorial_vqe`. In this tutorial, we load pre-optimized rotations and focus on # comparing the speed of evaluating the potential energy surface with sequential and parallel # evaluation. These parameters can be downloaded by clicking :download:`here # <../demonstrations/vqe_parallel/RY_params.npy>`. params = np.load("vqe_parallel/RY_params.npy") ############################################################################## # Finally, the energies as functions of rotation angle can be given using # :class:`~.pennylane.VQECost`. energies = [qml.VQECost(circuit, h, devs) for h in hamiltonians] ############################################################################## # Calculating the potential energy surface # ---------------------------------------- # # :class:`~.pennylane.VQECost` returns a :class:`~.pennylane.QNodeCollection` which can be # evaluated using the input parameters to the ansatz circuit. The # :class:`~.pennylane.QNodeCollection` can be evaluated asynchronously by passing the keyword # argument ``parallel=True``. When ``parallel=False`` (the default behaviour), the QNodes are # instead evaluated sequentially. # # We can use this feature to compare the sequential and parallel times required to calculate the # potential energy surface. The following function calculates the surface:
def circuit(params, wires=0): qml.RX(params[0], wires=wires) qml.RY(params[1], wires=wires) ############################################################################## # We then define our cost function using the ``VQECost`` class, which supports the computation of # block-diagonal or diagonal approximations to the Fubini-Study metric tensor. This tensor is a # crucial component for optimizing with quantum natural gradients. coeffs = [1, 1] obs = [qml.PauliX(0), qml.PauliZ(0)] H = qml.Hamiltonian(coeffs, obs) cost_fn = qml.VQECost(circuit, H, dev) ############################################################################## # To analyze performance of the quantum natural gradient on VQE calculations, # we will set up and execute optimizations using the ``GradientDescentOptimizer`` # and the ``QNGOptimizer`` using the block-diagonal approximation to the metric tensor. # # To perform a fair comparison, we fix the initial parameters for the two optimizers. init_params = np.array([3.97507603, 3.00854038]) ############################################################################## # We will carry out each optimization over a maximum of 500 steps. As was done in the VQE # tutorial, we aim to reach a convergence tolerance of around :math:`10^{-6}`. We use a step size of 0.01. max_iterations = 500
###################################################################### # # Note that :func:`~.pennylane.layer` allows us to pass variational parameters # ``params[0]`` and ``params[1]`` into each layer of the circuit. That's it! The last # step is PennyLane's specialty: optimizing the circuit parameters. # # The cost function is the expectation value of :math:`H_C`, which we want to minimize. The # function :func:`~.pennylane.VQECost` is designed for this purpose: it returns the # expectation value of an input Hamiltonian with respect to the circuit's output state. # We also define the device on which the simulation is # performed. We use the PennyLane-Qulacs plugin to # run the circuit on the Qulacs simulator: # dev = qml.device("qulacs.simulator", wires=wires) cost_function = qml.VQECost(circuit, cost_h, dev) ###################################################################### # # Finally, we optimize the cost function using the built-in # :func:`~.pennylane.GradientDescentOptimizer`. We perform optimization for seventy steps and initialize the # parameters: optimizer = qml.GradientDescentOptimizer() steps = 70 params = [[0.5, 0.5], [0.5, 0.5]] ###################################################################### # # Notice that we set each of the initial parameters to :math:`0.5`. For demonstration purposes, # we chose initial parameters that we know work fairly well, and don't get stuck in any local minima.
hf_state = qchem.hf_state(electrons, qubits) print(hf_state) ############################################################################## # Finally, we can use the :func:`~.pennylane.templates.subroutines.UCCSD` function to define # our VQE ansatz, ansatz = partial(UCCSD, init_state=hf_state, s_wires=s_wires, d_wires=d_wires) ############################################################################## # Next, we use the PennyLane class :class:`~.pennylane.VQECost` to define the cost function. # This requires specifying the circuit, target Hamiltonian, and the device. It returns # a cost function that can be evaluated with the circuit parameters: cost_fn = qml.VQECost(ansatz, H, dev) ############################################################################## # As a reminder, we also built the total spin operator :math:`\hat{S}^2` for which # we can now define a function to compute its expectation value: S2_exp_value = qml.VQECost(ansatz, S2, dev) ############################################################################## # The total spin :math:`S` of the trial state can be obtained from the # expectation value :math:`\langle \hat{S}^2 \rangle` as, # # .. math:: # # S = -\frac{1}{2} + \sqrt{\frac{1}{4} + \langle \hat{S}^2 \rangle}. #
qml.CNOT(wires=[3, 1]) ############################################################################## # .. note:: # # The qubit register has been initialized to :math:`|1100\rangle` which encodes the # Hartree-Fock state of the hydrogen molecule described with a `minimal basis # <https://en.wikipedia.org/wiki/Basis_set_(chemistry)#Minimal_basis_sets>`__. # # The cost function for optimizing the circuit can be created using the :class:`~.VQECost` # class, which is tailored for VQE optimization. It requires specifying the # circuit, target Hamiltonian, and the device, and returns a cost function that can # be evaluated with the circuit parameters: cost_fn = qml.VQECost(circuit, h, dev) ############################################################################## # Wrapping up, we fix an optimizer and randomly initialize circuit parameters. For reliable # results, we fix the seed of the random number generator, since in practice it may be necessary # to re-initialize the circuit several times before convergence occurs. opt = qml.GradientDescentOptimizer(stepsize=0.4) np.random.seed(0) params = np.random.normal(0, np.pi, (qubits, 3)) print(params) ############################################################################## # We carry out the optimization over a maximum of 200 steps, aiming to reach a convergence # tolerance (difference in cost function for subsequent optimization steps) of :math:`\sim 10^{
def test_cost_expvals(self, coeffs, observables, expected): """Tests that the cost function returns correct expectation values""" dev = qml.device("default.qubit", wires=2) hamiltonian = qml.vqe.Hamiltonian(coeffs, observables) cost = qml.VQECost(lambda params, **kwargs: None, hamiltonian, dev) assert cost([]) == sum(expected)
def test_cost_invalid_ansatz(self, ansatz, mock_device): """Tests that the cost function raises an exception if the ansatz is not valid""" hamiltonian = qml.vqe.Hamiltonian((1.0, ), [qml.PauliZ(0)]) with pytest.raises(ValueError, match="not a callable function."): cost = qml.VQECost(4, hamiltonian, mock_device)