def circuit2(a, b, c): ansatz(a, b, c) return expval(qml.Hermitian(np.kron(Z, Z), wires=[0, 2]))
def obs(): """A fixture of observables to go after the queue fixture.""" return [ qml.expval(qml.PauliX(wires=0)), qml.expval(qml.Hermitian(np.identity(4), wires=[1, 2])), ]
def layer0_off_diag_double(params): layer0_subcircuit(params) ZZ = np.kron(np.diag([1, -1]), np.diag([1, -1])) return expval(qml.Hermitian(ZZ, wires=[0, 1]))
def _metric_tensor_cov_matrix(tape, diag_approx): r"""This is the metric tensor method for the block diagonal, using the covariance matrix of the generators of each layer. Args: tape (pennylane.QNode or .QuantumTape): quantum tape or QNode to find the metric tensor of diag_approx (bool): if True, use the diagonal approximation. If ``False`` , a block-diagonal approximation of the metric tensor is computed. Returns: list[pennylane.tape.QuantumTape]: Transformed tapes that compute the probabilities required for the covariance matrix callable: Post-processing function that computes the covariance matrix from the results of the tapes in the first return value list[list[.Observable]]: Observables measured in each tape, one inner list corresponding to one tape in the first return value list[list[float]]: Coefficients to scale the results for each observable, one inner list corresponding to one tape in the first return value This method assumes the ``generator`` of all parametrized operations with respect to which the tensor is computed to be Hermitian. This is the case for unitary single-parameter operations. """ # get the circuit graph graph = tape.graph metric_tensor_tapes = [] obs_list = [] coeffs_list = [] params_list = [] for queue, curr_ops, param_idx, _ in graph.iterate_parametrized_layers(): params_list.append(param_idx) coeffs_list.append([]) obs_list.append([]) # for each operation in the layer, get the generator for op in curr_ops: gen, s = op.generator if op.inverse: s = -s w = op.wires coeffs_list[-1].append(s) # get the observable corresponding to the generator of the current operation if isinstance(gen, np.ndarray): # generator is a Hermitian matrix obs_list[-1].append(qml.Hermitian(gen, w)) elif issubclass(gen, qml.operation.Observable): # generator is an existing PennyLane operation obs_list[-1].append(gen(w)) else: raise qml.QuantumFunctionError( f"Can't generate metric tensor, generator {gen}" "has no corresponding observable") # Create a quantum tape with all operations # prior to the parametrized layer, and the rotations # to measure in the basis of the parametrized layer generators. with tape.__class__() as layer_tape: for op in queue: qml.apply(op) for o in obs_list[-1]: o.diagonalizing_gates() qml.probs(wires=tape.wires) metric_tensor_tapes.append(layer_tape) def processing_fn(probs): gs = [] for prob, obs, coeffs in zip(probs, obs_list, coeffs_list): # calculate the covariance matrix of this layer scale = qml.math.convert_like(qml.math.outer(coeffs, coeffs), prob) scale = qml.math.cast_like(scale, prob) g = scale * qml.math.cov_matrix( prob, obs, wires=tape.wires, diag_approx=diag_approx) gs.append(g) # create the block diagonal metric tensor return qml.math.block_diag(gs) return metric_tensor_tapes, processing_fn, obs_list, coeffs_list
def circuit(theta, phi): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.Hermitian(A_, wires=[0, 1]))
def circuit(): qml.RX(phi, wires=[0]) qml.RY(theta, wires=[0]) return qml.var(qml.Hermitian(H, wires=0))
def circuit(): qml.RX(theta, wires=[0]) return qml.sample(qml.Hermitian(A_, wires=0))
def test_execute_all_samples(mock_run): result = GateModelQuantumTaskResult.from_string( json.dumps({ "braketSchemaHeader": { "name": "braket.task_result.gate_model_task_result", "version": "1", }, "measurements": [[0, 0, 1], [1, 0, 1], [1, 1, 0], [0, 0, 0]], "resultTypes": [ { "type": { "observable": ["h", "i"], "targets": [0, 1], "type": "sample" }, "value": [1, -1, 1, 1], }, { "type": { "observable": [[[[0.0, 0.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 0.0]]]], "targets": [2], "type": "sample", }, "value": [1, -1, 1, 1], }, ], "measuredQubits": [0, 1, 3], "taskMetadata": { "braketSchemaHeader": { "name": "braket.task_result.task_metadata", "version": "1", }, "id": "task_arn", "shots": 0, "deviceId": "default", }, "additionalMetadata": { "action": { "braketSchemaHeader": { "name": "braket.ir.jaqcd.program", "version": "1" }, "instructions": [{ "control": 0, "target": 1, "type": "cnot" }], }, }, })) task = Mock() task.result.return_value = result mock_run.return_value = task dev = _aws_device(wires=3) with QuantumTape() as circuit: qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.sample(qml.Hadamard(0) @ qml.Identity(1)) qml.sample(qml.Hermitian(np.array([[0, 1], [1, 0]]), wires=[2])) assert dev.execute(circuit).shape == (2, 4)
def test_hermitian(self, device, shots, tol): """Test that a tensor product involving qml.Hermitian works correctly""" theta = 0.432 phi = 0.123 varphi = -0.543 # Reseed here so that all eigenvalues are guaranteed to appear in the sample np.random.seed(143) dev = device(3) A = np.array([ [-6, 2 + 1j, -3, -5 + 2j], [2 - 1j, 0, 2 - 1j, -5 + 4j], [-3, 2 + 1j, 0, -4 + 3j], [-5 - 2j, -5 - 4j, -4 - 3j, -6], ]) obs = qml.PauliZ(wires=[0], do_queue=False) @ qml.Hermitian( A, wires=[1, 2], do_queue=False) with mimic_execution_for_sample(dev): dev.apply( [ qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.RX(varphi, wires=[2]), qml.CNOT(wires=[0, 1]), qml.CNOT(wires=[1, 2]), ], rotations=obs.diagonalizing_gates(), ) s1 = dev.sample(obs) # s1 should only contain the eigenvalues of # the hermitian matrix tensor product Z Z = np.diag([1, -1]) eigvals = np.linalg.eigvalsh(np.kron(Z, A)) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), **tol) mean = np.mean(s1) expected = 0.5 * (-6 * np.cos(theta) * (np.cos(varphi) + 1) - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) + 3 * np.cos(varphi) * np.sin(phi) + np.sin(phi)) assert np.allclose(mean, expected, **tol) var = np.var(s1) expected = ( 1057 - np.cos(2 * phi) + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) + 16 * np.sin(2 * phi) - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi))**2 - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - 8 * np.cos(theta) * (4 * np.cos(phi) * (4 + 8 * np.cos(varphi) + np.cos(2 * varphi) - (1 + 6 * np.cos(varphi)) * np.sin(varphi)) + np.sin(phi) * (15 + 8 * np.cos(varphi) - 11 * np.cos(2 * varphi) + 42 * np.sin(varphi) + 3 * np.sin(2 * varphi)))) / 16 assert np.allclose(var, expected, **tol)
O = tensor_observable O_expected = expected O_pruned = O.prune() assert type(O_pruned) == type(expected) assert O_pruned.wires == expected.wires equal_obs = [ (qml.PauliZ(0), qml.PauliZ(0), True), (qml.PauliZ(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliX(1) @ qml.Identity(2), True), (qml.PauliZ("b"), qml.PauliZ("b") @ qml.Identity(1.3), True), (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), True), (qml.PauliZ(0), qml.PauliZ(1) @ qml.Identity(0), False), ( qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), qml.Identity(1) @ qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), True, ), (qml.PauliZ("a") @ qml.PauliX(1), qml.PauliX(1) @ qml.PauliZ("a"), True), (qml.PauliZ("a"), qml.Hamiltonian([1], [qml.PauliZ("a")]), True), ] add_obs = [ (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), qml.Hamiltonian([2], [qml.PauliZ(0)])), ( qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1)]), ), (
class TestTensor: """Unit tests for the Tensor class""" def test_construct(self): """Test construction of a tensor product""" X = qml.PauliX(0) Y = qml.PauliY(2) T = Tensor(X, Y) assert T.obs == [X, Y] T = Tensor(T, Y) assert T.obs == [X, Y, Y] with pytest.raises( ValueError, match="Can only perform tensor products between observables" ): Tensor(T, qml.CNOT(wires=[0, 1])) def test_name(self): """Test that the names of the observables are returned as expected""" X = qml.PauliX(0) Y = qml.PauliY(2) t = Tensor(X, Y) assert t.name == [X.name, Y.name] def test_num_wires(self): """Test that the correct number of wires is returned""" p = np.array([0.5]) X = qml.PauliX(0) Y = qml.Hermitian(p, wires=[1, 2]) t = Tensor(X, Y) assert t.num_wires == 3 def test_wires(self): """Test that the correct nested list of wires is returned""" p = np.array([0.5]) X = qml.PauliX(0) Y = qml.Hermitian(p, wires=[1, 2]) t = Tensor(X, Y) assert t.wires == Wires([0, 1, 2]) def test_params(self): """Test that the correct flattened list of parameters is returned""" p = np.array([0.5]) X = qml.PauliX(0) Y = qml.Hermitian(p, wires=[1, 2]) t = Tensor(X, Y) assert t.data == [p] def test_num_params(self): """Test that the correct number of parameters is returned""" p = np.array([0.5]) X = qml.PauliX(0) Y = qml.Hermitian(p, wires=[1, 2]) Z = qml.Hermitian(p, wires=[1, 2]) t = Tensor(X, Y, Z) assert t.num_params == 2 def test_parameters(self): """Test that the correct nested list of parameters is returned""" p = np.array([0.5]) X = qml.PauliX(0) Y = qml.Hermitian(p, wires=[1, 2]) t = Tensor(X, Y) assert t.parameters == [[], [p]] def test_multiply_obs(self): """Test that multiplying two observables produces a tensor""" X = qml.PauliX(0) Y = qml.Hadamard(2) t = X @ Y assert isinstance(t, Tensor) assert t.obs == [X, Y] def test_multiply_obs_tensor(self): """Test that multiplying an observable by a tensor produces a tensor""" X = qml.PauliX(0) Y = qml.Hadamard(2) Z = qml.PauliZ(1) t = X @ Y t = Z @ t assert isinstance(t, Tensor) assert t.obs == [Z, X, Y] def test_multiply_tensor_obs(self): """Test that multiplying a tensor by an observable produces a tensor""" X = qml.PauliX(0) Y = qml.Hadamard(2) Z = qml.PauliZ(1) t = X @ Y t = t @ Z assert isinstance(t, Tensor) assert t.obs == [X, Y, Z] def test_multiply_tensor_tensor(self): """Test that multiplying a tensor by a tensor produces a tensor""" X = qml.PauliX(0) Y = qml.PauliY(2) Z = qml.PauliZ(1) H = qml.Hadamard(3) t1 = X @ Y t2 = Z @ H t = t2 @ t1 assert isinstance(t, Tensor) assert t.obs == [Z, H, X, Y] def test_multiply_tensor_in_place(self): """Test that multiplying a tensor in-place produces a tensor""" X = qml.PauliX(0) Y = qml.PauliY(2) Z = qml.PauliZ(1) H = qml.Hadamard(3) t = X t @= Y t @= Z @ H assert isinstance(t, Tensor) assert t.obs == [X, Y, Z, H] def test_operation_multiply_invalid(self): """Test that an exception is raised if an observable is multiplied by an operation""" X = qml.PauliX(0) Y = qml.CNOT(wires=[0, 1]) Z = qml.PauliZ(0) with pytest.raises( ValueError, match="Can only perform tensor products between observables" ): X @ Y with pytest.raises( ValueError, match="Can only perform tensor products between observables" ): T = X @ Z T @ Y with pytest.raises( ValueError, match="Can only perform tensor products between observables" ): T = X @ Z Y @ T def test_eigvals(self): """Test that the correct eigenvalues are returned for the Tensor""" X = qml.PauliX(0) Y = qml.PauliY(2) t = Tensor(X, Y) assert np.array_equal(t.eigvals, np.kron([1, -1], [1, -1])) # test that the eigvals are now cached and not recalculated assert np.array_equal(t._eigvals_cache, t.eigvals) @pytest.mark.usefixtures("tear_down_hermitian") def test_eigvals_hermitian(self, tol): """Test that the correct eigenvalues are returned for the Tensor containing an Hermitian observable""" X = qml.PauliX(0) hamiltonian = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) Herm = qml.Hermitian(hamiltonian, wires=[1, 2]) t = Tensor(X, Herm) d = np.kron(np.array([1.0, -1.0]), np.array([-1.0, 1.0, 1.0, 1.0])) t = t.eigvals assert np.allclose(t, d, atol=tol, rtol=0) def test_eigvals_identity(self, tol): """Test that the correct eigenvalues are returned for the Tensor containing an Identity""" X = qml.PauliX(0) Iden = qml.Identity(1) t = Tensor(X, Iden) d = np.kron(np.array([1.0, -1.0]), np.array([1.0, 1.0])) t = t.eigvals assert np.allclose(t, d, atol=tol, rtol=0) def test_eigvals_identity_and_hermitian(self, tol): """Test that the correct eigenvalues are returned for the Tensor containing multiple types of observables""" H = np.diag([1, 2, 3, 4]) O = qml.PauliX(0) @ qml.Identity(2) @ qml.Hermitian(H, wires=[4, 5]) res = O.eigvals expected = np.kron(np.array([1.0, -1.0]), np.kron(np.array([1.0, 1.0]), np.arange(1, 5))) assert np.allclose(res, expected, atol=tol, rtol=0) def test_diagonalizing_gates(self, tol): """Test that the correct diagonalizing gate set is returned for a Tensor of observables""" H = np.diag([1, 2, 3, 4]) O = qml.PauliX(0) @ qml.Identity(2) @ qml.PauliY(1) @ qml.Hermitian(H, [5, 6]) res = O.diagonalizing_gates() # diagonalize the PauliX on wire 0 (H.X.H = Z) assert isinstance(res[0], qml.Hadamard) assert res[0].wires == Wires([0]) # diagonalize the PauliY on wire 1 (U.Y.U^\dagger = Z # where U = HSZ). assert isinstance(res[1], qml.PauliZ) assert res[1].wires == Wires([1]) assert isinstance(res[2], qml.S) assert res[2].wires == Wires([1]) assert isinstance(res[3], qml.Hadamard) assert res[3].wires == Wires([1]) # diagonalize the Hermitian observable on wires 5, 6 assert isinstance(res[4], qml.QubitUnitary) assert res[4].wires == Wires([5, 6]) O = O @ qml.Hadamard(4) res = O.diagonalizing_gates() # diagonalize the Hadamard observable on wire 4 # (RY(-pi/4).H.RY(pi/4) = Z) assert isinstance(res[-1], qml.RY) assert res[-1].wires == Wires([4]) assert np.allclose(res[-1].parameters, -np.pi / 4, atol=tol, rtol=0) def test_diagonalizing_gates_numerically_diagonalizes(self, tol): """Test that the diagonalizing gate set numerically diagonalizes the tensor observable""" # create a tensor observable acting on consecutive wires H = np.diag([1, 2, 3, 4]) O = qml.PauliX(0) @ qml.PauliY(1) @ qml.Hermitian(H, [2, 3]) O_mat = O.matrix diag_gates = O.diagonalizing_gates() # group the diagonalizing gates based on what wires they act on U_list = [] for _, g in itertools.groupby(diag_gates, lambda x: x.wires.tolist()): # extract the matrices of each diagonalizing gate mats = [i.matrix for i in g] # Need to revert the order in which the matrices are applied such that they adhere to the order # of matrix multiplication # E.g. for PauliY: [PauliZ(wires=self.wires), S(wires=self.wires), Hadamard(wires=self.wires)] # becomes Hadamard @ S @ PauliZ, where @ stands for matrix multiplication mats = mats[::-1] if len(mats) > 1: # multiply all unitaries together before appending mats = [multi_dot(mats)] # append diagonalizing unitary for specific wire to U_list U_list.append(mats[0]) # since the test is assuming consecutive wires for each observable # in the tensor product, it is sufficient to Kronecker product # the entire list. U = functools.reduce(np.kron, U_list) res = U @ O_mat @ U.conj().T expected = np.diag(O.eigvals) # once diagonalized by U, the result should be a diagonal # matrix of the eigenvalues. assert np.allclose(res, expected, atol=tol, rtol=0) def test_tensor_matrix(self, tol): """Test that the tensor product matrix method returns the correct result""" H = np.diag([1, 2, 3, 4]) O = qml.PauliX(0) @ qml.PauliY(1) @ qml.Hermitian(H, [2, 3]) res = O.matrix expected = np.kron(qml.PauliY._matrix(), H) expected = np.kron(qml.PauliX._matrix(), expected) assert np.allclose(res, expected, atol=tol, rtol=0) def test_multiplication_matrix(self, tol): """If using the ``@`` operator on two observables acting on the same wire, the tensor class should treat this as matrix multiplication.""" O = qml.PauliX(0) @ qml.PauliX(0) res = O.matrix expected = qml.PauliX._matrix() @ qml.PauliX._matrix() assert np.allclose(res, expected, atol=tol, rtol=0) herm_matrix = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) tensor_obs = [ (qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2), [qml.PauliZ(0), qml.PauliZ(2)]), ( qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2) @ qml.PauliZ(3) @ qml.PauliZ(4) @ qml.Identity(5), [qml.PauliX(1), qml.PauliZ(3), qml.PauliZ(4)], ), # List containing single observable is returned (qml.PauliZ(0) @ qml.Identity(1), [qml.PauliZ(0)]), (qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2), [qml.PauliX(1)]), (qml.Identity(0) @ qml.Identity(1), [qml.Identity(0)]), ( qml.Identity(0) @ qml.Identity(1) @ qml.Hermitian(herm_matrix, wires=[2, 3]), [qml.Hermitian(herm_matrix, wires=[2, 3])], ), ] @pytest.mark.parametrize("tensor_observable, expected", tensor_obs) def test_non_identity_obs(self, tensor_observable, expected): """Tests that the non_identity_obs property returns a list that contains no Identity instances.""" O = tensor_observable for idx, obs in enumerate(O.non_identity_obs): assert type(obs) == type(expected[idx]) assert obs.wires == expected[idx].wires tensor_obs_pruning = [ (qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2), qml.PauliZ(0) @ qml.PauliZ(2)), ( qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2) @ qml.PauliZ(3) @ qml.PauliZ(4) @ qml.Identity(5), qml.PauliX(1) @ qml.PauliZ(3) @ qml.PauliZ(4), ), # Single observable is returned (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0)), (qml.Identity(0) @ qml.PauliX(1) @ qml.Identity(2), qml.PauliX(1)), (qml.Identity(0) @ qml.Identity(1), qml.Identity(0)), (qml.Identity(0) @ qml.Identity(1), qml.Identity(0)), ( qml.Identity(0) @ qml.Identity(1) @ qml.Hermitian(herm_matrix, wires=[2, 3]), qml.Hermitian(herm_matrix, wires=[2, 3]), ), ] @pytest.mark.parametrize("tensor_observable, expected", tensor_obs_pruning) def test_prune(self, tensor_observable, expected): """Tests that the prune method returns the expected Tensor or single non-Tensor Observable.""" O = tensor_observable O_expected = expected O_pruned = O.prune() assert type(O_pruned) == type(expected) assert O_pruned.wires == expected.wires
##################################################### # Hamiltonians H_ONE_QUBIT = np.array([[1.0, 0.5j], [-0.5j, 2.5]]) H_TWO_QUBITS = np.array( [[0.5, 1.0j, 0.0, -3j], [-1.0j, -1.1, 0.0, -0.1], [0.0, 0.0, -0.9, 12.0], [3j, -0.1, 12.0, 0.0]] ) COEFFS = [(0.5, 1.2, -0.7), (2.2, -0.2, 0.0), (0.33,)] OBSERVABLES = [ (qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)), (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), (qml.Hermitian(H_TWO_QUBITS, [0, 1]),), ] JUNK_INPUTS = [None, [], tuple(), 5.0, {"junk": -1}] valid_hamiltonians = [ ((1.0,), (qml.Hermitian(H_TWO_QUBITS, [0, 1]),)), ((-0.8,), (qml.PauliZ(0),)), ((0.6,), (qml.PauliX(0) @ qml.PauliX(1),)), ((0.5, -1.6), (qml.PauliX(0), qml.PauliY(1))), ((0.5, -1.6), (qml.PauliX(1), qml.PauliY(1))), ((0.5, -1.6), (qml.PauliX("a"), qml.PauliY("b"))), ((1.1, -0.4, 0.333), (qml.PauliX(0), qml.Hermitian(H_ONE_QUBIT, 2), qml.PauliZ(2))), ((-0.4, 0.15), (qml.Hermitian(H_TWO_QUBITS, [0, 2]), qml.PauliZ(1))), ([1.5, 2.0], [qml.PauliZ(0), qml.PauliY(2)]), (np.array([-0.1, 0.5]), [qml.Hermitian(H_TWO_QUBITS, [0, 1]), qml.PauliY(0)]),
def kernel(x1, x2): """The quantum kernel.""" AngleEmbedding(x1, wires=range(n_qubits)) qml.inv(AngleEmbedding(x2, wires=range(n_qubits))) return qml.expval(qml.Hermitian(projector, wires=range(n_qubits)))
O_expected = expected O_pruned = O.prune() assert type(O_pruned) == type(expected) assert O_pruned.wires == expected.wires equal_obs = [ (qml.PauliZ(0), qml.PauliZ(0), True), (qml.PauliZ(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliX(1) @ qml.Identity(2), True), (qml.PauliZ("b"), qml.PauliZ("b") @ qml.Identity(1.3), True), (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), True), (qml.PauliZ(0), qml.PauliZ(1) @ qml.Identity(0), False), ( qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), qml.Identity(1) @ qml.Hermitian(np.array([[0, 1], [1, 0]]), 0), True, ), (qml.PauliZ("a") @ qml.PauliX(1), qml.PauliX(1) @ qml.PauliZ("a"), True), (qml.PauliZ("a"), qml.Hamiltonian([1], [qml.PauliZ("a")]), True), ] add_obs = [ (qml.PauliZ(0) @ qml.Identity(1), qml.PauliZ(0), qml.Hamiltonian([2], [qml.PauliZ(0)])), ( qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.Hamiltonian( [1, 1],
def circuit(x): qml.RX(x, wires=[0]) qml.CNOT(wires=[0, 1]) qml.SWAP(wires=[1, 0]) qml.RZ(-0.2, wires=[1]) return qml.probs(wires=[0]), qml.var(qml.Hermitian(H, wires=1))
def comp_basis_measurement(wires): n_wires = len(wires) return qml.Hermitian(np.diag(range(2**n_wires)), wires=wires)
import pennylane as qml pytestmark = pytest.mark.skip_unsupported # ========================================================== # Some useful global variables # observables for which device support is tested obs = { "Identity": qml.Identity(wires=[0]), "Hadamard": qml.Hadamard(wires=[0]), "Hermitian": qml.Hermitian(np.eye(2), wires=[0]), "PauliX": qml.PauliX(wires=[0]), "PauliY": qml.PauliY(wires=[0]), "PauliZ": qml.PauliZ(wires=[0]), "Projector": qml.Projector(np.array([1]), wires=[0]), "SparseHamiltonian": qml.SparseHamiltonian(coo_matrix(np.eye(8)), wires=[0, 1, 2]), "Hamiltonian": qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliX(0)]), } all_obs = obs.keys()
def qf(x, y): qml.RX(x, wires=[0]) qml.RY(x, wires=[0]) return qml.expval(qml.Hermitian(np.diag([y, 1]), 0))
def circuit(): qml.RY(theta, wires=[0]) qml.RY(phi, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.expval(qml.Hermitian(A, wires=0)), qml.expval( qml.Hermitian(A, wires=1))
def circuit(a): qml.RX(a, wires=0) return qml.var(qml.Hermitian(A, 0))
def circuit(): qml.RX(theta, wires=[0]) qml.RY(2 * theta, wires=[1]) qml.CNOT(wires=[0, 1]) return qml.sample(qml.Hermitian(A_, wires=[0, 1]))
class TestInputs: """Test inputs and pre-processing.""" def test_hamiltonian_error(self): """Tests if the correct error is thrown when hamiltonian is not a pennylane.Hamiltonian object""" n_wires = 2 dev = qml.device("default.qubit", wires=n_wires) hamiltonian = np.array([[1, 1], [1, 1]]) @qml.qnode(dev) def circuit(): qml.ApproxTimeEvolution(hamiltonian, 2, 3) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)] with pytest.raises(ValueError, match="hamiltonian must be of type pennylane.Hamiltonian"): circuit() @pytest.mark.parametrize( ("hamiltonian"), [ qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.Hadamard(0)]), qml.Hamiltonian( [1, 1], [qml.PauliX(0) @ qml.Hermitian(np.array([[1, 1], [1, 1]]), 1), qml.PauliX(0)], ), ], ) def test_non_pauli_error(self, hamiltonian): """Tests if the correct errors are thrown when the user attempts to input a matrix with non-Pauli terms""" n_wires = 2 dev = qml.device("default.qubit", wires=n_wires) @qml.qnode(dev) def circuit(): qml.ApproxTimeEvolution(hamiltonian, 2, 3) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)] with pytest.raises( ValueError, match="hamiltonian must be written in terms of Pauli matrices" ): circuit() def test_id(self): """Tests that the id attribute can be set.""" h = qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliY(0)]) template = qml.ApproxTimeEvolution(h, 2, 3, id="a") assert template.id == "a" def test_wire_indices(self): """Tests that correct wires are set.""" wire_indices = [0, 1] H = ( qml.PauliX(wire_indices[0]) + qml.PauliZ(wire_indices[1]) + 0.5 * qml.PauliX(wire_indices[0]) @ qml.PauliX(wire_indices[1]) ) qml.ApproxTimeEvolution(H, 0.5, 2) assert wire_indices[0] in H.wires assert wire_indices[1] in H.wires
##################################################### # Hamiltonians H_ONE_QUBIT = np.array([[1.0, 0.5j], [-0.5j, 2.5]]) H_TWO_QUBITS = np.array( [[0.5, 1.0j, 0.0, -3j], [-1.0j, -1.1, 0.0, -0.1], [0.0, 0.0, -0.9, 12.0], [3j, -0.1, 12.0, 0.0]] ) COEFFS = [(0.5, 1.2, -0.7), (2.2, -0.2, 0.0), (0.33,)] OBSERVABLES = [ (qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)), (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), (qml.Hermitian(H_TWO_QUBITS, [0, 1]),), ] JUNK_INPUTS = [None, [], tuple(), 5.0, {"junk": -1}] valid_hamiltonians = [ ((1.0,), (qml.Hermitian(H_TWO_QUBITS, [0, 1]),)), ((-0.8,), (qml.PauliZ(0),)), ((0.5, -1.6), (qml.PauliX(0), qml.PauliY(1))), ((0.5, -1.6), (qml.PauliX(1), qml.PauliY(1))), ((1.1, -0.4, 0.333), (qml.PauliX(0), qml.Hermitian(H_ONE_QUBIT, 2), qml.PauliZ(2))), ((-0.4, 0.15), (qml.Hermitian(H_TWO_QUBITS, [0, 2]), qml.PauliZ(1))), ([1.5, 2.0], [qml.PauliZ(0), qml.PauliY(2)]), (np.array([-0.1, 0.5]), [qml.Hermitian(H_TWO_QUBITS, [0, 1]), qml.PauliY(0)]), ((0.5, 1.2), (qml.PauliX(0), qml.PauliX(0) @ qml.PauliX(1))), ]
def circuit(params): StronglyEntanglingLayers(weights=params, wires=[0, 1]) return expval(qml.Hermitian(H, wires=[0, 1]))
##################################################### # Hamiltonians H_ONE_QUBIT = np.array([[1.0, 0.5j], [-0.5j, 2.5]]) H_TWO_QUBITS = np.array([[0.5, 1.0j, 0.0, -3j], [-1.0j, -1.1, 0.0, -0.1], [0.0, 0.0, -0.9, 12.0], [3j, -0.1, 12.0, 0.0]]) COEFFS = [(0.5, 1.2, -0.7), (2.2, -0.2, 0.0), (0.33, )] OBSERVABLES = [ (qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)), (qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1), qml.PauliZ(1)), (qml.Hermitian(H_TWO_QUBITS, [0, 1]), ), ] JUNK_INPUTS = [None, [], tuple(), 5.0, {"junk": -1}] valid_hamiltonians = [ ((1.0, ), (qml.Hermitian(H_TWO_QUBITS, [0, 1]), )), ((-0.8, ), (qml.PauliZ(0), )), ((0.6, ), (qml.PauliX(0) @ qml.PauliX(1), )), ((0.5, -1.6), (qml.PauliX(0), qml.PauliY(1))), ((0.5, -1.6), (qml.PauliX(1), qml.PauliY(1))), ((0.5, -1.6), (qml.PauliX("a"), qml.PauliY("b"))), ((1.1, -0.4, 0.333), (qml.PauliX(0), qml.Hermitian(H_ONE_QUBIT, 2), qml.PauliZ(2))), ((-0.4, 0.15), (qml.Hermitian(H_TWO_QUBITS, [0, 2]), qml.PauliZ(1))), ([1.5, 2.0], [qml.PauliZ(0), qml.PauliY(2)]),
def circuit(params, n=None): StronglyEntanglingLayers(weights=params, wires=[0, 1]) idx = np.random.choice(np.arange(5), size=n, replace=False) A = np.sum(terms[idx], axis=0) return expval(qml.Hermitian(A, wires=[0, 1]))
def parameter_shift_var(self, idx, params, **options): """Generate the tapes and postprocessing methods required to compute the gradient of a parameter and its variance using the parameter-shift method. Args: idx (int): trainable parameter index to differentiate with respect to params (list[Any]): the quantum tape operation parameters Keyword Args: shift=pi/2 (float): the size of the shift for two-term parameter-shift gradient computations Returns: tuple[list[QuantumTape], function]: A tuple containing the list of generated tapes, in addition to a post-processing function to be applied to the evaluated tapes. """ tapes = [] # Get <A>, the expectation value of the tape with unshifted parameters. evA_tape = self.copy() evA_tape.set_parameters(params) # Convert all variance measurements on the tape into expectation values for i in self.var_idx: obs = evA_tape._measurements[i].obs evA_tape._measurements[i] = MeasurementProcess( qml.operation.Expectation, obs=obs) # evaluate the analytic derivative of <A> pdA_tapes, pdA_fn = evA_tape.parameter_shift(idx, params, **options) tapes.extend(pdA_tapes) # For involutory observables (A^2 = I) we have d<A^2>/dp = 0. # Currently, the only observable we have in PL that may be non-involutory is qml.Hermitian involutory = [ i for i in self.var_idx if self.observables[i].name != "Hermitian" ] # If there are non-involutory observables A present, we must compute d<A^2>/dp. non_involutory = set(self.var_idx) - set(involutory) if non_involutory: pdA2_tape = self.copy() for i in non_involutory: # We need to calculate d<A^2>/dp; to do so, we replace the # involutory observables A in the queue with A^2. obs = pdA2_tape._measurements[i].obs A = obs.matrix obs = qml.Hermitian(A @ A, wires=obs.wires, do_queue=False) pdA2_tape._measurements[i] = MeasurementProcess( qml.operation.Expectation, obs=obs) # Non-involutory observables are present; the partial derivative of <A^2> # may be non-zero. Here, we calculate the analytic derivatives of the <A^2> # observables. pdA2_tapes, pdA2_fn = pdA2_tape.parameter_shift( idx, params, **options) tapes.extend(pdA2_tapes) # Make sure that the expectation value of the tape with unshifted parameters # is only calculated once, if `self._append_evA_tape` is True. if self._append_evA_tape: tapes.append(evA_tape) # Now that the <A> tape has been appended, we want to avoid # appending it for subsequent parameters, as the result can simply # be re-used. self._append_evA_tape = False def processing_fn(results): """Computes the gradient of the parameter at index ``idx`` via the parameter-shift method for a circuit containing a mixture of expectation values and variances. Args: results (list[real]): evaluated quantum tapes Returns: array[float]: 1-dimensional array of length determined by the tape output measurement statistics """ pdA = pdA_fn(results[0:2]) pdA2 = 0 if non_involutory: pdA2 = pdA2_fn(results[2:4]) if involutory: pdA2[np.array(involutory)] = 0 # Check if the expectation value of the tape with unshifted parameters # has already been calculated. if self._evA_result is None: # The expectation value hasn't been previously calculated; # it will be the last element of the `results` argument. self._evA_result = np.array(results[-1]) # return d(var(A))/dp = d<A^2>/dp -2 * <A> * d<A>/dp for the variances, # d<A>/dp for plain expectations return np.where(self.var_mask, pdA2 - 2 * self._evA_result * pdA, pdA) return tapes, processing_fn
def circuit(x): qml.RX(x, wires=[0]) qml.CNOT(wires=[0, 1]) qml.RY(0.4, wires=[0]) qml.RZ(-0.2, wires=[1]) return qml.expval(qml.PauliX(0)), qml.var(qml.Hermitian(H, wires=1))
def layer1_off_diag_double(params): layer1_subcircuit(params) X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]]) YX = np.kron(Y, X) return expval(qml.Hermitian(YX, wires=[1, 2]))
def circuit(a, b, c): ansatz(a, b, c) return sample(qml.PauliZ(0) @ qml.Hermitian(A, [1, 2]))