diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index d2b9b2ed5..582969e08 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -184,6 +184,11 @@ def _function(params: dict = None, wires: list = None): operation_wires = [wire_map[(qubit.register.name, qubit.index)] for qubit in op[1]] + # New Qiskit gates that are not natively supported by PL (identical + # gates exist with a different name) + # TODO: remove the following when gates have been renamed in PennyLane + instruction_name = "U3Gate" if instruction_name == "UGate" else instruction_name + if instruction_name in inv_map and inv_map[instruction_name] in pennylane_ops.ops: # Extract the bound parameters from the operation. If the bound parameters are a # Qiskit ParameterExpression, then replace it with the corresponding PennyLane diff --git a/pennylane_qiskit/qiskit_device.py b/pennylane_qiskit/qiskit_device.py index 8d7a4e955..c7a254b1d 100644 --- a/pennylane_qiskit/qiskit_device.py +++ b/pennylane_qiskit/qiskit_device.py @@ -33,6 +33,25 @@ from ._version import __version__ +# Auxiliary functions for gates subject to deprecation +def U1Gate(theta): + """Auxiliary function for the ``U1Gate``.""" + return ex.PhaseGate(theta) + + +def U2Gate(phi, lam): + """Auxiliary function for the ``U2Gate``. + + Uses the equation ``u2(phi, lam) = u(pi/2, phi, lam)``. + """ + return ex.U(np.pi / 2, phi, lam) + + +def U3Gate(theta, phi, lam): + """Auxiliary function for the ``U3Gate``.""" + return ex.U(theta, phi, lam) + + QISKIT_OPERATION_MAP = { # native PennyLane operations also native to qiskit "PauliX": ex.XGate, @@ -52,12 +71,16 @@ "CRX": ex.CRXGate, "CRY": ex.CRYGate, "CRZ": ex.CRZGate, - "PhaseShift": ex.U1Gate, + "PhaseShift": ex.PhaseGate, "QubitStateVector": ex.Initialize, - "U2": ex.U2Gate, - "U3": ex.U3Gate, "Toffoli": ex.CCXGate, "QubitUnitary": ex.UnitaryGate, + "U": ex.UGate, + # Qiskit gates subject to deprecation (using custom definitions that depend on + # the latest recommended gates) + "U1": U1Gate, + "U2": U2Gate, + "U3": U3Gate, } # Separate dictionary for the inverses as the operations dictionary needs @@ -147,22 +170,28 @@ def __init__(self, wires, provider, backend, shots=1024, **kwargs): # Initialize inner state self.reset() - # determine if backend supports backend options and noise models, - # and properly put together backend run arguments - s = inspect.signature(b.run) - self.run_args = {} self.compile_backend = None - if "compile_backend" in kwargs: self.compile_backend = kwargs.pop("compile_backend") + aer_provider = str(provider) == "AerProvider" + self.noise_model = None if "noise_model" in kwargs: - if "noise_model" in s.parameters: - self.run_args["noise_model"] = kwargs.pop("noise_model") - else: + if not aer_provider or backend != "qasm_simulator": raise ValueError("Backend {} does not support noisy simulations".format(backend)) - if "backend_options" in s.parameters: + self.noise_model = kwargs.pop("noise_model") + + # Get further arguments for run + s = inspect.signature(b.run) + self.run_args = {} + + if aer_provider: + # Consider the remaining kwargs as keyword arguments to run + self.run_args.update(kwargs) + + elif "backend_options" in s.parameters: + # BasicAer self.run_args["backend_options"] = kwargs @property @@ -177,6 +206,7 @@ def reset(self): self._current_job = None self._state = None # statevector of a simulator backend + self.noise_model = None def apply(self, operations, **kwargs): rotations = kwargs.get("rotations", []) @@ -276,14 +306,17 @@ def compile(self): memory = str(compile_backend) not in self._state_backends return assemble( - experiments=compiled_circuits, - backend=compile_backend, - shots=self.shots, - memory=memory, + experiments=compiled_circuits, backend=compile_backend, shots=self.shots, memory=memory ) def run(self, qobj): """Run the compiled circuit, and query the result.""" + backend = self.backend + + if self.noise_model: + # Set the noise model before execution + backend.set_options(noise_model=self.noise_model) + self._current_job = self.backend.run(qobj, **self.run_args) result = self._current_job.result() diff --git a/requirements.txt b/requirements.txt index 186253761..58a9a01cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=0.20 +qiskit>=0.23 pennylane>=0.11.0 numpy networkx>=2.2;python_version>'3.5' diff --git a/setup.py b/setup.py index 6a2d5e886..b4d9403c0 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ long_description = fh.read() requirements = [ - "qiskit>=0.20", + "qiskit>=0.23", "pennylane>=0.11.0", "numpy", "networkx>=2.2;python_version>'3.5'", diff --git a/tests/test_converter.py b/tests/test_converter.py index c8d5a620f..bed764b13 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -132,7 +132,7 @@ def test_quantum_circuit_with_gate_requiring_multiple_parameters(self, recorder) theta = Parameter('θ') qc = QuantumCircuit(3, 1) - qc.u3(phi, lam, theta, [0]) + qc.u(phi, lam, theta, [0]) quantum_circuit = load(qc) @@ -333,12 +333,11 @@ def test_one_qubit_parametrized_operations_supported_by_pennylane(self, recorder q_reg = QuantumRegister(1) qc = QuantumCircuit(q_reg) - qc.u1(angle, single_wire) + qc.p(angle, single_wire) qc.rx(angle, single_wire) qc.ry(angle, single_wire) qc.rz(angle, single_wire) - qc.u2(phi, lam, [0]) - qc.u3(phi, lam, theta, [0]) + qc.u(phi, lam, theta, [0]) quantum_circuit = load(qc) with recorder: @@ -360,16 +359,11 @@ def test_one_qubit_parametrized_operations_supported_by_pennylane(self, recorder assert recorder.queue[3].parameters == [angle] assert recorder.queue[3].wires == Wires(single_wire) - assert recorder.queue[4].name == 'U2' - assert len(recorder.queue[4].parameters) == 2 - assert recorder.queue[4].parameters == [0.3, 0.4] + assert recorder.queue[4].name == 'U3' + assert len(recorder.queue[4].parameters) == 3 + assert recorder.queue[4].parameters == [0.3, 0.4, 0.2] assert recorder.queue[4].wires == Wires([0]) - assert recorder.queue[5].name == 'U3' - assert len(recorder.queue[5].parameters) == 3 - assert recorder.queue[5].parameters == [0.3, 0.4, 0.2] - assert recorder.queue[5].wires == Wires([0]) - def test_two_qubit_operations_supported_by_pennylane(self, recorder): """Tests loading a circuit with the two-qubit operations supported by PennyLane.""" @@ -551,36 +545,60 @@ def test_operation_transformed_into_qubit_unitary(self, recorder): assert np.array_equal(recorder.queue[0].parameters[0], ex.CHGate().to_matrix()) assert recorder.queue[0].wires == Wires([0, 1]) - def test_quantum_circuit_error_by_passing_wrong_parameters(self, recorder): - """Tests the load method for a QuantumCircuit raises a QiskitError, - if the wrong type of arguments were passed.""" + def test_qiskit_gates_to_be_deprecated(self, recorder): + """Tests the Qiskit gates that will be deprecated in an upcoming Qiskit version. + + This test case can be removed once the gates are finally deprecated. + """ + qc = QuantumCircuit(1, 1) - theta = Parameter('θ') - angle = 'some_string_instead_of_an_angle' + single_wire = [0] - qc = QuantumCircuit(3, 1) - qc.rz(theta, [0]) + with pytest.warns(DeprecationWarning) as record: + with recorder: + qc.u1(0.1, single_wire) + qc.u2(0.1, 0.2, single_wire) + qc.u3(0.1, 0.2, 0.3, single_wire) + + # check that warnings were raised + assert len(record) == 3 + # check that the message matches + deprecation_substring = "method is deprecated" + assert deprecation_substring in record[0].message.args[0] + assert deprecation_substring in record[1].message.args[0] + assert deprecation_substring in record[2].message.args[0] quantum_circuit = load(qc) + with recorder: + quantum_circuit() - with pytest.raises(QiskitError): - with recorder: - quantum_circuit(params={theta: angle}) + assert recorder.queue[0].name == 'U1' + assert recorder.queue[0].parameters == [0.1] + assert recorder.queue[0].wires == Wires(single_wire) + + assert recorder.queue[1].name == 'U2' + assert recorder.queue[1].parameters == [0.1, 0.2] + assert recorder.queue[1].wires == Wires(single_wire) + + assert recorder.queue[2].name == 'U3' + assert recorder.queue[2].parameters == [0.1, 0.2, 0.3] + assert recorder.queue[2].wires == Wires(single_wire) - def test_quantum_circuit_error_by_calling_wrong_parameters(self, recorder): - """Tests that the load method for a QuantumCircuit raises a TypeError, + def test_quantum_circuit_error_by_passing_wrong_parameters(self, recorder): + """Tests the load method for a QuantumCircuit raises a QiskitError, if the wrong type of arguments were passed.""" + theta = Parameter('θ') angle = 'some_string_instead_of_an_angle' qc = QuantumCircuit(3, 1) - qc.rz(angle, [0]) + qc.rz(theta, [0]) quantum_circuit = load(qc) - with pytest.raises(TypeError, match="parameter expected, got "): + with pytest.raises(QiskitError): with recorder: - quantum_circuit() + quantum_circuit(params={theta: angle}) def test_quantum_circuit_error_passing_parameters_not_required(self, recorder): """Tests the load method raises a QiskitError if arguments diff --git a/tests/test_integration.py b/tests/test_integration.py index 3af6315c2..f701af063 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -131,7 +131,7 @@ def test_noise_model(self): """Test that the noise model argument is properly extracted if the backend supports it""" dev = qml.device("qiskit.aer", wires=2, noise_model="test value") - assert dev.run_args["noise_model"] == "test value" + assert dev.noise_model == "test value" def test_invalid_noise_model(self): """Test that the noise model argument causes an exception to be raised @@ -139,12 +139,11 @@ def test_invalid_noise_model(self): with pytest.raises(ValueError, match="does not support noisy simulations"): dev = qml.device("qiskit.basicaer", wires=2, noise_model="test value") - @pytest.mark.parametrize("d", pldevices) - def test_overflow_backend_options(self, d): - """Test all overflow backend options are extracted""" - dev = qml.device(d[0], wires=2, k1="v1", k2="v2") - assert dev.run_args["backend_options"]["k1"] == "v1" - assert dev.run_args["backend_options"]["k2"] == "v2" + def test_overflow_kwargs(self): + """Test all overflow kwargs are extracted for the AerDevice""" + dev = qml.device('qiskit.aer', wires=2, k1="v1", k2="v2") + assert dev.run_args["k1"] == "v1" + assert dev.run_args["k2"] == "v2" class TestLoadIntegration: