From 577dc2f016e9e9db2bd8b23ebf0bd95c0c6dea93 Mon Sep 17 00:00:00 2001 From: Matthew Silverman Date: Thu, 22 Jun 2023 11:03:47 -0400 Subject: [PATCH] Remove retired VQE runner (#313) * pre release version bump * remove VQE runner now that it's retired * change test to not fail with qiskit 0.43; put back requirement --------- Co-authored-by: lillian542 --- CHANGELOG.md | 6 + doc/devices/runtime.rst | 31 -- pennylane_qiskit/__init__.py | 1 - pennylane_qiskit/vqe_runtime_runner.py | 382 -------------- tests/test_runtime.py | 655 +------------------------ 5 files changed, 7 insertions(+), 1068 deletions(-) delete mode 100644 pennylane_qiskit/vqe_runtime_runner.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f44f6c30..139b2adbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ * Added a `RemoteDevice` (PennyLane device name: `qiskit.remote`) that accepts a backend instance directly. [(#304)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/304) +### Breaking changes + +* The `vqe_runner` has been removed, as the Qiskit Runtime VQE program has been retired. + [(#313)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/313) + ### Bug fixes * The list of supported gates is replaced with `pennylane.ops._qubit__ops__` so that the `CZ` gate is included. @@ -14,6 +19,7 @@ This release contains contributions from (in alphabetical order): +Matthew Silverman, Frederik Wilde, Etienne Wodey (Alpine Quantum Technologies GmbH) diff --git a/doc/devices/runtime.rst b/doc/devices/runtime.rst index da9197d4d..db94c90cc 100644 --- a/doc/devices/runtime.rst +++ b/doc/devices/runtime.rst @@ -18,35 +18,4 @@ You can use the ``circuit_runner`` and ``sampler`` devices by using their short dev = qml.device('qiskit.ibmq.sampler', wires=2, backend='ibmq_qasm_simulator', shots=8000, **kwargs) - -Custom Runtime Programs -~~~~~~~~~~~~~~~~~~~~~~~ - -Not all Qiskit runtime programs correspond to complete devices, some solve specific problems (VQE, QAOA, etc...). -We created a wrapper to use PennyLane objects while solving VQE problems on IBM backends. - -.. code-block:: python - - from pennylane_qiskit import vqe_runner - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - hamiltonian = qml.Hamiltonian(coeffs, obs) - shots = 8000 - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={"hub": "ibm-q", "group": "open", "project": "main"}, - ) - More details on Qiskit runtime programs in the `IBMQ runtime documentation `_. diff --git a/pennylane_qiskit/__init__.py b/pennylane_qiskit/__init__.py index 426293812..5b4af934a 100644 --- a/pennylane_qiskit/__init__.py +++ b/pennylane_qiskit/__init__.py @@ -21,4 +21,3 @@ from .converter import load, load_qasm, load_qasm_from_file from .runtime_devices import IBMQCircuitRunnerDevice from .runtime_devices import IBMQSamplerDevice -from .vqe_runtime_runner import vqe_runner diff --git a/pennylane_qiskit/vqe_runtime_runner.py b/pennylane_qiskit/vqe_runtime_runner.py deleted file mode 100644 index 96ab387aa..000000000 --- a/pennylane_qiskit/vqe_runtime_runner.py +++ /dev/null @@ -1,382 +0,0 @@ -# Copyright 2021-2022 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -This module contains a function to run aa custom PennyLane VQE problem on qiskit runtime. -""" -# pylint: disable=too-few-public-methods,protected-access,too-many-arguments,too-many-branches,too-many-statements - -import warnings -import inspect -import os -from collections import OrderedDict - -import pennylane.numpy as np -import pennylane as qml - -import qiskit.circuit.library.n_local as lib_local -from qiskit_ibm_runtime import QiskitRuntimeService -from qiskit_ibm_runtime.program import ResultDecoder -from qiskit.algorithms.optimizers import SPSA, SciPyOptimizer -from qiskit.circuit import ParameterVector, QuantumCircuit, QuantumRegister -from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.opflow.primitive_ops import PauliSumOp -from scipy.optimize import OptimizeResult - -from pennylane_qiskit.ibmq import connect -from pennylane_qiskit.qiskit_device import QiskitDevice - - -class VQEResultDecoder(ResultDecoder): - """The class is used to decode the result from the runtime problem and return it as a - Scipy Optimizer result. - """ - - @classmethod - def decode(cls, data): - """Decode the data from the VQE program.""" - data = super().decode(data) - return OptimizeResult(data) - - -class RuntimeJobWrapper: - """A simple Job wrapper that attaches intermediate results directly to the job object itself - in the ``intermediate_results`` attribute via the ``_callback`` function. - """ - - def __init__(self): - self._job = None - self._decoder = VQEResultDecoder - self.intermediate_results = { - "nfev": [], - "parameters": [], - "function": [], - "step": [], - } - - def _callback(self, *args): - """The callback function that attaches intermediate results to the wrapper: - - Args: - nfev (int): Number of evaluation. - xk (array_like): A list or NumPy array to attach. - fk (float): Value of the function. - step (float): Value of the step. - """ - # If it is a dictionary it is the final result and does not belong to intermediate results - if not isinstance(args[1], dict): - _, (nfev, xk, fk, step) = args - self.intermediate_results["nfev"].append(nfev) - self.intermediate_results["parameters"].append(xk) - self.intermediate_results["function"].append(fk) - self.intermediate_results["step"].append(step) - - def result(self): - """Get the result of the job as a SciPy OptimizerResult object. - - This method blocks until the job is done, cancelled, or raises an error. - - Returns: - OptimizerResult: An optimizer result object. - """ - return self._job.result(decoder=self._decoder) - - -def vqe_runner( - backend, - hamiltonian, - x0, - ansatz="EfficientSU2", - ansatz_config=None, - optimizer="SPSA", - optimizer_config=None, - shots=8192, - use_measurement_mitigation=False, - **kwargs, -): - """Routine that executes a given VQE problem via the VQE program on the target backend. - - Args: - backend (str): Qiskit backend name. - hamiltonian (qml.Hamiltonian): Hamiltonian whose ground state we want to find. - x0 (array_like): Initial vector of parameters. - ansatz (Quantum function or str): Optional, a PennyLane quantum function or the name of the Qiskit - ansatz quantum circuit to use. Default='EfficientSU2' - ansatz_config (dict): Optional, configuration parameters for the ansatz circuit if from Qiskit library. - optimizer (str): Optional, string specifying classical optimizer. Default='SPSA'. - optimizer_config (dict): Optional, configuration parameters for the optimizer. - shots (int): Optional, number of shots to take per circuit. Default=1024. - use_measurement_mitigation (bool): Optional, use measurement mitigation. Default=False. - - Returns: - OptimizeResult: The result in SciPy optimization format. - """ - # Init the dictionnaries - if ansatz_config is None: - ansatz_config = {} - - if optimizer_config is None: - optimizer_config = {"maxiter": 100} - - if not isinstance(hamiltonian, qml.Hamiltonian): - raise qml.QuantumFunctionError("A PennyLane Hamiltonian object is required.") - - connect(kwargs) - - inputs = {} - - # Validate circuit ansatz and number of qubits - if not isinstance(ansatz, str): - ( - inputs["initial_parameters"], - inputs["ansatz"], - num_qubits, - wires, - ) = _pennylane_to_qiskit_ansatz(ansatz, x0, hamiltonian) - - # The circuit will be taken from the Qiskit library as a str was passed. - else: - wires = hamiltonian.wires - num_qubits = len(wires) - - ansatz_circ = getattr(lib_local, ansatz, None) - if ansatz_circ is None: - raise ValueError(f"Ansatz {ansatz} not in n_local circuit library.") - - # If given x0, validate its length against num_params in ansatz - x0 = np.asarray(x0) - ansatz_circ = ansatz_circ(num_qubits, **ansatz_config) - num_params = ansatz_circ.num_parameters - - if x0.shape[0] != num_params: - warnings.warn( - "The shape of parameters array is not correct, a random initialization has been applied." - ) - x0 = 2 * np.pi * np.random.rand(num_params) - - inputs["ansatz"] = ansatz_circ - inputs["initial_parameters"] = x0 - - # Transform the PennyLane hamilonian to a suitable form - hamiltonian = hamiltonian_to_list_string(hamiltonian, wires) - - inputs["operator"] = PauliSumOp.from_list(hamiltonian) - - # Set the optimizer - if optimizer == "SPSA": - inputs["optimizer"] = SPSA(**optimizer_config) - elif optimizer == "QNSPSA": - raise ValueError("QNSPSA is not available for vqe_runner") - # TODO: fix serialization of fidelity function - # fidelity = QNSPSA.get_fidelity(inputs["ansatz"]) - # inputs["optimizer"] = QNSPSA(fidelity, **optimizer_config) - else: # SciPy optimizers - inputs["optimizer"] = SciPyOptimizer(optimizer, options=optimizer_config) - - # Set the rest of the inputs - inputs["shots"] = shots - inputs["use_measurement_mitigation"] = use_measurement_mitigation - - # Specify a single hub, group and project - hub = kwargs.get("hub", "ibm-q") - group = kwargs.get("group", "open") - project = kwargs.get("project", "main") - instance = "/".join([hub, group, project]) - - options = {"backend": backend, "instance": instance} - - service = QiskitRuntimeService(channel="ibm_quantum", token=os.getenv("IBMQX_TOKEN")) - rt_job = RuntimeJobWrapper() - rt_job._job = service.run( - program_id="vqe", inputs=inputs, options=options, callback=rt_job._callback - ) - return rt_job - - -def _pennylane_to_qiskit_ansatz(ansatz, x0, hamiltonian): - r"""Convert an ansatz from PennyLane to a circuit in Qiskit. - - Args: - ansatz (Quantum Function): A PennyLane quantum function that represents the circuit. - x0 (array_like): The array of parameters. - num_qubits_h (int): Number of qubits evaluated from the Hamiltonian. - - Returns: - list[tuple[float,str]]: Hamiltonian in a format for the runtime program. - """ - - if isinstance(ansatz, (qml.QNode, qml.tape.QuantumScript)): - raise qml.QuantumFunctionError("The ansatz must be a callable quantum function.") - - if not callable(ansatz): - raise ValueError("Input ansatz is not a quantum function or a string.") - - if len(inspect.getfullargspec(ansatz).args) != 1: - raise qml.QuantumFunctionError("Param should be a single vector.") - try: - tape_param = x0[0] if len(x0) == 1 else x0 - tape = qml.transforms.make_tape(ansatz)(np.array(tape_param)).expand( - depth=5, stop_at=lambda obj: obj.name in QiskitDevice._operation_map - ) - except IndexError as e: - raise qml.QuantumFunctionError("Not enough parameters in X0.") from e - - # Raise exception if there are no operations - if len(tape.operations) == 0: - raise qml.QuantumFunctionError("Function contains no quantum operations.") - - params = tape.get_parameters() - trainable_params = [p for p in params if qml.math.requires_grad(p)] - num_params = len(trainable_params) - - if len(x0) != num_params: - warnings.warn( - "In order to match the tape expansion, the number of parameters has been changed." - ) - x0 = 2 * np.pi * np.random.rand(num_params) - - wires_circuit = tape.wires - wires_hamiltonian = hamiltonian.wires - all_wires = wires_circuit + wires_hamiltonian - - # Set the number of qubits - num_qubits = len(all_wires) - - circuit_ansatz = _qiskit_ansatz(num_params, num_qubits, all_wires, tape) - - return x0, circuit_ansatz, num_qubits, all_wires - - -def _qiskit_ansatz(num_params, num_qubits, wires, tape): - """Transform a quantum tape from PennyLane to a Qiskit circuit. - - Args: - num_params (int): Number of parameters. - num_qubits (int): Number of qubits. - wires (qml.wire.Wires): Wires used in the tape and Hamiltonian. - tape (qml.tape.QuantumTape): The quantum tape of the circuit ansatz in PennyLane. - - Returns: - QuantumCircuit: Qiskit quantum circuit. - - """ - consecutive_wires = qml.wires.Wires(range(num_qubits)) - wires_map = OrderedDict(zip(wires, consecutive_wires)) - # From here: Create the Qiskit ansatz circuit - params_vector = ParameterVector("p", num_params) - - reg = QuantumRegister(num_qubits, "q") - circuit_ansatz = QuantumCircuit(reg, name="vqe") - - circuits = [] - - j = 0 - for operation in tape.operations: - wires = operation.wires.map(wires_map) - par = operation.parameters - operation = operation.name - mapped_operation = QiskitDevice._operation_map[operation] - - qregs = [reg[i] for i in wires.labels] - - adjoint = operation.startswith("Adjoint(") - split_op = operation.split("Adjoint(") - - if ( - adjoint - and split_op[1] in ("QubitUnitary)", "QubitStateVector)") - or not adjoint - and split_op[0] in ("QubitUnitary", "QubitStateVector") - ): - # Need to revert the order of the quantum registers used in - # Qiskit such that it matches the PennyLane ordering - qregs = list(reversed(qregs)) - dag = circuit_to_dag(QuantumCircuit(reg, name="")) - - if operation in ("QubitUnitary", "QubitStateVector"): - # Parameters are matrices - gate = mapped_operation(par[0]) - else: - # Parameters for the operation - if par and qml.math.requires_grad(par[0]): - op_num_params = len(par) - par = [params_vector[j + num] for num in range(op_num_params)] - j += op_num_params - - gate = mapped_operation(*par) - - dag.apply_operation_back(gate, qargs=qregs) - circuit = dag_to_circuit(dag) - circuits.append(circuit) - - for circuit in circuits: - circuit_ansatz &= circuit - - return circuit_ansatz - - -def hamiltonian_to_list_string(hamiltonian, wires): - r"""Convert a Hamiltonian object from PennyLane to a list of pairs representing each coefficient and - term in the Hamiltonian. - - Args: - hamiltonian (qml.Hamiltonian): A Hamiltonian from PennyLane. - wires (qml.wires.Wires): A list of qubits from PennyLane. - - Returns: - list[tuple[str,float]]: Hamiltonian in a format for the runtime program. - """ - - num_qubits = len(wires) - - consecutive_wires = qml.wires.Wires(range(num_qubits)) - wires_map = OrderedDict(zip(wires, consecutive_wires)) - - coeff, observables = hamiltonian.terms() - - authorized_obs = {"PauliX", "PauliY", "PauliZ", "Identity"} - - for obs in observables: - obs_names = obs.name if isinstance(obs.name, list) else [obs.name] - if any(ob not in authorized_obs for ob in obs_names): - raise qml.QuantumFunctionError("Observable is not accepted.") - - # Create string Hamiltonian - obs_str = {"PauliX": "X", "PauliY": "Y", "PauliZ": "Z", "Hadamard": "H", "Identity": "I"} - - obs_org = [] - # Map the PennyLane hamiltonian to a list PauliY(1) @ PauliY(0) -> [[[0,'Y'], [1,'Y']]] - for obs in observables: - # Tensors - if isinstance(obs.name, list): - internal = [ - [i, obs_str[j]] for i, j in zip(obs.wires.map(wires_map).tolist(), obs.name) - ] - internal.sort() - obs_org.append(internal) - else: - obs_org.append([[obs.wires.map(wires_map).tolist()[0], obs_str[obs.name]]]) - - # Create the hamiltonian terms as lists of strings [[[0,'Y'], [1,'Y']]] -> [['YI'], ['IY']] - obs_list = [] - for elem in obs_org: - empty_obs = ["I"] * num_qubits - for el in elem: - wire = el[0] - observable = el[1] - empty_obs[wire] = observable - obs_list.append(empty_obs) - - # Create the list of tuples with coeffs and Hamiltonian terms as strings [['YI'], ['IY']] -> [('YI', 1), ('IY', 1)] - hamiltonian = [("".join(elem), coeff[i]) for i, elem in enumerate(obs_list)] - return hamiltonian diff --git a/tests/test_runtime.py b/tests/test_runtime.py index b9eefa1ef..413682e51 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -20,7 +20,6 @@ import pytest from pennylane_qiskit import IBMQCircuitRunnerDevice, IBMQSamplerDevice -from pennylane_qiskit.vqe_runtime_runner import vqe_runner, hamiltonian_to_list_string @pytest.mark.usefixtures("skip_if_no_account") @@ -60,7 +59,7 @@ def circuit(theta, phi): "initial_layout": [0, 1], "layout_method": "trivial", "routing_method": "basic", - "translation_method": "unroller", + "translation_method": "translator", "seed_transpiler": 42, "optimization_level": 2, "init_qubits": True, @@ -226,655 +225,3 @@ def circuit(): circuit() assert len(dev.tracker.history) == 2 - - -@pytest.mark.usefixtures("skip_if_no_account") -class TestCustomVQE: - """Class to test the custom VQE program.""" - - @pytest.mark.parametrize("shots", [8000]) - def test_simple_hamiltonian(self, tol, shots): - """Test a simple VQE problem with Hamiltonian and a circuit from PennyLane""" - - tol = 1e-1 - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert np.allclose(job.result()["optimal_value"], -1.43, tol) - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_ansatz_qiskit(self, shots): - """Test a simple VQE problem with an ansatz from Qiskit library.""" - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz="EfficientSU2", - x0=[0.0, 0.0, 0.0, 0.0], - shots=shots, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_ansatz_qiskit_invalid(self, shots): - """Test a simple VQE problem with an invalid ansatz from Qiskit library.""" - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises( - ValueError, match="Ansatz InEfficientSU2 not in n_local circuit library." - ): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz="InEfficientSU2", - x0=[3.97507603, 3.00854038], - shots=shots, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_qnode(self, shots): - """Test that we cannot pass a QNode as ansatz circuit.""" - - with qml.tape.QuantumTape() as vqe_tape: - qml.RX(3.97507603, wires=0) - qml.RY(3.00854038, wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises( - qml.QuantumFunctionError, match="The ansatz must be a callable quantum function." - ): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_tape, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_tape(self, shots): - """Test that we cannot pass a tape as ansatz circuit.""" - - dev = qml.device("default.qubit", wires=1) - - @qml.qnode(dev) - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises( - qml.QuantumFunctionError, match="The ansatz must be a callable quantum function." - ): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_wrong_input(self, shots): - """Test that we can only give a single vector parameter to the ansatz circuit.""" - - def vqe_circuit(params, wire): - qml.RX(params[0], wires=wire) - qml.RY(params[1], wires=wire) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(qml.QuantumFunctionError, match="Param should be a single vector."): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_wrong_number_input_param(self, shots): - """Test that we need a certain number of parameters.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - qml.RY(params[2], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(qml.QuantumFunctionError, match="Not enough parameters in X0."): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[0, 0], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_one_param(self, shots): - """Test that we can only give a single vector parameter to the ansatz circuit.""" - - def vqe_circuit(params): - qml.RX(params, wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[0.0], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_too_many_param(self, shots): - """Test that we handle the case where too many parameters were given.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.warns( - UserWarning, - match="In order to match the tape expansion, the number of parameters has been changed.", - ): - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038, 3.55637849], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_more_qubits_in_circuit_than_hamiltonian(self, shots): - """Test that we handle the case where there are more qubits in the circuit than the hamiltonian.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=1) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_qubitunitary(self, shots): - """Test that we can handle a QubitUnitary operation.""" - - def vqe_circuit(params): - qml.QubitUnitary(np.array([[1, 0], [0, 1]]), wires=0) - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=1) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_inverse(self, shots): - """Test that we can handle inverse operations.""" - - def vqe_circuit(params): - qml.adjoint(qml.RX(params[0], wires=0)) - qml.RX(params[1], wires=1) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_hamiltonian_tensor(self, shots): - """Test that we can handle tensor Hamiltonians.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=1) - - coeffs = [0.2, -0.543] - obs = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliY(1)] - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize( - "bad_op", [qml.Hermitian(np.array([[1, 0], [0, -1]]), wires=0), qml.Hadamard(1)] - ) - @pytest.mark.parametrize("shots", [8000]) - def test_not_auth_operation_hamiltonian(self, shots, bad_op): - """Test the observables in the Hamiltonian are I, X, Y, or Z.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=0) - - H = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]]) - coeffs = [1, 1] - obs = [qml.PauliX(0), bad_op] - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(qml.QuantumFunctionError, match="Observable is not accepted."): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_not_auth_operation_hamiltonian_tensor(self, shots): - """Test the observables in the tensor Hamiltonian are I, X, Y, or Z.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=1) - - H = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]]) - coeffs = [1, 1] - obs = [qml.PauliX(0) @ qml.Hermitian(H, wires=1), qml.PauliZ(wires=1)] - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(qml.QuantumFunctionError, match="Observable is not accepted."): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_scipy_optimizer(self, tol, shots): - """Test we can run a VQE problem with a SciPy optimizer.""" - - tol = 1e-1 - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="Powell", - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - result = job.result()["optimal_value"] - - assert np.allclose(result, -1.43, tol) - assert "parameters" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_scipy_optimizer(self, shots): - """Test we can run a VQE problem with a SciPy optimizer.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="COBYLA", - optimizer_config={"maxiter": 10}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - result = job.result()["optimal_value"] - - assert "parameters" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_simple_hamiltonian_with_untrainable_parameters(self, tol, shots): - """Test a simple VQE problem with untrainable parameters.""" - - tol = 1e-1 - - def vqe_circuit(params): - qml.RZ(0.1, wires=0) - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - qml.RZ(0.2, wires=0) - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - job = vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer="SPSA", - optimizer_config={"maxiter": 40}, - kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"}, - ) - - assert np.allclose(job.result()["optimal_value"], -1.43, tol) - assert isinstance(job.intermediate_results, dict) - assert "nfev" in job.intermediate_results - assert "parameters" in job.intermediate_results - assert "function" in job.intermediate_results - assert "step" in job.intermediate_results - - @pytest.mark.parametrize("shots", [8000]) - def test_invalid_function(self, shots): - """Test that an invalid function cannot be passed.""" - - def vqe_circuit(params): - c = params[0] + params[1] - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises( - qml.QuantumFunctionError, match="Function contains no quantum operations." - ): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - @pytest.mark.parametrize("shots", [8000]) - def test_invalid_ansatz(self, shots): - """Test that an invalid ansatz cannot be passed.""" - - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(ValueError, match="Input ansatz is not a quantum function or a string."): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=10, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - ) - - def test_qnspsa_disabled(self): - """Tests that QNSPSA is rejected before launching a job.""" - coeffs = [1, 1] - obs = [qml.PauliX(0), qml.PauliZ(0)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - - with pytest.raises(ValueError, match="QNSPSA is not available for vqe_runner"): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - x0=[3.97507603, 3.00854038], - optimizer="QNSPSA", - ) - - -def test_hamiltonian_to_list_string(): - """Test the function that transforms a PennyLane Hamiltonian to a list string Hamiltonian.""" - coeffs = [1, 1] - obs = [qml.PauliX(0) @ qml.PauliX(2), qml.PauliY(0) @ qml.PauliZ(1)] - - hamiltonian = qml.Hamiltonian(coeffs, obs) - result = hamiltonian_to_list_string(hamiltonian, hamiltonian.wires) - - assert [("XIX", 1), ("YZI", 1)] == result - - -@pytest.mark.parametrize("shots", [8000]) -def test_hamiltonian_format(shots): - """Test that a PennyLane Hamiltonian is required.""" - - def vqe_circuit(params): - qml.RX(params[0], wires=0) - qml.RX(params[1], wires=1) - - hamiltonian = qml.PauliZ(wires=0) - - with pytest.raises( - qml.QuantumFunctionError, match="A PennyLane Hamiltonian object is required." - ): - vqe_runner( - backend="ibmq_qasm_simulator", - hamiltonian=hamiltonian, - ansatz=vqe_circuit, - x0=[3.97507603, 3.00854038], - shots=shots, - optimizer_config={"maxiter": 10}, - kwargs={ - "hub": "ibm-q-startup", - "group": "ibm-q-startup", - "project": "reservations", - }, - )