def test_invalid_decompose(self, operable_mock_device_2_wires): """Test that an error is raised if the device does not support an operation arising from a decomposition.""" class DummyOp(qml.operation.Operation): """Dummy operation""" num_params = 0 num_wires = 1 par_domain = "R" grad_method = "A" @staticmethod def decomposition(wires=None): ops = [qml.Hadamard(wires=wires)] return ops queue = [ qml.Rot(0, 1, 2, wires=0), DummyOp(wires=0), qml.RX(6, wires=0) ] with pytest.raises(qml.DeviceError, match="DummyOp not supported on device"): decompose_queue(queue, operable_mock_device_2_wires)
def test_decompose_queue(self, operable_mock_device_2_wires): """Test that decompose queue works correctly when an operation exists that can be decomposed""" queue = [ qml.Rot(0, 1, 2, wires=0), qml.U3(3, 4, 5, wires=0), qml.RX(6, wires=0) ] res = decompose_queue(queue, operable_mock_device_2_wires) assert len(res) == 5 assert res[0].name == "Rot" assert res[0].parameters == [0, 1, 2] assert res[1].name == "Rot" assert res[1].parameters == [5, 3, -5] assert res[2].name == "PhaseShift" assert res[2].parameters == [5] assert res[3].name == "PhaseShift" assert res[3].parameters == [4] assert res[4].name == "RX" assert res[4].parameters == [6]
def test_decompose_queue_recursive(self, operable_mock_device_2_wires_with_inverses): """Test that decompose queue works correctly when an operation exists that can be decomposed""" queue = [qml.CRY(1, wires=[0, 1]), qml.U3(3, 4, 5, wires=0)] res = decompose_queue(queue, operable_mock_device_2_wires_with_inverses) assert len(res) == 9 assert res[0].name == "RY" assert res[0].parameters == [0.5] assert res[1].name == "CNOT" assert res[2].name == "RY" assert res[2].parameters == [-0.5] assert res[3].name == "CNOT" assert res[4].name == "RZ" assert res[4].parameters == [5] assert res[5].name == "RY" assert res[5].parameters == [3] assert res[6].name == "RZ" assert res[6].parameters == [-5] assert res[7].name == "PhaseShift" assert res[7].parameters == [5] assert res[8].name == "PhaseShift" assert res[8].parameters == [4]
def test_no_decomposition(self, operable_mock_device_2_wires): """Test that decompose queue makes no changes if there are no operations to be decomposed""" queue = [qml.Rot(0, 1, 2, wires=0), qml.CNOT(wires=[0, 1]), qml.RX(6, wires=0)] res = decompose_queue(queue, operable_mock_device_2_wires) assert res == queue
def test_decompose_queue_inv(self, operable_mock_device_2_wires_with_inverses): """Test that decompose queue works correctly when an operation exists that can be decomposed""" queue = [ qml.Rot(0, 1, 2, wires=0).inv(), qml.U3(3, 4, 5, wires=0).inv(), qml.RX(6, wires=0).inv(), ] res = decompose_queue(queue, operable_mock_device_2_wires_with_inverses) assert len(res) == 9 assert res[0].name == "RZ.inv" assert res[0].parameters == [2] assert res[1].name == "RY.inv" assert res[1].parameters == [1] assert res[2].name == "RZ.inv" assert res[2].parameters == [0] assert res[3].name == "PhaseShift.inv" assert res[3].parameters == [4] assert res[4].name == "PhaseShift.inv" assert res[4].parameters == [5] assert res[5].name == "RZ.inv" assert res[5].parameters == [-5] assert res[6].name == "RY.inv" assert res[6].parameters == [3] assert res[7].name == "RZ.inv" assert res[7].parameters == [5] assert res[8].name == "RX.inv" assert res[8].parameters == [6]
def to_openqasm(self, rotations=True): """Serialize the circuit as an OpenQASM 2.0 program. Only operations are serialized; all measurements are assumed to take place in the computational basis. .. note:: The serialized OpenQASM program assumes that gate definitions in ``qelib1.inc`` are available. Args: rotations (bool): in addition to serializing user-specified operations, also include the gates that diagonalize the measured wires such that they are in the eigenbasis of the circuit observables. Returns: str: OpenQASM serialization of the circuit """ # We import decompose_queue here to avoid a circular import from pennylane.qnodes.base import decompose_queue # pylint: disable=import-outside-toplevel class QASMSerializerDevice: """A mock device, to be used when performing the decomposition. The short_name is used in error messages if the decomposition fails. """ # pylint: disable=too-few-public-methods short_name = "QASM serializer" supports_operation = staticmethod(lambda x: x in OPENQASM_GATES) # add the QASM headers qasm_str = "OPENQASM 2.0;\n" qasm_str += 'include "qelib1.inc";\n' if self.num_wires == 0: # empty circuit return qasm_str # create the quantum and classical registers qasm_str += "qreg q[{}];\n".format(self.num_wires) qasm_str += "creg c[{}];\n".format(self.num_wires) # get the user applied circuit operations operations = self.operations if rotations: # if requested, append diagonalizing gates corresponding # to circuit observables operations += self.diagonalizing_gates # decompose the queue decomposed_ops = decompose_queue(operations, QASMSerializerDevice) # create the QASM code representing the operations for op in decomposed_ops: gate = OPENQASM_GATES[op.name] wires = ",".join(["q[{}]".format(w) for w in op.wires]) params = "" if op.num_params > 0: # If the operation takes parameters, construct a string # with parameter values. params = "(" + ",".join([str(p) for p in op.parameters]) + ")" qasm_str += "{name}{params} {wires};\n".format(name=gate, params=params, wires=wires) # apply computational basis measurements to each quantum register # NOTE: This is not strictly necessary, we could inspect self.observables, # and then only measure wires which are requested by the user. However, # some devices which consume QASM require all registers be measured, so # measure all wires to be safe. for wire in range(self.num_wires): qasm_str += "measure q[{wire}] -> c[{wire}];\n".format(wire=wire) return qasm_str