def test_grid_when_sample_no_wires(self): """A test to ensure the sample operation applies to all wires when none are explicitly provided.""" ops = [qml.Hadamard(wires=0), qml.CNOT(wires=[0, 1])] obs_no_wires = [qml.measure.sample(op=None, wires=None)] obs_w_wires = [qml.measure.sample(op=None, wires=[0, 1, 2])] circuit_no_wires = CircuitGraph(ops, obs_no_wires, wires=Wires([0, 1, 2])) circuit_w_wires = CircuitGraph(ops, obs_w_wires, wires=Wires([0, 1, 2])) sample_w_wires_op = qml.measure.sample(op=None, wires=[0, 1, 2]) expected_grid = { 0: [ops[0], ops[1], sample_w_wires_op], 1: [ops[1], sample_w_wires_op], 2: [sample_w_wires_op], } for key in range(3): lst_w_wires = circuit_w_wires._grid[key] lst_no_wires = circuit_no_wires._grid[key] lst_expected = expected_grid[key] assert lst_mp_equality(lst_no_wires, lst_w_wires) assert lst_mp_equality(lst_no_wires, lst_expected)
def test_update_node(self, ops): """Changing nodes in the graph.""" circuit = CircuitGraph(ops, {}) new = qml.RX(0.1, wires=0) circuit.update_node(ops[0], new) assert circuit.operations[0] is new
def test_apply(self, gate, apply_unitary, shots): """Test the application of gates""" dev = plf.QVMDevice(device="3q-pyqvm", shots=shots) 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 = CircuitGraph([op(p, wires=w)] + [obs], {}) else: p = [0.432423, 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 = CircuitGraph([op(*p, wires=w)] + [obs], {}) # Creating the circuit graph using an operation that take no parameters else: circuit_graph = CircuitGraph([op(wires=w)] + [obs], {}) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() res = dev.expval(obs) expected = np.vdot(state, np.kron(np.kron(Z, I), I) @ state) # verify the device is now in the expected state # Note we have increased the tolerance here, since we are only # performing 1024 shots. self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))
def test_is_sampled(self): """Test that circuit graphs with sampled observables properly return True for CircuitGraph.is_sampled""" circuit = CircuitGraph([qml.expval(qml.PauliX(0)), qml.var(qml.PauliZ(1))], {}) assert not circuit.is_sampled circuit = CircuitGraph([qml.expval(qml.PauliX(0)), qml.sample(qml.PauliZ(1))], {}) assert circuit.is_sampled
def test_no_dependence(self): """Test case where operations do not depend on each other. This should result in a graph with no edges.""" ops = [qml.RX(0.43, wires=0), qml.RY(0.35, wires=1)] res = CircuitGraph(ops, {}).graph assert len(res) == 2 assert not res.edges()
def test_no_dependence(self): """Test case where operations do not depend on each other. This should result in a graph with no edges.""" queue = [qml.RX(0.43, wires=0, do_queue=False), qml.RY(0.35, wires=1, do_queue=False)] obs = [] res = CircuitGraph(queue, obs).graph assert len(res) == 2 assert not res.edges()
def test_ancestors_and_descendants_example(self, ops): """ Test that the ``ancestors`` and ``descendants`` methods return the expected result. """ circuit = CircuitGraph(ops, {}) ancestors = circuit.ancestors([ops[6]]) assert len(ancestors) == 3 for o_idx in (0, 1, 3): assert ops[o_idx] in ancestors descendants = circuit.descendants([ops[6]]) assert descendants == set([ops[8]])
def test_ancestors_and_descendants_example(self, ops, obs): """ Test that the ``ancestors`` and ``descendants`` methods return the expected result. """ circuit = CircuitGraph(ops, obs, Wires([0, 1, 2])) queue = ops + obs ancestors = circuit.ancestors([queue[6]]) assert len(ancestors) == 3 for o_idx in (0, 1, 3): assert queue[o_idx] in ancestors descendants = circuit.descendants([queue[6]]) assert descendants == set([queue[8]])
def test_get_nodes_example(self, queue, obs): """ Given a sample circuit, test that the `get_nodes` method returns the expected result. """ circuit = CircuitGraph(queue, obs) o_idxs = [0, 1, 2, 3, 4, 5, 6, 7, 8] nodes = circuit.get_nodes(o_idxs) assert nodes[0]["op"] == queue[0] assert nodes[1]["op"] == queue[1] assert nodes[2]["op"] == queue[2] assert nodes[3]["op"] == queue[3] assert nodes[4]["op"] == queue[4] assert nodes[5]["op"] == queue[5] assert nodes[6]["op"] == queue[6] assert nodes[7]["op"] == obs[0] assert nodes[8]["op"] == obs[1] assert nodes[0]["idx"] == 0 assert nodes[1]["idx"] == 1 assert nodes[2]["idx"] == 2 assert nodes[3]["idx"] == 3 assert nodes[4]["idx"] == 4 assert nodes[5]["idx"] == 5 assert nodes[6]["idx"] == 6 assert nodes[7]["idx"] == 7 assert nodes[8]["idx"] == 8 assert nodes[0]["name"] == "RX" assert nodes[1]["name"] == "RY" assert nodes[2]["name"] == "RZ" assert nodes[3]["name"] == "CNOT" assert nodes[4]["name"] == "Hadamard" assert nodes[5]["name"] == "CNOT" assert nodes[6]["name"] == "PauliX" assert nodes[7]["name"] == "PauliX" assert nodes[8]["name"] == "Hermitian" assert nodes[0]["return_type"] is None assert nodes[1]["return_type"] is None assert nodes[2]["return_type"] is None assert nodes[3]["return_type"] is None assert nodes[4]["return_type"] is None assert nodes[5]["return_type"] is None assert nodes[6]["return_type"] is None assert nodes[7]["return_type"] == Expectation assert nodes[8]["return_type"] == Expectation
def test_dependence(self, ops, obs): """Test a more complex example containing operations that do depend on the result of previous operations""" circuit = CircuitGraph(ops, obs, Wires([0, 1, 2])) graph = circuit.graph assert len(graph.node_indexes()) == 9 assert len(graph.edges()) == 9 queue = ops + obs # all ops should be nodes in the graph for k in queue: assert k in graph.nodes() # all nodes in the graph should be ops # for k in graph.nodes: for k in graph.nodes(): assert k is queue[k.queue_idx] a = set((graph.get_node_data(e[0]), graph.get_node_data(e[1])) for e in graph.edge_list()) b = set( (queue[a], queue[b]) for a, b in [ (0, 3), (1, 3), (2, 4), (3, 5), (3, 6), (4, 5), (5, 7), (5, 8), (6, 8), ] ) assert a == b
def test_circuit_hash_none_no_compiled_program_was_stored_in_dict( self, qvm, monkeypatch): """Test that QVM device does not store the compiled program in a dictionary if the _circuit_hash attribute is None""" dev = qml.device("forest.qvm", device="2q-qvm") theta = 0.432 phi = 0.123 O1 = qml.expval(qml.Identity(wires=[0])) O2 = qml.expval(qml.Identity(wires=[1])) circuit_graph = CircuitGraph([ qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ], [O1, O2]) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._circuit_hash = None call_history = [] with monkeypatch.context() as m: m.setattr(QuantumComputer, "compile", lambda self, prog: call_history.append(prog)) m.setattr(QuantumComputer, "run", lambda self, **kwargs: None) dev.generate_samples() assert dev.circuit_hash is None assert len(dev._compiled_program_dict.items()) == 0 assert len(call_history) == 1
def test_hadamard_expectation(self, shots, qvm, compiler): """Test that Hadamard expectation value is correct""" theta = 0.432 phi = 0.123 dev = plf.QVMDevice(device="2q-qvm", shots=shots) O1 = qml.expval(qml.Hadamard(wires=[0])) O2 = qml.expval(qml.Hadamard(wires=[1])) circuit_graph = CircuitGraph( [ qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ] + [O1, O2], {}, ) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() res = np.array([dev.expval(O1), dev.expval(O2)]) # below are the analytic expectation values for this circuit expected = np.array([ np.sin(theta) * np.sin(phi) + np.cos(theta), np.cos(theta) * np.cos(phi) + np.sin(phi) ]) / np.sqrt(2) self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))
def test_op_queue_is_filled_during_execution( self, mock_qubit_device_with_paulis_and_methods, monkeypatch ): """Tests that the op_queue is correctly filled when apply is called and that accessing op_queue raises no error""" queue = [qml.PauliX(wires=0), qml.PauliY(wires=1), qml.PauliZ(wires=2)] observables = [qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2))] circuit_graph = CircuitGraph(queue + observables, {}) call_history = [] with monkeypatch.context() as m: m.setattr(QubitDevice, "apply", lambda self, x, **kwargs: call_history.extend(x + kwargs.get('rotations', []))) mock_qubit_device_with_paulis_and_methods.execute(circuit_graph) assert call_history == queue assert len(call_history) == 3 assert isinstance(call_history[0], qml.PauliX) assert call_history[0].wires == [0] assert isinstance(call_history[1], qml.PauliY) assert call_history[1].wires == [1] assert isinstance(call_history[2], qml.PauliZ) assert call_history[2].wires == [2]
def test_paulix_expectation(self, shots): """Test that PauliX expectation value is correct""" theta = 0.432 phi = 0.123 dev = plf.QVMDevice(device="2q-pyqvm", shots=shots) O1 = qml.expval(qml.PauliX(wires=[0])) O2 = qml.expval(qml.PauliX(wires=[1])) circuit_graph = CircuitGraph([ qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ] + [O1, O2], {}) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() res = np.array([dev.expval(O1), dev.expval(O2)]) # below are the analytic expectation values for this circuit self.assertAllAlmostEqual( res, np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), delta=3 / np.sqrt(shots))
def test_sample_values_hermitian(self, qvm, tol): """Tests if the samples of a Hermitian observable returned by sample have the correct values """ theta = 0.543 shots = 1_000_000 A = np.array([[1, 2j], [-2j, 0]]) dev = plf.QVMDevice(device="1q-qvm", shots=shots) O1 = qml.sample(qml.Hermitian(A, wires=[0])) circuit_graph = CircuitGraph([qml.RX(theta, wires=[0])] + [O1], {}, dev.wires) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev._samples = dev.generate_samples() s1 = dev.sample(O1) # 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, shots): """Tests for variance calculation using an arbitrary Hermitian observable""" dev = plf.QVMDevice(device="2q-pyqvm", shots=100 * shots) phi = 0.543 theta = 0.6543 # test correct variance for <A> of a rotated state A = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) O1 = qml.var(qml.Hermitian(A, wires=[0])) circuit_graph = CircuitGraph([ qml.RX(phi, wires=[0]), qml.RY(theta, wires=[0]), ] + [O1], {}) # test correct variance for <A> of a rotated state dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() var = np.array([dev.var(O1)]) 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=0.3)
def test_identity_expectation(self, shots): """Test that identity expectation value (i.e. the trace) is 1""" theta = 0.432 phi = 0.123 dev = plf.QVMDevice(device="2q-pyqvm", shots=shots) O1 = qml.expval(qml.Identity(wires=[0])) O2 = qml.expval(qml.Identity(wires=[1])) circuit_graph = CircuitGraph([ qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ] + [O1, O2], {}) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() res = np.array([dev.expval(O1), dev.expval(O2)]) # below are the analytic expectation values for this circuit (trace should always be 1) self.assertAllAlmostEqual(res, np.array([1, 1]), delta=3 / np.sqrt(shots))
def test_multi_qubit_hermitian_expectation(self, shots, qvm, compiler): """Test that arbitrary multi-qubit Hermitian expectation values are correct""" theta = np.random.random() phi = np.random.random() 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 = plf.QVMDevice(device="2q-pyqvm", shots=10 * shots) O1 = qml.expval(qml.Hermitian(A, wires=[0, 1])) circuit_graph = CircuitGraph([ qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ] + [O1], {}) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() res = np.array([dev.expval(O1)]) # 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) self.assertAllAlmostEqual(res, expected, delta=6 / np.sqrt(shots))
def test_var(self, shots): """Tests for variance calculation""" dev = plf.QVMDevice(device="2q-pyqvm", shots=shots) phi = 0.543 theta = 0.6543 O1 = qml.var(qml.PauliZ(wires=[0])) circuit_graph = CircuitGraph([ qml.RX(phi, wires=[0]), qml.RY(theta, wires=[0]), ] + [O1], {}) # test correct variance for <Z> of a rotated state dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() var = np.array([dev.var(O1)]) expected = 0.25 * (3 - np.cos(2 * theta) - 2 * np.cos(theta)**2 * np.cos(2 * phi)) self.assertAlmostEqual(var, expected, delta=3 / np.sqrt(shots))
def evaluate_obs(self, obs, args, kwargs): """Evaluate the value of the given observables. Assumes :meth:`construct` has already been called. Args: obs (Iterable[Observable]): observables to measure args (tuple[Any]): positional arguments to the quantum function (differentiable) kwargs (dict[str, Any]): auxiliary arguments (not differentiable) Returns: array[float]: measured values """ kwargs = self._default_args(kwargs) self._set_variables(args, kwargs) self.device.reset() if isinstance(self.device, qml.QubitDevice): # create a circuit graph containing the existing operations, and the # observables to be evaluated. circuit_graph = CircuitGraph(self.circuit.operations + list(obs), self.circuit.variable_deps) ret = self.device.execute(circuit_graph) else: ret = self.device.execute(self.circuit.operations, obs, self.circuit.variable_deps) return ret
def test_hermitian_expectation(self, shots): """Test that arbitrary Hermitian expectation values are correct""" theta = 0.432 phi = 0.123 dev = plf.QVMDevice(device="2q-pyqvm", shots=5 * shots) O1 = qml.expval(qml.Hermitian(H, wires=[0])) O2 = qml.expval(qml.Hermitian(H, wires=[1])) circuit_graph = CircuitGraph([ qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1]) ] + [O1, O2], {}) dev.apply(circuit_graph.operations, rotations=circuit_graph.diagonalizing_gates) dev.generate_samples() res = np.array([dev.expval(O1), dev.expval(O2)]) # below are the analytic expectation values for this circuit with arbitrary # Hermitian observable H a = H[0, 0] re_b = H[0, 1].real d = H[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]) self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots))
def test_dependence(self, ops): """Test a more complex example containing operations that do depend on the result of previous operations""" circuit = CircuitGraph(ops, {}) graph = circuit.graph assert len(graph) == 9 assert len(graph.edges()) == 9 # all ops should be nodes in the graph for k in ops: assert k in graph.nodes # all nodes in the graph should be ops for k in graph.nodes: assert k is ops[k.queue_idx] # Finally, checking the adjacency of the returned DAG: assert set(graph.edges()) == set((ops[a], ops[b]) for a, b in [ (0, 3), (1, 3), (2, 4), (3, 5), (3, 6), (4, 5), (5, 7), (5, 8), (6, 8), ])
def test_diagonalizing_gates(self): """Tests that the diagonalizing gates are correct for a circuit""" circuit = CircuitGraph([qml.expval(qml.PauliX(0)), qml.var(qml.PauliZ(1))], {}) diag_gates = circuit.diagonalizing_gates assert len(diag_gates) == 1 assert isinstance(diag_gates[0], qml.Hadamard) assert diag_gates[0].wires == [0]
def test_serialize_numeric_arguments(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have numeric arguments.""" circuit_graph_1 = CircuitGraph(queue, observable_queue, Wires([0, 1, 2])) circuit_graph_2 = CircuitGraph(queue, observable_queue, Wires([0, 1, 2])) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize()
def test_serialize_symbolic_argument(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have symbolic arguments.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize()
def test_serialize_numeric_arguments_observables(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have identical queues and empty variable_deps.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize()
def test_serialize_symbolic_argument_multiple_times(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have the same symbolic argument used multiple times.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}, Wires([0, 1])) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}, Wires([0, 1])) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize()
def test_unsupported_operations_raise_error(self, mock_qubit_device_with_paulis_and_methods): """Tests that the operations are properly applied and queued""" queue = [qml.PauliX(wires=0), qml.PauliY(wires=1), qml.Hadamard(wires=2)] observables = [qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2))] circuit_graph = CircuitGraph(queue + observables, {}) with pytest.raises(DeviceError, match="Gate Hadamard not supported on device"): mock_qubit_device_with_paulis_and_methods.execute(circuit_graph)
def test_in_topological_order_example(self, ops, obs): """ Test ``_in_topological_order`` method returns the expected result. """ circuit = CircuitGraph(ops, obs, Wires([0, 1, 2])) to = circuit._in_topological_order(ops) to_expected = [ qml.RZ(0.35, wires=[2]), qml.Hadamard(wires=[2]), qml.RY(0.35, wires=[1]), qml.RX(0.43, wires=[0]), qml.CNOT(wires=[0, 1]), qml.PauliX(wires=[1]), qml.CNOT(wires=[2, 0]), ] assert str(to) == str(to_expected)
def test_passing_keyword_arguments_to_execute(self, mock_qubit_device_with_paulis_rotations_and_methods, monkeypatch, queue, observables): """Tests that passing keyword arguments to execute propagates those kwargs to the apply() method""" circuit_graph = CircuitGraph(queue + observables, {}) call_history = {} with monkeypatch.context() as m: m.setattr(QubitDevice, "apply", lambda self, x, **kwargs: call_history.update(kwargs)) mock_qubit_device_with_paulis_rotations_and_methods.execute(circuit_graph, hash=circuit_graph.hash) len(call_history.items()) == 1 call_history["hash"] = circuit_graph.hash