def step_and_cost(self): r"""Update the circuit with one step of the optimizer and return the corresponding objective function value prior to the step. Returns: tuple[.QNode, float]: the optimized circuit and the objective function output prior to the step. """ # pylint: disable=not-callable cost = self.circuit() omegas = self.get_omegas() non_zero_omegas = -omegas[omegas != 0] nonzero_idx = np.nonzero(omegas)[0] non_zero_lie_algebra_elements = [self.lie_algebra_basis_names[i] for i in nonzero_idx] lie_gradient = qml.Hamiltonian( non_zero_omegas, [qml.grouping.string_to_pauli_word(ps) for ps in non_zero_lie_algebra_elements], ) new_circuit = append_time_evolution( lie_gradient, self.stepsize, self.trottersteps, self.exact )(self.circuit.func) self.circuit = qml.QNode(new_circuit, self.circuit.device) return self.circuit, cost
def test_ansatz_qiskit(self, token, tol, shots): """Test a simple VQE problem with an ansatz from Qiskit library.""" IBMQ.enable_account(token) coeffs = [1, 1] obs = [qml.PauliX(0), qml.PauliZ(0)] hamiltonian = qml.Hamiltonian(coeffs, obs) program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations") job = vqe_runner( program_id=program_id, backend="ibmq_qasm_simulator", hamiltonian=hamiltonian, ansatz="EfficientSU2", x0=[0.0, 0.0, 0.0, 0.0], shots=shots, kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, ) provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations") delete_vqe_runner(provider=provider, program_id=program_id) assert isinstance(job.intermediate_results, dict) assert "nfev" in job.intermediate_results assert "parameters" in job.intermediate_results assert "function" in job.intermediate_results assert "step" in job.intermediate_results assert "accepted" in job.intermediate_results
def test_qnode(self, token, tol, shots): """Test that we cannot pass a QNode as ansatz circuit.""" IBMQ.enable_account(token) with qml.tape.QuantumTape() as vqe_tape: qml.RX(3.97507603, wires=0) qml.RY(3.00854038, wires=0) coeffs = [1, 1] obs = [qml.PauliX(0), qml.PauliZ(0)] hamiltonian = qml.Hamiltonian(coeffs, obs) program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations") with pytest.raises( qml.QuantumFunctionError, match="The ansatz must be a callable quantum function." ): vqe_runner( program_id=program_id, backend="ibmq_qasm_simulator", hamiltonian=hamiltonian, ansatz=vqe_tape, x0=[3.97507603, 3.00854038], shots=shots, optimizer="SPSA", optimizer_config={"maxiter": 40}, kwargs={ "hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations", }, ) provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations") delete_vqe_runner(provider=provider, program_id=program_id)
def __add__(self, H): r"""The addition operation between a Hamiltonian and a Hamiltonian/Tensor/Observable.""" coeffs = self.coeffs.copy() ops = self.ops.copy() if isinstance(H, Hamiltonian): coeffs.extend(H.coeffs.copy()) ops.extend(H.ops.copy()) return qml.Hamiltonian(coeffs, ops, simplify=True) if isinstance(H, (Tensor, Observable)): coeffs.append(1) ops.append(H) return qml.Hamiltonian(coeffs, ops, simplify=True) raise ValueError(f"Cannot add Hamiltonian and {type(H)}")
def parse_hamiltonian_input(input_data): """ DO NOT MODIFY anything in this function! It is used to judge your solution. Turns the contents of the input file into a Hamiltonian. Args: filename(str): Name of the input file that contains the Hamiltonian. Returns: qml.Hamiltonian object of the Hamiltonian specified in the file. """ # Get the input coeffs = [] pauli_terms = [] # Go through line by line and build up the Hamiltonian for line in input_data.split("S"): line = line.strip() tokens = line.split(" ") # Parse coefficients sign, value = tokens[0], tokens[1] coeff = float(value) if sign == "-": coeff *= -1 coeffs.append(coeff) # Parse Pauli component pauli = tokens[2:] pauli_terms.append(pauli_token_to_operator(pauli)) return qml.Hamiltonian(coeffs, pauli_terms)
def test_single_shots(self, mocker, monkeypatch): """Test that, if the shot budget for a single term is 1, that the number of dimensions for the returned Jacobian is expanded""" coeffs = [0.2, 0.1, 0.1] dev = qml.device("default.qubit", wires=2, shots=100) H = qml.Hamiltonian( coeffs, [qml.PauliZ(0), qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(1)]) expval_cost = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, H, dev) weights = qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2) opt = qml.ShotAdaptiveOptimizer(min_shots=10) spy = mocker.spy(qml, "jacobian") spy_dims = mocker.spy(np, "expand_dims") mocker.patch("scipy.stats._multivariate.multinomial_gen.rvs", return_value=np.array([[4, 1, 5]])) grads = opt.weighted_random_sampling(expval_cost.qnodes, coeffs, 10, [0], weights) spy_dims.assert_called_once() assert len(spy.call_args_list) == 3 assert len(grads) == 1 assert grads[0].shape == (10, *weights.shape)
def test_multiple_devices(self, mocker): """Test that passing multiple devices to ExpvalCost works correctly""" dev = [ qml.device("default.qubit", wires=2), qml.device("default.mixed", wires=2) ] spy = mocker.spy(DefaultQubit, "apply") spy2 = mocker.spy(DefaultMixed, "apply") obs = [qml.PauliZ(0), qml.PauliZ(1)] h = qml.Hamiltonian([1, 1], obs) qnodes = qml.ExpvalCost(qml.templates.BasicEntanglerLayers, h, dev) w = qml.init.basic_entangler_layers_uniform(3, 2, seed=1967) res = qnodes(w) spy.assert_called_once() spy2.assert_called_once() mapped = qml.map(qml.templates.BasicEntanglerLayers, obs, dev) exp = sum(mapped(w)) assert np.allclose(res, exp) with pytest.warns( UserWarning, match="ExpvalCost was instantiated with multiple devices."): qnodes.metric_tensor([w])
def test_invalid_hamiltonian_expansion_finite_shots(self, mocker): """Test that an error is raised if multiple expectations are requested when using finite shots""" dev = qml.device("default.qubit", wires=3, shots=50000) obs = [ qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1) ] c = np.array([-0.6543, 0.24, 0.54]) H = qml.Hamiltonian(c, obs) H.compute_grouping() assert len(H.grouping_indices) == 2 @qnode(dev) def circuit(): return qml.expval(H), qml.expval(H) with pytest.raises( ValueError, match="Can only return the expectation of a single Hamiltonian" ): circuit()
def expand(self): # uses standard PauliRot decomposition through ApproxTimeEvolution. hamiltonian = qml.Hamiltonian(self.parameters[1:], self.hamiltonian.ops) time = self.parameters[0] return qml.templates.ApproxTimeEvolution(hamiltonian, time, 1).expand()
def test_multiple_devices(self, mocker): """Test that passing multiple devices to ExpvalCost works correctly""" dev = [qml.device("default.qubit", wires=2), qml.device("default.mixed", wires=2)] spy = mocker.spy(DefaultQubit, "apply") spy2 = mocker.spy(DefaultMixed, "apply") obs = [qml.PauliZ(0), qml.PauliZ(1)] h = qml.Hamiltonian([1, 1], obs) qnodes = qml.ExpvalCost(qml.templates.BasicEntanglerLayers, h, dev) np.random.seed(1967) w = np.random.random(qml.templates.BasicEntanglerLayers.shape(n_layers=3, n_wires=2)) res = qnodes(w) spy.assert_called_once() spy2.assert_called_once() mapped = qml.map(qml.templates.BasicEntanglerLayers, obs, dev) exp = sum(mapped(w)) assert np.allclose(res, exp) with pytest.warns(UserWarning, match="ExpvalCost was instantiated with multiple devices."): qml.metric_tensor(qnodes, approx="block-diag")(w)
def test_hamiltonian_expansion_finite_shots(self, mocker): """Test that the Hamiltonian is expanded if there are non-commuting groups and the number of shots is finite""" dev = qml.device("default.qubit", wires=3, shots=50000) obs = [ qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1) ] c = np.array([-0.6543, 0.24, 0.54]) H = qml.Hamiltonian(c, obs) H.compute_grouping() assert len(H.grouping_indices) == 2 @qnode(dev) def circuit(): return qml.expval(H) spy = mocker.spy(qml.transforms, "hamiltonian_expand") res = circuit() assert np.allclose(res, c[2], atol=0.1) spy.assert_called() tapes, fn = spy.spy_return assert len(tapes) == 2
def test_four_term_case(self): """Tests the parameter shift rules for `CommutingEvolution` equal the finite difference result for a four term shift rule case.""" n_wires = 2 dev = qml.device("default.qubit", wires=n_wires) coeffs = [1, -1] obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] hamiltonian = qml.Hamiltonian(coeffs, obs) frequencies = (2, 4) @qml.qnode(dev) def circuit(time): qml.PauliX(0) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(0)) x_vals = [ np.array(x, requires_grad=True) for x in np.linspace(-np.pi, np.pi, num=10) ] grads_finite_diff = [ qml.gradients.finite_diff(circuit)(x) for x in x_vals ] grads_param_shift = [ qml.gradients.param_shift(circuit)(x) for x in x_vals ] assert all(np.isclose(grads_finite_diff, grads_param_shift, atol=1e-4))
def test_two_term_case(self): """Tests the parameter shift rules for `CommutingEvolution` equal the finite difference result for a two term shift rule case.""" n_wires = 1 dev = qml.device("default.qubit", wires=n_wires) hamiltonian = qml.Hamiltonian([1], [qml.PauliX(0)]) frequencies = (2, ) @qml.qnode(dev) def circuit(time): qml.PauliX(0) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(0)) x_vals = np.linspace(-np.pi, np.pi, num=10) grads_finite_diff = [ qml.gradients.finite_diff(circuit)(x) for x in x_vals ] grads_param_shift = [ qml.gradients.param_shift(circuit)(x) for x in x_vals ] assert all(np.isclose(grads_finite_diff, grads_param_shift, atol=1e-4))
def test_adjoint(): """Tests the CommutingEvolution.adjoint method provides the correct adjoint operation.""" n_wires = 2 dev1 = qml.device("default.qubit", wires=n_wires) dev2 = qml.device("default.qubit", wires=n_wires) obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)] coeffs = [1, -1] hamiltonian = qml.Hamiltonian(coeffs, obs) frequencies = (2, ) @qml.qnode(dev1) def adjoint_evolution_circuit(time): for i in range(n_wires): qml.Hadamard(i) qml.adjoint(qml.CommutingEvolution)(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(1)) @qml.qnode(dev2) def evolution_circuit(time): for i in range(n_wires): qml.Hadamard(i) qml.CommutingEvolution(hamiltonian, time, frequencies) return qml.expval(qml.PauliZ(1)) evolution_circuit(0.13) adjoint_evolution_circuit(-0.13) assert all(np.isclose(dev1.state, dev2.state))
def test_translate_result_type_hamiltonian_unsupported_return(return_type): """Tests if a NotImplementedError is raised by translate_result_type with Hamiltonian observable and non-Expectation return type""" obs = qml.Hamiltonian((2, 3), (qml.PauliX(wires=0), qml.PauliY(wires=1))) obs.return_type = return_type with pytest.raises(NotImplementedError, match="unsupported for Hamiltonian"): translate_result_type(obs, [0], frozenset())
def test_hamiltonian_grad(self): """Test that the gradient of Hamiltonians works as expected.""" dev = qml.device("default.qubit", wires=2) with qml.tape.JacobianTape() as tape: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) qml.expval( qml.Hamiltonian([-1.5, 2.0], [qml.PauliZ(0), qml.PauliZ(1)])) tape.trainable_params = {2, 3} res = qml.math.stack(tape.jacobian(dev)[0]) with qml.tape.JacobianTape() as tape1: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) with qml.tape.JacobianTape() as tape2: qml.RY(0.3, wires=0) qml.RX(0.5, wires=1) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(1)) expected = qml.math.stack(qml.execute([tape1, tape2], dev, None)) assert np.allclose(expected, res)
def test_learning_error(self): """Test that an exception is raised if the learning rate is beyond the lipschitz bound""" coeffs = [0.3, 0.1] H = qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)]) dev = qml.device("default.qubit", wires=1, shots=100) expval_cost = qml.ExpvalCost(lambda x, **kwargs: qml.RX(x, wires=0), H, dev) opt = qml.ShotAdaptiveOptimizer(min_shots=10, stepsize=100.0) # lipschitz constant is given by sum(|coeffs|) lipschitz = np.sum(np.abs(coeffs)) assert opt._stepsize > 2 / lipschitz with pytest.raises( ValueError, match=f"The learning rate must be less than {2 / lipschitz}"): opt.step(expval_cost, 0.5) # for a single QNode, the lipschitz constant is simply 1 opt = qml.ShotAdaptiveOptimizer(min_shots=10, stepsize=100.0) with pytest.raises( ValueError, match=f"The learning rate must be less than {2 / 1}"): opt.step(expval_cost.qnodes[0], 0.5)
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.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = Variable([a, b], dtype=tf.float64) cost = qml.ExpvalCost(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_vqe_optimization(self): """Test that a simple VQE circuit can be optimized""" dev = qml.device("default.qubit", wires=2, shots=100) coeffs = [0.1, 0.2] obs = [qml.PauliZ(0), qml.PauliX(0)] H = qml.Hamiltonian(coeffs, obs) def ansatz(x, **kwargs): qml.Rot(*x[0], wires=0) qml.Rot(*x[1], wires=1) qml.CNOT(wires=[0, 1]) qml.Rot(*x[2], wires=0) qml.Rot(*x[3], wires=1) qml.CNOT(wires=[0, 1]) cost = qml.ExpvalCost(ansatz, H, dev) params = np.random.random((4, 3), requires_grad=True) initial_loss = cost(params) min_shots = 10 loss = initial_loss opt = qml.ShotAdaptiveOptimizer(min_shots=10) for i in range(100): params = opt.step(cost, params) loss = cost(params) assert loss < initial_loss assert np.allclose(loss, -1 / (2 * np.sqrt(5)), atol=0.1, rtol=0.2) assert opt.shots_used > min_shots
def test_cost_evaluate(self, params, ansatz, coeffs, observables): """Tests that the cost function evaluates properly""" hamiltonian = qml.Hamiltonian(coeffs, observables) dev = qml.device("default.qubit", wires=3) expval = qml.ExpvalCost(ansatz, hamiltonian, dev) assert type(expval(params)) == np.float64 assert np.shape(expval(params)) == () # expval should be scalar
def test_sparse_matrix(self, coeffs, obs, wires, ref_matrix): """Tests that sparse_hamiltonian returns a correct sparse matrix""" H = qml.Hamiltonian(coeffs, obs) sparse_matrix = qml.utils.sparse_hamiltonian(H, wires) assert np.allclose(sparse_matrix.toarray(), ref_matrix)
def test_circuits_evaluate(self, ansatz, observables, params, tol): """Tests simple VQE evaluations.""" coeffs = [1.0] * len(observables) dev = qml.device("default.qubit", wires=3) H = qml.Hamiltonian(coeffs, observables) # pass H directly @qml.qnode(dev) def circuit(): ansatz(params, wires=range(3)) return qml.expval(H) res = circuit() res_expected = [] for obs in observables: @qml.qnode(dev) def circuit(): ansatz(params, wires=range(3)) return qml.expval(obs) res_expected.append(circuit()) res_expected = np.sum([c * r for c, r in zip(coeffs, res_expected)]) assert np.isclose(res, res_expected, atol=tol)
def __mul__(self, a): r"""The scalar multiplication operation between a scalar and a Hamiltonian.""" if isinstance(a, (int, float)): coeffs = [a * c for c in self.coeffs.copy()] return qml.Hamiltonian(coeffs, self.ops.copy()) raise ValueError(f"Cannot multiply Hamiltonian by {type(a)}")
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.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = np.array([a, b]) cost = qml.ExpvalCost(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_bit_driver_output(self): """Tests that the bit driver Hamiltonian has the correct output""" H = qaoa.bit_driver(range(3), 1) hamiltonian = qml.Hamiltonian([1, 1, 1], [qml.PauliZ(0), qml.PauliZ(1), qml.PauliZ(2)]) assert decompose_hamiltonian(H) == decompose_hamiltonian(hamiltonian)
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.Hamiltonian(coeffs, observables) a, b = 0.54, 0.123 params = torch.autograd.Variable(torch.tensor([a, b]), requires_grad=True) cost = qml.ExpvalCost(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_ansatz_qiskit_invalid(self, token, tol, shots): """Test a simple VQE problem with an invalid ansatz from Qiskit library.""" IBMQ.enable_account(token) coeffs = [1, 1] obs = [qml.PauliX(0), qml.PauliZ(0)] hamiltonian = qml.Hamiltonian(coeffs, obs) program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations") with pytest.raises( ValueError, match="Ansatz InEfficientSU2 not in n_local circuit library." ): vqe_runner( program_id=program_id, backend="ibmq_qasm_simulator", hamiltonian=hamiltonian, ansatz="InEfficientSU2", x0=[3.97507603, 3.00854038], shots=shots, kwargs={ "hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations", }, ) provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations") delete_vqe_runner(provider=provider, program_id=program_id)
def test_translate_result_type_hamiltonian_expectation(): """Tests that a Hamiltonian is translated correctly""" obs = qml.Hamiltonian((2, 3), (qml.PauliX(wires=0), qml.PauliY(wires=1))) obs.return_type = ObservableReturnTypes.Expectation braket_result_type_calculated = translate_result_type(obs, [0], frozenset()) braket_result_type = (Expectation(observables.X(), [0]), Expectation(observables.Y(), [1])) assert braket_result_type == braket_result_type_calculated
def test_wrong_input(self, token, tol, shots): """Test that we can only give a single vector parameter to the ansatz circuit.""" IBMQ.enable_account(token) def vqe_circuit(params, wire): qml.RX(params[0], wires=wire) qml.RY(params[1], wires=wire) coeffs = [1, 1] obs = [qml.PauliX(0), qml.PauliZ(0)] hamiltonian = qml.Hamiltonian(coeffs, obs) program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations") with pytest.raises(qml.QuantumFunctionError, match="Param should be a single vector."): vqe_runner( program_id=program_id, backend="ibmq_qasm_simulator", hamiltonian=hamiltonian, ansatz=vqe_circuit, x0=[3.97507603, 3.00854038], shots=shots, optimizer="SPSA", optimizer_config={"maxiter": 40}, kwargs={ "hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations", }, ) provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations") delete_vqe_runner(provider=provider, program_id=program_id)
def test_metric_tensor_tape_mode(self): """Test that the metric tensor can be calculated in tape mode, and that it is equal to a metric tensor calculated in non-tape mode.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=2) p = np.array([1., 1., 1.]) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) h = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]) qnodes = qml.ExpvalCost(ansatz, h, dev) mt = qml.metric_tensor(qnodes)(p) assert qml.tape_mode_active() # Check that tape mode is still active qml.disable_tape() @qml.qnode(dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) return qml.expval(qml.PauliZ(0)) mt2 = circuit.metric_tensor([p]) assert np.allclose(mt, mt2)