def test_apply(self, gate, apply_unitary, tol): """Test the application of gates to a state""" dev = plf.NumpyWavefunctionDevice(wires=3) try: # get the equivalent pennylane operation class op = getattr(qml.ops, gate) except AttributeError: # get the equivalent pennylane-forest operation class op = getattr(plf, gate) # the list of wires to apply the operation to w = list(range(op.num_wires)) obs = qml.expval(qml.PauliZ(0)) if op.par_domain == "A": # the parameter is an array if gate == "QubitUnitary": p = np.array(U) w = [0] state = apply_unitary(U, 3) elif gate == "BasisState": p = np.array([1, 1, 1]) state = np.array([0, 0, 0, 0, 0, 0, 0, 1]) w = list(range(dev.num_wires)) circuit_graph = qml.CircuitGraph([op(p, wires=w)] + [obs], {}, dev.wires) else: p = [0.432_423, 2, 0.324][:op.num_params] fn = test_operation_map[gate] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn # calculate the expected output state = apply_unitary(O, 3) # Creating the circuit graph using a parametrized operation if p: circuit_graph = qml.CircuitGraph([op(*p, wires=w)] + [obs], {}, dev.wires) # Creating the circuit graph using an operation that take no parameters else: circuit_graph = qml.CircuitGraph([op(wires=w)] + [obs], {}, dev.wires) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) res = dev.expval(obs) # verify the device is now in the expected state self.assertAllAlmostEqual(dev._state, state, delta=tol)
def test_sample_values(self, tol): """Tests if the samples returned by sample have the correct values """ dev = plf.NumpyWavefunctionDevice(wires=1, shots=10) phi = 1.5708 circuit_operations = [qml.RX(phi, wires=[0])] O = qml.sample(qml.PauliZ(0)) observables = [O] circuit_graph = qml.CircuitGraph(circuit_operations + observables, {}, dev.wires) # test correct variance for <Z> of a rotated state dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O) # s1 should only contain 1 and -1 self.assertAllAlmostEqual(s1**2, 1, delta=tol)
def test_var_hermitian(self, tol): """Tests for variance calculation using an arbitrary Hermitian observable""" dev = plf.NumpyWavefunctionDevice(wires=2) phi = 0.543 theta = 0.6543 H = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) circuit_operations = [qml.RX(phi, wires=[0]), qml.RY(theta, wires=[0])] O = qml.var(qml.Hermitian(H, wires=[0])) observables = [O] circuit_graph = qml.CircuitGraph(circuit_operations + observables, {}, dev.wires) # test correct variance for <Z> of a rotated state dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) var = dev.var(O) # test correct variance for <H> of a rotated state expected = 0.5 * (2 * np.sin(2 * theta) * np.cos(phi)**2 + 24 * np.sin(phi) * np.cos(phi) * (np.sin(theta) - np.cos(theta)) + 35 * np.cos(2 * phi) + 39) self.assertAlmostEqual(var, expected, delta=tol)
def test_multi_mode_hermitian_expectation(self, theta, phi, varphi, tol): """Test that arbitrary multi-mode Hermitian expectation values are correct""" 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], ]) dev = DefaultQubitTF(wires=2) queue = [ qml.RY(theta, wires=0), qml.RY(phi, wires=1), qml.CNOT(wires=[0, 1]) ] observables = [qml.Hermitian(A, wires=[0, 1])] for i in range(len(observables)): observables[i].return_type = qml.operation.Expectation res = dev.execute( qml.CircuitGraph(queue + observables, {}, Wires([0, 1]))) # below is the analytic expectation value for this circuit with arbitrary # Hermitian observable A expected = 0.5 * (6 * np.cos(theta) * np.sin(phi) - np.sin(theta) * (8 * np.sin(phi) + 7 * np.cos(phi) + 3) - 2 * np.sin(phi) - 6 * np.cos(phi) - 6) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_hermitian_expectation(self, theta, phi, varphi, tol): """Test that arbitrary Hermitian expectation values are correct""" dev = DefaultQubitTF(wires=2) queue = [ qml.RY(theta, wires=0), qml.RY(phi, wires=1), qml.CNOT(wires=[0, 1]) ] observables = [qml.Hermitian(A, wires=[i]) for i in range(2)] for i in range(len(observables)): observables[i].return_type = qml.operation.Expectation res = dev.execute( qml.CircuitGraph(queue + observables, {}, Wires([0, 1]))) a = A[0, 0] re_b = A[0, 1].real d = A[1, 1] ev1 = ((a - d) * np.cos(theta) + 2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2 ev2 = ((a - d) * np.cos(theta) * np.cos(phi) + 2 * re_b * np.sin(phi) + a + d) / 2 expected = np.array([ev1, ev2]) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_single_wire_expectation(self, gate, obs, expected, theta, phi, varphi, tol): """Test that identity expectation value (i.e. the trace) is 1""" dev = DefaultQubitTF(wires=2) queue = [gate(theta, wires=0), gate(phi, wires=1), qml.CNOT(wires=[0, 1])] observables = [obs(wires=[i]) for i in range(2)] for i in range(len(observables)): observables[i].return_type = qml.operation.Expectation res = dev.execute(qml.CircuitGraph(queue + observables, {}, Wires([0, 1, 2]))) assert np.allclose(res, expected(theta, phi), atol=tol, rtol=0)
def test_var(self, theta, phi, varphi, tol): """Tests for variance calculation""" dev = DefaultQubitTF(wires=1) # test correct variance for <Z> of a rotated state queue = [qml.RX(phi, wires=0), qml.RY(theta, wires=0)] observables = [qml.PauliZ(wires=[0])] for i in range(len(observables)): observables[i].return_type = qml.operation.Variance res = dev.execute(qml.CircuitGraph(queue + observables, {}, Wires([0]))) expected = 0.25 * (3 - np.cos(2 * theta) - 2 * np.cos(theta) ** 2 * np.cos(2 * phi)) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_sample_values_hermitian_multi_qubit(self, tol): """Tests if the samples of a multi-qubit Hermitian observable returned by sample have the correct values """ shots = 1_000_000 dev = plf.NumpyWavefunctionDevice(wires=2, shots=shots) theta = 0.543 A = np.array([ [1, 2j, 1 - 2j, 0.5j], [-2j, 0, 3 + 4j, 1], [1 + 2j, 3 - 4j, 0.75, 1.5 - 2j], [-0.5j, 1, 1.5 + 2j, -1], ]) circuit_operations = [ qml.RX(theta, wires=[0]), qml.RY(2 * theta, wires=[1]), qml.CNOT(wires=[0, 1]), ] O = qml.sample(qml.Hermitian(A, wires=[0, 1])) observables = [O] circuit_graph = qml.CircuitGraph(circuit_operations + observables, {}, dev.wires) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O) # s1 should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), atol=tol, rtol=0) # make sure the mean matches the analytic mean expected = (88 * np.sin(theta) + 24 * np.sin(2 * theta) - 40 * np.sin(3 * theta) + 5 * np.cos(theta) - 6 * np.cos(2 * theta) + 27 * np.cos(3 * theta) + 6) / 32 assert np.allclose(np.mean(s1), expected, atol=0.1, rtol=0)
def graph(self): """Returns a directed acyclic graph representation of the recorded quantum circuit: >>> tape.graph <pennylane.circuit_graph.CircuitGraph object at 0x7fcc0433a690> Note that the circuit graph is only constructed once, on first call to this property, and cached for future use. Returns: .CircuitGraph: the circuit graph object """ if self._graph is None: self._graph = qml.CircuitGraph(self.operations, self.observables, self.wires, self._par_info, self.trainable_params) return self._graph
def test_sample_values_hermitian(self, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ dev = plf.NumpyWavefunctionDevice(wires=1, shots=1_000_000) theta = 0.543 A = np.array([[1, 2j], [-2j, 0]]) circuit_operations = [qml.RX(theta, wires=[0])] O = qml.sample(qml.Hermitian(A, wires=[0])) observables = [O] circuit_graph = qml.CircuitGraph(circuit_operations + observables, {}, dev.wires) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O) # s1 should only contain the eigenvalues of # the hermitian matrix eigvals = np.linalg.eigvalsh(A) assert np.allclose(sorted(list(set(s1))), sorted(eigvals), atol=tol, rtol=0) # the analytic mean is 2*sin(theta)+0.5*cos(theta)+0.5 assert np.allclose(np.mean(s1), 2 * np.sin(theta) + 0.5 * np.cos(theta) + 0.5, atol=0.1, rtol=0) # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose(np.var(s1), 0.25 * (np.sin(theta) - 4 * np.cos(theta))**2, atol=0.1, rtol=0)
def test_var_hermitian(self, theta, phi, varphi, tol): """Tests for variance calculation using an arbitrary Hermitian observable""" dev = DefaultQubitTF(wires=2) # test correct variance for <H> of a rotated state H = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) queue = [qml.RX(phi, wires=0), qml.RY(theta, wires=0)] observables = [qml.Hermitian(H, wires=[0])] for i in range(len(observables)): observables[i].return_type = qml.operation.Variance res = dev.execute(qml.CircuitGraph(queue + observables, {})) expected = 0.5 * (2 * np.sin(2 * theta) * np.cos(phi)**2 + 24 * np.sin(phi) * np.cos(phi) * (np.sin(theta) - np.cos(theta)) + 35 * np.cos(2 * phi) + 39) assert np.allclose(res, expected, atol=tol, rtol=0)
def test_var(self, tol, qvm): """Tests for variance calculation""" dev = plf.WavefunctionDevice(wires=2) phi = 0.543 theta = 0.6543 circuit_operations = [qml.RX(phi, wires=[0]), qml.RY(theta, wires=[0])] O = qml.var(qml.PauliZ(wires=[0])) observables = [O] circuit_graph = qml.CircuitGraph(circuit_operations + observables, {}) # test correct variance for <Z> of a rotated state dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) var = dev.var(qml.PauliZ(0)) expected = 0.25 * (3 - np.cos(2 * theta) - 2 * np.cos(theta)**2 * np.cos(2 * phi)) self.assertAlmostEqual(var, expected, delta=tol)
def metric_tensor(self, args, kwargs=None, *, diag_approx=False, only_construct=False): """Evaluate the value of the metric tensor. Args: args (tuple[Any]): positional (differentiable) arguments kwargs (dict[str, Any]): auxiliary arguments diag_approx (bool): iff True, use the diagonal approximation only_construct (bool): Iff True, construct the circuits used for computing the metric tensor but do not execute them, and return None. Returns: array[float]: metric tensor """ # pylint:disable=too-many-branches kwargs = kwargs or {} kwargs = self._default_args(kwargs) if self.circuit is None or self.mutable: # construct the circuit self._construct(args, kwargs) if self._metric_tensor_subcircuits is None: self._construct_metric_tensor(diag_approx=diag_approx) if only_construct: return None # temporarily store the parameter values in the Variable class self._set_variables(args, kwargs) tensor = np.zeros([self.num_variables, self.num_variables]) # execute constructed metric tensor subcircuits for params, circuit in self._metric_tensor_subcircuits.items(): self.device.reset() s = np.array(circuit["scale"]) V = circuit["eigenbasis_matrix"] if not diag_approx: # block diagonal approximation unitary_op = qml.QubitUnitary(V, wires=list(range( self.num_wires)), do_queue=False) if isinstance(self.device, qml.QubitDevice): ops = circuit["queue"] + [unitary_op ] + [qml.expval(qml.PauliZ(0))] circuit_graph = qml.CircuitGraph(ops, self.variable_deps) self.device.execute(circuit_graph) else: self.device.execute( circuit["queue"] + [unitary_op], [ qml.expval(qml.PauliZ(wire)) for wire in list(range(self.device.num_wires)) ], ) probs = list(self.device.probability()) first_order_ev = np.zeros([len(params)]) second_order_ev = np.zeros([len(params), len(params)]) for idx, ev in circuit["Ki_expectations"]: first_order_ev[idx] = ev @ probs for idx, ev in circuit["KiKj_expectations"]: # idx is a 2-tuple (i, j), representing # generators K_i, K_j second_order_ev[idx] = ev @ probs # since K_i and K_j are assumed to commute, # <psi|K_j K_i|psi> = <psi|K_i K_j|psi>, # and thus the matrix of second-order expectations # is symmetric second_order_ev[idx[1], idx[0]] = second_order_ev[idx] g = np.zeros([len(params), len(params)]) for i, j in itertools.product(range(len(params)), repeat=2): g[i, j] = (s[i] * s[j] * (second_order_ev[i, j] - first_order_ev[i] * first_order_ev[j])) row = np.array(params).reshape(-1, 1) col = np.array(params).reshape(1, -1) circuit["result"] = np.diag(g) tensor[row, col] = g else: # diagonal approximation if isinstance(self.device, qml.QubitDevice): circuit_graph = qml.CircuitGraph( circuit["queue"] + circuit["observable"], self.variable_deps) variances = self.device.execute(circuit_graph) else: variances = self.device.execute(circuit["queue"], circuit["observable"]) circuit["result"] = s**2 * variances tensor[np.array(params), np.array(params)] = circuit["result"] return tensor