From a4c59d37f055f07843e7633630ca0b29255f5b12 Mon Sep 17 00:00:00 2001 From: lillian542 Date: Mon, 5 Feb 2024 14:53:47 -0500 Subject: [PATCH 01/21] save draft of SparsePauliOp conversion --- pennylane_qiskit/converter.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 2149ac2ed..04ba150b5 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -256,3 +256,24 @@ def load_qasm_from_file(file: str): function: the new PennyLane template """ return load(QuantumCircuit.from_qasm_file(file)) + +def convert_sparse_pauli_op_to_pl(sparse_op, params=None): + """docstring with clear examples here - remember to comment on wire ordering!""" + + if params: + sparse_op = sparse_op.assign_parameters(params) + + op_map = {'X': qml.PauliX, 'Y': qml.PauliY, 'Z': qml.PauliZ, 'I': qml.Identity} + + coeffs = sparse_op.coeffs + if ParameterExpression in [type(c) for c in coeffs]: + raise RuntimeError(f"Not all parameter expressions are assigned in coeffs {coeffs}") + + qiskit_terms = sparse_op.paulis + pl_terms = [] + + for term in qiskit_terms: + operators = [op_map[str(op)](wire) for wire, op in enumerate(term)] + pl_terms.append(qml.prod(*operators).simplify()) + + return qml.dot(coeffs, pl_terms) \ No newline at end of file From 1b021d30bd4ce78b65d31b57fdb513414503ff44 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 21 Feb 2024 18:13:49 -0500 Subject: [PATCH 02/21] Add docstring to `convert_sparse_pauli_op_to_pl()` --- pennylane_qiskit/converter.py | 58 ++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 03c72dc30..f990337cb 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -23,6 +23,7 @@ from qiskit.circuit import Parameter, ParameterExpression, Measure, Barrier from qiskit.circuit.library import GlobalPhaseGate from qiskit.exceptions import QiskitError +from qiskit.quantum_info import SparsePauliOp from sympy import lambdify import pennylane as qml @@ -437,13 +438,60 @@ def load_qasm_from_file(file: str): """ return load(QuantumCircuit.from_qasm_file(file)) -def convert_sparse_pauli_op_to_pl(sparse_op, params=None): - """docstring with clear examples here - remember to comment on wire ordering!""" - + +def convert_sparse_pauli_op_to_pl( + sparse_op: SparsePauliOp, + params: Any = None, +) -> qml.operation.Operator: + """Converts a Qiskit SparsePauliOp into a PennyLane operator. + + Args: + sparse_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted + params (Any): optional assignment of coefficient values for the SparsePauliOp; see the + `Qiskit documentation `__ + to learn more about the expected format of these parameters + + Returns: + pennylane.operation.Operator: The equivalent PennyLane operator. + + .. note:: + + The order in which Pauli operations appear in each SparsePauliOp term is the reverse of how + they are applied in the PennyLane operator. This is consistent with the iteration order of a + Pauli term in Qiskit. + + **Example** + + Consider the following script which creates a Qiskit ``SparsePauliOp``: + + .. code-block:: python + + import numpy as np + from qiskit.circuit import Parameter + from qiskit.quantum_info import SparsePauliOp + + a, b, c = [Parameter(var) for var in "abc"] + qiskit_op = SparsePauliOp(["II", "XZ", "YX"], np.array([a, b, c])) + + The ``SparsePauliOp`` has three coefficients (parameters): + + >>> qiskit_op + SparsePauliOp(['II', 'XZ', 'YX'], + coeffs=[ParameterExpression(1.0*a), ParameterExpression(1.0*b), + ParameterExpression(1.0*c)]) + + Now, to convert the ``SparsePauliOp`` into a PennyLane operator, run: + + >>> from pennylane_qiskit.converter import convert_sparse_pauli_op_to_pl + >>> convert_sparse_pauli_op_to_pl(qiskit_op, {a: 2, b: 3, c: 4}) + ((2+0j)*(Identity(wires=[0, 1]))) + + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) + """ if params: sparse_op = sparse_op.assign_parameters(params) - op_map = {'X': qml.PauliX, 'Y': qml.PauliY, 'Z': qml.PauliZ, 'I': qml.Identity} + op_map = {"X": qml.PauliX, "Y": qml.PauliY, "Z": qml.PauliZ, "I": qml.Identity} coeffs = sparse_op.coeffs if ParameterExpression in [type(c) for c in coeffs]: @@ -456,4 +504,4 @@ def convert_sparse_pauli_op_to_pl(sparse_op, params=None): operators = [op_map[str(op)](wire) for wire, op in enumerate(term)] pl_terms.append(qml.prod(*operators).simplify()) - return qml.dot(coeffs, pl_terms) \ No newline at end of file + return qml.dot(coeffs, pl_terms) From 1891c4830711eb046b01704b0ebeb57262fa3217 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 21 Feb 2024 18:16:34 -0500 Subject: [PATCH 03/21] Add tests for `convert_sparse_pauli_op_to_pl()` --- tests/test_converter.py | 100 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/tests/test_converter.py b/tests/test_converter.py index 5774d5665..4a0c4dd69 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -7,11 +7,12 @@ from qiskit.circuit import Parameter from qiskit.circuit.library import EfficientSU2 from qiskit.exceptions import QiskitError -from qiskit.quantum_info.operators import Operator +from qiskit.quantum_info import SparsePauliOp import pennylane as qml from pennylane import numpy as np from pennylane_qiskit.converter import ( + convert_sparse_pauli_op_to_pl, load, load_qasm, load_qasm_from_file, @@ -1416,3 +1417,100 @@ def test_diff_meas_circuit(self): qtemp2 = load(qc, measurements=[qml.expval(qml.PauliZ(0))]) assert qtemp()[0] != qtemp2()[0] and qtemp2()[0] == qml.expval(qml.PauliZ(0)) + + +class TestConvertSparsePauliOp: + """Tests for the :func:`convert_sparse_pauli_op_to_pl()` function.""" + + @pytest.mark.parametrize( + "sparse_pauli_op, want_op", + [ + ( + SparsePauliOp("I"), + qml.Identity(wires=0), + ), + ( + SparsePauliOp("XYZ"), + qml.prod(qml.PauliZ(wires=0), qml.PauliY(wires=1), qml.PauliX(wires=2)), + ), + ( + SparsePauliOp(["XY", "ZX"]), + qml.sum( + qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0)), + qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0)), + ) + ), + ] + ) + def test_convert_with_default_coefficients(self, sparse_pauli_op, want_op): + """Tests that a SparsePauliOp can be converted into a PennyLane operator with the default + coefficients. + """ + have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op) + assert qml.equal(have_op, want_op) + + @pytest.mark.parametrize( + "sparse_pauli_op, want_op", + [ + ( + SparsePauliOp("I", coeffs=[2]), + qml.s_prod(2, qml.Identity(wires=0)), + ), + ( + SparsePauliOp(["XY", "ZX"], coeffs=[3, 7]), + qml.sum( + qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), + qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), + ) + ), + ] + ) + def test_convert_with_literal_coefficients(self, sparse_pauli_op, want_op): + """Tests that a SparsePauliOp can be converted into a PennyLane operator with literal + coefficient values. + """ + have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op) + assert qml.equal(have_op, want_op) + + + def test_convert_with_parameter_coefficients(self): + """Tests that a SparsePauliOp can be converted into a PennyLane operator by assigning values + to each parameterized coefficient. + """ + a, b = [Parameter(var) for var in "ab"] + sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + + have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3, b: 7}) + want_op = qml.sum( + qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), + qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), + ) + assert qml.equal(have_op, want_op) + + def test_convert_too_few_coefficients(self): + """Tests that a RuntimeError is raised if an attempt is made to convert a SparsePauliOp into + a PennyLane operator without assigning values for all parameterized coefficients. + """ + a, b = [Parameter(var) for var in "ab"] + sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + + match = ( + "Not all parameter expressions are assigned in coeffs " + r"\[\(3\+0j\) ParameterExpression\(1\.0\*b\)\]" + ) + with pytest.raises(RuntimeError, match=match): + convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3}) + + def test_convert_too_many_coefficients(self): + """Tests that a SparsePauliOp can be converted into a PennyLane operator by assigning values + to a strict superset of the parameterized coefficients. + """ + a, b, c = [Parameter(var) for var in "abc"] + sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + + have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3, b: 7, c: 9}) + want_op = qml.sum( + qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), + qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), + ) + assert qml.equal(have_op, want_op) From 8cbe0e7ab4f9d506b77148be40a47dd53fed1c2b Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Wed, 21 Feb 2024 18:18:44 -0500 Subject: [PATCH 04/21] Synchronize `qiskit` and `qiskit-terra` requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a9538d351..827a96ca5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,7 +29,7 @@ qiskit-aer==0.13.3 qiskit-ibm-runtime==0.17.0 qiskit-ibm-provider==0.7.3 qiskit-ignis==0.7.1 -qiskit-terra==0.45.1 +qiskit-terra==0.45.3 requests==2.31.0 requests-ntlm==1.2.0 retworkx==0.11.0 From 53fe08d0c9ae75bb723d0cb62a5aa0de07b28b42 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 12:04:38 -0500 Subject: [PATCH 05/21] Remove unused requirement `tweedledum` --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1ca722eab..3936ac0f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,6 +40,5 @@ stevedore==5.1.0 symengine==0.11.0 sympy==1.12 toml==0.10.2 -tweedledum==1.1.1 urllib3==2.2.1 websocket-client==1.7.0 From 3c66e1a2336ca3a6de2524a677b574e7203420ed Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 12:19:55 -0500 Subject: [PATCH 06/21] Add `wires` argument to `convert_sparse_pauli_op_to_pl()` --- pennylane_qiskit/converter.py | 16 +++++++++++-- tests/test_converter.py | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 484cd584b..ba7900219 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -15,7 +15,7 @@ This module contains functions for converting Qiskit QuantumCircuit objects into PennyLane circuit templates. """ -from typing import Dict, Any +from typing import Dict, Any, Sequence, Union import warnings from functools import partial, reduce @@ -565,12 +565,16 @@ def load_qasm_from_file(file: str): def convert_sparse_pauli_op_to_pl( sparse_op: SparsePauliOp, + wires: Union[Sequence, None] = None, params: Any = None, ) -> qml.operation.Operator: """Converts a Qiskit SparsePauliOp into a PennyLane operator. Args: sparse_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted + wires (Sequence | None): optional assignment of wires for the converted SparsePauliOp; if + the original SparsePauliOp acted on :math:`N` qubits, then this must be a sequence of + length :math:`N` params (Any): optional assignment of coefficient values for the SparsePauliOp; see the `Qiskit documentation `__ to learn more about the expected format of these parameters @@ -612,6 +616,14 @@ def convert_sparse_pauli_op_to_pl( + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) """ + if wires is not None and len(wires) != sparse_op.num_qubits: + raise RuntimeError( + f"The specified number of wires - {len(wires)} - does not match the " + f"number of qubits the SparsePauliOp acts on." + ) + + wire_map = map_wires(range(sparse_op.num_qubits), wires) + if params: sparse_op = sparse_op.assign_parameters(params) @@ -625,7 +637,7 @@ def convert_sparse_pauli_op_to_pl( pl_terms = [] for term in qiskit_terms: - operators = [op_map[str(op)](wire) for wire, op in enumerate(term)] + operators = [op_map[str(op)](wire_map[wire]) for wire, op in enumerate(term)] pl_terms.append(qml.prod(*operators).simplify()) return qml.dot(coeffs, pl_terms) diff --git a/tests/test_converter.py b/tests/test_converter.py index c63444bb9..f74480dbe 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -1770,3 +1770,48 @@ def test_convert_too_many_coefficients(self): qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), ) assert qml.equal(have_op, want_op) + + @pytest.mark.parametrize( + "sparse_pauli_op, wires, want_op", + [ + ( + SparsePauliOp("XYZ"), + "ABC", + qml.prod(qml.PauliZ(wires="A"), qml.PauliY(wires="B"), qml.PauliX(wires="C")), + ), + ( + SparsePauliOp(["XY", "ZX"]), + [1, 0], + qml.sum( + qml.prod(qml.PauliX(wires=0), qml.PauliY(wires=1)), + qml.prod(qml.PauliZ(wires=0), qml.PauliX(wires=1)), + ) + ), + ] + ) + def test_convert_with_wires(self, sparse_pauli_op, wires, want_op): + """Tests that a SparsePauliOp can be converted into a PennyLane operator with custom wires.""" + have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, wires=wires) + assert qml.equal(have_op, want_op) + + def test_convert_with_too_few_wires(self): + """Tests that a RuntimeError is raised if an attempt is made to convert a SparsePauliOp into + a PennyLane operator with too few custom wires. + """ + match = ( + r"The specified number of wires - 1 - does not match " + f"the number of qubits the SparsePauliOp acts on." + ) + with pytest.raises(RuntimeError, match=match): + convert_sparse_pauli_op_to_pl(SparsePauliOp("II"), wires=[0]) + + def test_convert_with_too_many_wires(self): + """Tests that a RuntimeError is raised if an attempt is made to convert a SparsePauliOp into + a PennyLane operator with too many custom wires. + """ + match = ( + r"The specified number of wires - 3 - does not match " + f"the number of qubits the SparsePauliOp acts on." + ) + with pytest.raises(RuntimeError, match=match): + convert_sparse_pauli_op_to_pl(SparsePauliOp("II"), wires=[0, 1, 2]) From 16269f8fc381c7496772068fb1d2467c031946e8 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 12:23:30 -0500 Subject: [PATCH 07/21] Swap order of `params` and `wires` arguments --- pennylane_qiskit/converter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index ba7900219..bfa9f0d4b 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -565,19 +565,19 @@ def load_qasm_from_file(file: str): def convert_sparse_pauli_op_to_pl( sparse_op: SparsePauliOp, - wires: Union[Sequence, None] = None, params: Any = None, + wires: Union[Sequence, None] = None, ) -> qml.operation.Operator: """Converts a Qiskit SparsePauliOp into a PennyLane operator. Args: sparse_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted - wires (Sequence | None): optional assignment of wires for the converted SparsePauliOp; if - the original SparsePauliOp acted on :math:`N` qubits, then this must be a sequence of - length :math:`N` params (Any): optional assignment of coefficient values for the SparsePauliOp; see the `Qiskit documentation `__ to learn more about the expected format of these parameters + wires (Sequence | None): optional assignment of wires for the converted SparsePauliOp; if + the original SparsePauliOp acted on :math:`N` qubits, then this must be a sequence of + length :math:`N` Returns: pennylane.operation.Operator: The equivalent PennyLane operator. From e47512b7315870ad0631ee9ce9d35bd81aacc6b4 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 13:24:15 -0500 Subject: [PATCH 08/21] Pass `coeffs` by keyword argument in docstring example Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- pennylane_qiskit/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index bfa9f0d4b..51d913802 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -599,7 +599,7 @@ def convert_sparse_pauli_op_to_pl( from qiskit.quantum_info import SparsePauliOp a, b, c = [Parameter(var) for var in "abc"] - qiskit_op = SparsePauliOp(["II", "XZ", "YX"], np.array([a, b, c])) + qiskit_op = SparsePauliOp(["II", "XZ", "YX"], coeffs=np.array([a, b, c])) The ``SparsePauliOp`` has three coefficients (parameters): From 3d494bbc46cf5a8de3ec3361c25456cd2abd3a87 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 13:24:43 -0500 Subject: [PATCH 09/21] Pass `params` by keyword argument in example Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- pennylane_qiskit/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 51d913802..997f28df9 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -611,7 +611,7 @@ def convert_sparse_pauli_op_to_pl( Now, to convert the ``SparsePauliOp`` into a PennyLane operator, run: >>> from pennylane_qiskit.converter import convert_sparse_pauli_op_to_pl - >>> convert_sparse_pauli_op_to_pl(qiskit_op, {a: 2, b: 3, c: 4}) + >>> convert_sparse_pauli_op_to_pl(qiskit_op, params={a: 2, b: 3, c: 4}) ((2+0j)*(Identity(wires=[0, 1]))) + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) From abe67821431309ddf9c6f35636bfd9a0b4dc81a5 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 13:32:32 -0500 Subject: [PATCH 10/21] Add comment about wire ordering to Qiskit term `for` loop Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- pennylane_qiskit/converter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 997f28df9..8546bced3 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -637,6 +637,9 @@ def convert_sparse_pauli_op_to_pl( pl_terms = [] for term in qiskit_terms: + # term is a special Qiskit type. Iterating over the term goes right to left + # in accordance with Qiskit wire order convention, i.e. `enumerate("XZ")` will be + # [(0, "Z"), (1, "X")], so we don't need to reverse to match the PL convention operators = [op_map[str(op)](wire_map[wire]) for wire, op in enumerate(term)] pl_terms.append(qml.prod(*operators).simplify()) From c528969c6586911b1691c62cccd5f0ac3aa7aa26 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 13:28:23 -0500 Subject: [PATCH 11/21] Rename function to `convert_sparse_pauli_op()` --- pennylane_qiskit/__init__.py | 2 +- pennylane_qiskit/converter.py | 10 ++++++---- tests/test_converter.py | 20 ++++++++++---------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pennylane_qiskit/__init__.py b/pennylane_qiskit/__init__.py index 5b4af934a..20afa07b3 100644 --- a/pennylane_qiskit/__init__.py +++ b/pennylane_qiskit/__init__.py @@ -18,6 +18,6 @@ from .basic_aer import BasicAerDevice from .ibmq import IBMQDevice from .remote import RemoteDevice -from .converter import load, load_qasm, load_qasm_from_file +from .converter import load, load_qasm, load_qasm_from_file, convert_sparse_pauli_op from .runtime_devices import IBMQCircuitRunnerDevice from .runtime_devices import IBMQSamplerDevice diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 8546bced3..d2ca84bff 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -502,7 +502,9 @@ def _function(*args, params: dict = None, wires: list = None, **kwargs): # Check for elif branches (doesn't require qjit) if elif_fns: - m_val = sum(2**idx * mid_circ_regs[clbit] for idx, clbit in enumerate(res_reg)) + m_val = sum( + 2**idx * mid_circ_regs[clbit] for idx, clbit in enumerate(res_reg) + ) for elif_bit, elif_branch in elif_fns: qml.cond(m_val == elif_bit, elif_branch)( *operation_args, **operation_kwargs @@ -563,7 +565,7 @@ def load_qasm_from_file(file: str): return load(QuantumCircuit.from_qasm_file(file)) -def convert_sparse_pauli_op_to_pl( +def convert_sparse_pauli_op( sparse_op: SparsePauliOp, params: Any = None, wires: Union[Sequence, None] = None, @@ -610,8 +612,8 @@ def convert_sparse_pauli_op_to_pl( Now, to convert the ``SparsePauliOp`` into a PennyLane operator, run: - >>> from pennylane_qiskit.converter import convert_sparse_pauli_op_to_pl - >>> convert_sparse_pauli_op_to_pl(qiskit_op, params={a: 2, b: 3, c: 4}) + >>> from pennylane_qiskit.converter import convert_sparse_pauli_op + >>> convert_sparse_pauli_op(qiskit_op, params={a: 2, b: 3, c: 4}) ((2+0j)*(Identity(wires=[0, 1]))) + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) diff --git a/tests/test_converter.py b/tests/test_converter.py index f74480dbe..bd498746a 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -11,7 +11,7 @@ import pennylane as qml from pennylane import numpy as np from pennylane_qiskit.converter import ( - convert_sparse_pauli_op_to_pl, + convert_sparse_pauli_op, load, load_qasm, load_qasm_from_file, @@ -1676,7 +1676,7 @@ def circuit_loaded_qiskit_circuit(): class TestConvertSparsePauliOp: - """Tests for the :func:`convert_sparse_pauli_op_to_pl()` function.""" + """Tests for the :func:`convert_sparse_pauli_op()` function.""" @pytest.mark.parametrize( "sparse_pauli_op, want_op", @@ -1702,7 +1702,7 @@ def test_convert_with_default_coefficients(self, sparse_pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with the default coefficients. """ - have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op) + have_op = convert_sparse_pauli_op(sparse_pauli_op) assert qml.equal(have_op, want_op) @pytest.mark.parametrize( @@ -1725,7 +1725,7 @@ def test_convert_with_literal_coefficients(self, sparse_pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with literal coefficient values. """ - have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op) + have_op = convert_sparse_pauli_op(sparse_pauli_op) assert qml.equal(have_op, want_op) @@ -1736,7 +1736,7 @@ def test_convert_with_parameter_coefficients(self): a, b = [Parameter(var) for var in "ab"] sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3, b: 7}) + have_op = convert_sparse_pauli_op(sparse_pauli_op, params={a: 3, b: 7}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1755,7 +1755,7 @@ def test_convert_too_few_coefficients(self): r"\[\(3\+0j\) ParameterExpression\(1\.0\*b\)\]" ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3}) + convert_sparse_pauli_op(sparse_pauli_op, params={a: 3}) def test_convert_too_many_coefficients(self): """Tests that a SparsePauliOp can be converted into a PennyLane operator by assigning values @@ -1764,7 +1764,7 @@ def test_convert_too_many_coefficients(self): a, b, c = [Parameter(var) for var in "abc"] sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, params={a: 3, b: 7, c: 9}) + have_op = convert_sparse_pauli_op(sparse_pauli_op, params={a: 3, b: 7, c: 9}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1791,7 +1791,7 @@ def test_convert_too_many_coefficients(self): ) def test_convert_with_wires(self, sparse_pauli_op, wires, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with custom wires.""" - have_op = convert_sparse_pauli_op_to_pl(sparse_pauli_op, wires=wires) + have_op = convert_sparse_pauli_op(sparse_pauli_op, wires=wires) assert qml.equal(have_op, want_op) def test_convert_with_too_few_wires(self): @@ -1803,7 +1803,7 @@ def test_convert_with_too_few_wires(self): f"the number of qubits the SparsePauliOp acts on." ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op_to_pl(SparsePauliOp("II"), wires=[0]) + convert_sparse_pauli_op(SparsePauliOp("II"), wires=[0]) def test_convert_with_too_many_wires(self): """Tests that a RuntimeError is raised if an attempt is made to convert a SparsePauliOp into @@ -1814,4 +1814,4 @@ def test_convert_with_too_many_wires(self): f"the number of qubits the SparsePauliOp acts on." ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op_to_pl(SparsePauliOp("II"), wires=[0, 1, 2]) + convert_sparse_pauli_op(SparsePauliOp("II"), wires=[0, 1, 2]) From ad1e351370085526059f83e90e099e0a9ed3271b Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 13:31:52 -0500 Subject: [PATCH 12/21] Reword note according to PR suggestion --- pennylane_qiskit/converter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index d2ca84bff..312ca1fe8 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -586,9 +586,10 @@ def convert_sparse_pauli_op( .. note:: - The order in which Pauli operations appear in each SparsePauliOp term is the reverse of how - they are applied in the PennyLane operator. This is consistent with the iteration order of a - Pauli term in Qiskit. + The wire ordering convention differs between PennyLane and Qiskit: PennyLane wires are + enumerated from left to right, while the Qiskit convention is to enumerate from right to + left. A ``SparsePauliOp`` term defined by the string ``"XYZ"`` applies ``Z`` on wire 0 + ``Y`` on wire 1, and ``X`` on wire 2. **Example** From 38b96e2a88ff2ba7d4fe9d8061b295c56fbb1330 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 14:54:34 -0500 Subject: [PATCH 13/21] Add 'Usage Details' section to docstring --- pennylane_qiskit/converter.py | 61 ++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 312ca1fe8..e7c2a3f97 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -597,27 +597,58 @@ def convert_sparse_pauli_op( .. code-block:: python - import numpy as np - from qiskit.circuit import Parameter from qiskit.quantum_info import SparsePauliOp - a, b, c = [Parameter(var) for var in "abc"] - qiskit_op = SparsePauliOp(["II", "XZ", "YX"], coeffs=np.array([a, b, c])) + qiskit_op = SparsePauliOp(["II", "XY"]) - The ``SparsePauliOp`` has three coefficients (parameters): + The ``SparsePauliOp`` contains two terms and acts over two qubits: >>> qiskit_op - SparsePauliOp(['II', 'XZ', 'YX'], - coeffs=[ParameterExpression(1.0*a), ParameterExpression(1.0*b), - ParameterExpression(1.0*c)]) + SparsePauliOp(['II', 'XY'], + coeffs=[1.+0.j, 1.+0.j]) - Now, to convert the ``SparsePauliOp`` into a PennyLane operator, run: + To convert the ``SparsePauliOp`` into a PennyLane operator, use: >>> from pennylane_qiskit.converter import convert_sparse_pauli_op - >>> convert_sparse_pauli_op(qiskit_op, params={a: 2, b: 3, c: 4}) - ((2+0j)*(Identity(wires=[0, 1]))) - + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) - + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) + >>> convert_sparse_pauli_op(qiskit_op) + Identity(wires=[0, 1]) + (PauliX(wires=[1]) @ PauliY(wires=[0])) + + .. details:: + :title: Usage Details + + You can convert a parameterized ``SparsePauliOp`` into a PennyLane operator by assigning + literal values to each coefficient parameter. For example, the script + + .. code-block:: python + + import numpy as np + from qiskit.circuit import Parameter + + a, b, c = [Parameter(var) for var in "abc"] + param_qiskit_op = SparsePauliOp(["II", "XZ", "YX"], coeffs=np.array([a, b, c])) + + defines a ``SparsePauliOp`` with three coefficients (parameters): + + >>> param_qiskit_op + SparsePauliOp(['II', 'XZ', 'YX'], + coeffs=[ParameterExpression(1.0*a), ParameterExpression(1.0*b), + + The ``SparsePauliOp`` can be converted into a PennyLane operator by calling the conversion + function and specifying the value of each parameter using the ``params`` argument: + + >>> convert_sparse_pauli_op(param_qiskit_op, params={a: 2, b: 3, c: 4}) + ((2+0j)*(Identity(wires=[0, 1]))) + + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) + + Similarly, a custom wire mapping can be applied to a ``SparsePauliOp`` as follows: + + >>> wired_qiskit_op = SparsePauliOp("XYZ") + >>> wired_qiskit_op + SparsePauliOp(['XYZ'], + coeffs=[1.+0.j]) + >>> convert_sparse_pauli_op(wired_qiskit_op, wires=[3, 5, 7]) + PauliY(wires=[5]) @ PauliZ(wires=[3]) @ PauliX(wires=[7]) """ if wires is not None and len(wires) != sparse_op.num_qubits: raise RuntimeError( @@ -640,8 +671,8 @@ def convert_sparse_pauli_op( pl_terms = [] for term in qiskit_terms: - # term is a special Qiskit type. Iterating over the term goes right to left - # in accordance with Qiskit wire order convention, i.e. `enumerate("XZ")` will be + # term is a special Qiskit type. Iterating over the term goes right to left + # in accordance with Qiskit wire order convention, i.e. `enumerate("XZ")` will be # [(0, "Z"), (1, "X")], so we don't need to reverse to match the PL convention operators = [op_map[str(op)](wire_map[wire]) for wire, op in enumerate(term)] pl_terms.append(qml.prod(*operators).simplify()) From 51f6f7102bd18337197e5ba454e78b7ed3e8f94e Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 14:55:53 -0500 Subject: [PATCH 14/21] Rename function to `load_pauli_op()` --- pennylane_qiskit/converter.py | 10 +++++----- tests/test_converter.py | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index e7c2a3f97..6cac90006 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -565,7 +565,7 @@ def load_qasm_from_file(file: str): return load(QuantumCircuit.from_qasm_file(file)) -def convert_sparse_pauli_op( +def load_pauli_op( sparse_op: SparsePauliOp, params: Any = None, wires: Union[Sequence, None] = None, @@ -609,8 +609,8 @@ def convert_sparse_pauli_op( To convert the ``SparsePauliOp`` into a PennyLane operator, use: - >>> from pennylane_qiskit.converter import convert_sparse_pauli_op - >>> convert_sparse_pauli_op(qiskit_op) + >>> from pennylane_qiskit.converter import load_pauli_op + >>> load_pauli_op(qiskit_op) Identity(wires=[0, 1]) + (PauliX(wires=[1]) @ PauliY(wires=[0])) .. details:: @@ -636,7 +636,7 @@ def convert_sparse_pauli_op( The ``SparsePauliOp`` can be converted into a PennyLane operator by calling the conversion function and specifying the value of each parameter using the ``params`` argument: - >>> convert_sparse_pauli_op(param_qiskit_op, params={a: 2, b: 3, c: 4}) + >>> load_pauli_op(param_qiskit_op, params={a: 2, b: 3, c: 4}) ((2+0j)*(Identity(wires=[0, 1]))) + ((3+0j)*(PauliX(wires=[1]) @ PauliZ(wires=[0]))) + ((4+0j)*(PauliY(wires=[1]) @ PauliX(wires=[0]))) @@ -647,7 +647,7 @@ def convert_sparse_pauli_op( >>> wired_qiskit_op SparsePauliOp(['XYZ'], coeffs=[1.+0.j]) - >>> convert_sparse_pauli_op(wired_qiskit_op, wires=[3, 5, 7]) + >>> load_pauli_op(wired_qiskit_op, wires=[3, 5, 7]) PauliY(wires=[5]) @ PauliZ(wires=[3]) @ PauliX(wires=[7]) """ if wires is not None and len(wires) != sparse_op.num_qubits: diff --git a/tests/test_converter.py b/tests/test_converter.py index bd498746a..5bef74a57 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -11,7 +11,7 @@ import pennylane as qml from pennylane import numpy as np from pennylane_qiskit.converter import ( - convert_sparse_pauli_op, + load_pauli_op, load, load_qasm, load_qasm_from_file, @@ -1675,8 +1675,8 @@ def circuit_loaded_qiskit_circuit(): assert circuit_loaded_qiskit_circuit() == circuit_native_pennylane() -class TestConvertSparsePauliOp: - """Tests for the :func:`convert_sparse_pauli_op()` function.""" +class TestLoadPauliOp: + """Tests for the :func:`load_pauli_op()` function.""" @pytest.mark.parametrize( "sparse_pauli_op, want_op", @@ -1702,7 +1702,7 @@ def test_convert_with_default_coefficients(self, sparse_pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with the default coefficients. """ - have_op = convert_sparse_pauli_op(sparse_pauli_op) + have_op = load_pauli_op(sparse_pauli_op) assert qml.equal(have_op, want_op) @pytest.mark.parametrize( @@ -1725,7 +1725,7 @@ def test_convert_with_literal_coefficients(self, sparse_pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with literal coefficient values. """ - have_op = convert_sparse_pauli_op(sparse_pauli_op) + have_op = load_pauli_op(sparse_pauli_op) assert qml.equal(have_op, want_op) @@ -1736,7 +1736,7 @@ def test_convert_with_parameter_coefficients(self): a, b = [Parameter(var) for var in "ab"] sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = convert_sparse_pauli_op(sparse_pauli_op, params={a: 3, b: 7}) + have_op = load_pauli_op(sparse_pauli_op, params={a: 3, b: 7}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1755,7 +1755,7 @@ def test_convert_too_few_coefficients(self): r"\[\(3\+0j\) ParameterExpression\(1\.0\*b\)\]" ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op(sparse_pauli_op, params={a: 3}) + load_pauli_op(sparse_pauli_op, params={a: 3}) def test_convert_too_many_coefficients(self): """Tests that a SparsePauliOp can be converted into a PennyLane operator by assigning values @@ -1764,7 +1764,7 @@ def test_convert_too_many_coefficients(self): a, b, c = [Parameter(var) for var in "abc"] sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = convert_sparse_pauli_op(sparse_pauli_op, params={a: 3, b: 7, c: 9}) + have_op = load_pauli_op(sparse_pauli_op, params={a: 3, b: 7, c: 9}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1791,7 +1791,7 @@ def test_convert_too_many_coefficients(self): ) def test_convert_with_wires(self, sparse_pauli_op, wires, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with custom wires.""" - have_op = convert_sparse_pauli_op(sparse_pauli_op, wires=wires) + have_op = load_pauli_op(sparse_pauli_op, wires=wires) assert qml.equal(have_op, want_op) def test_convert_with_too_few_wires(self): @@ -1803,7 +1803,7 @@ def test_convert_with_too_few_wires(self): f"the number of qubits the SparsePauliOp acts on." ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op(SparsePauliOp("II"), wires=[0]) + load_pauli_op(SparsePauliOp("II"), wires=[0]) def test_convert_with_too_many_wires(self): """Tests that a RuntimeError is raised if an attempt is made to convert a SparsePauliOp into @@ -1814,4 +1814,4 @@ def test_convert_with_too_many_wires(self): f"the number of qubits the SparsePauliOp acts on." ) with pytest.raises(RuntimeError, match=match): - convert_sparse_pauli_op(SparsePauliOp("II"), wires=[0, 1, 2]) + load_pauli_op(SparsePauliOp("II"), wires=[0, 1, 2]) From 867024b85886b7c9aa16b04a8b1040072adcbeef Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 15:00:10 -0500 Subject: [PATCH 15/21] Rename `sparse_op` to `pauli_op` --- pennylane_qiskit/converter.py | 16 ++++++++-------- tests/test_converter.py | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index 6cac90006..985fe408f 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -566,14 +566,14 @@ def load_qasm_from_file(file: str): def load_pauli_op( - sparse_op: SparsePauliOp, + pauli_op: SparsePauliOp, params: Any = None, wires: Union[Sequence, None] = None, ) -> qml.operation.Operator: """Converts a Qiskit SparsePauliOp into a PennyLane operator. Args: - sparse_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted + pauli_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted params (Any): optional assignment of coefficient values for the SparsePauliOp; see the `Qiskit documentation `__ to learn more about the expected format of these parameters @@ -650,30 +650,30 @@ def load_pauli_op( >>> load_pauli_op(wired_qiskit_op, wires=[3, 5, 7]) PauliY(wires=[5]) @ PauliZ(wires=[3]) @ PauliX(wires=[7]) """ - if wires is not None and len(wires) != sparse_op.num_qubits: + if wires is not None and len(wires) != pauli_op.num_qubits: raise RuntimeError( f"The specified number of wires - {len(wires)} - does not match the " f"number of qubits the SparsePauliOp acts on." ) - wire_map = map_wires(range(sparse_op.num_qubits), wires) + wire_map = map_wires(range(pauli_op.num_qubits), wires) if params: - sparse_op = sparse_op.assign_parameters(params) + pauli_op = pauli_op.assign_parameters(params) op_map = {"X": qml.PauliX, "Y": qml.PauliY, "Z": qml.PauliZ, "I": qml.Identity} - coeffs = sparse_op.coeffs + coeffs = pauli_op.coeffs if ParameterExpression in [type(c) for c in coeffs]: raise RuntimeError(f"Not all parameter expressions are assigned in coeffs {coeffs}") - qiskit_terms = sparse_op.paulis + qiskit_terms = pauli_op.paulis pl_terms = [] for term in qiskit_terms: # term is a special Qiskit type. Iterating over the term goes right to left # in accordance with Qiskit wire order convention, i.e. `enumerate("XZ")` will be - # [(0, "Z"), (1, "X")], so we don't need to reverse to match the PL convention + # [(0, "Z"), (1, "X")], so we don't need to reverse to match the PL convention. operators = [op_map[str(op)](wire_map[wire]) for wire, op in enumerate(term)] pl_terms.append(qml.prod(*operators).simplify()) diff --git a/tests/test_converter.py b/tests/test_converter.py index 5bef74a57..0c0e6c4fd 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -1679,7 +1679,7 @@ class TestLoadPauliOp: """Tests for the :func:`load_pauli_op()` function.""" @pytest.mark.parametrize( - "sparse_pauli_op, want_op", + "pauli_op, want_op", [ ( SparsePauliOp("I"), @@ -1698,15 +1698,15 @@ class TestLoadPauliOp: ), ] ) - def test_convert_with_default_coefficients(self, sparse_pauli_op, want_op): + def test_convert_with_default_coefficients(self, pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with the default coefficients. """ - have_op = load_pauli_op(sparse_pauli_op) + have_op = load_pauli_op(pauli_op) assert qml.equal(have_op, want_op) @pytest.mark.parametrize( - "sparse_pauli_op, want_op", + "pauli_op, want_op", [ ( SparsePauliOp("I", coeffs=[2]), @@ -1721,11 +1721,11 @@ def test_convert_with_default_coefficients(self, sparse_pauli_op, want_op): ), ] ) - def test_convert_with_literal_coefficients(self, sparse_pauli_op, want_op): + def test_convert_with_literal_coefficients(self, pauli_op, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with literal coefficient values. """ - have_op = load_pauli_op(sparse_pauli_op) + have_op = load_pauli_op(pauli_op) assert qml.equal(have_op, want_op) @@ -1734,9 +1734,9 @@ def test_convert_with_parameter_coefficients(self): to each parameterized coefficient. """ a, b = [Parameter(var) for var in "ab"] - sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = load_pauli_op(sparse_pauli_op, params={a: 3, b: 7}) + have_op = load_pauli_op(pauli_op, params={a: 3, b: 7}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1748,23 +1748,23 @@ def test_convert_too_few_coefficients(self): a PennyLane operator without assigning values for all parameterized coefficients. """ a, b = [Parameter(var) for var in "ab"] - sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) match = ( "Not all parameter expressions are assigned in coeffs " r"\[\(3\+0j\) ParameterExpression\(1\.0\*b\)\]" ) with pytest.raises(RuntimeError, match=match): - load_pauli_op(sparse_pauli_op, params={a: 3}) + load_pauli_op(pauli_op, params={a: 3}) def test_convert_too_many_coefficients(self): """Tests that a SparsePauliOp can be converted into a PennyLane operator by assigning values to a strict superset of the parameterized coefficients. """ a, b, c = [Parameter(var) for var in "abc"] - sparse_pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) + pauli_op = SparsePauliOp(["XY", "ZX"], coeffs=[a, b]) - have_op = load_pauli_op(sparse_pauli_op, params={a: 3, b: 7, c: 9}) + have_op = load_pauli_op(pauli_op, params={a: 3, b: 7, c: 9}) want_op = qml.sum( qml.s_prod(3, qml.prod(qml.PauliX(wires=1), qml.PauliY(wires=0))), qml.s_prod(7, qml.prod(qml.PauliZ(wires=1), qml.PauliX(wires=0))), @@ -1772,7 +1772,7 @@ def test_convert_too_many_coefficients(self): assert qml.equal(have_op, want_op) @pytest.mark.parametrize( - "sparse_pauli_op, wires, want_op", + "pauli_op, wires, want_op", [ ( SparsePauliOp("XYZ"), @@ -1789,9 +1789,9 @@ def test_convert_too_many_coefficients(self): ), ] ) - def test_convert_with_wires(self, sparse_pauli_op, wires, want_op): + def test_convert_with_wires(self, pauli_op, wires, want_op): """Tests that a SparsePauliOp can be converted into a PennyLane operator with custom wires.""" - have_op = load_pauli_op(sparse_pauli_op, wires=wires) + have_op = load_pauli_op(pauli_op, wires=wires) assert qml.equal(have_op, want_op) def test_convert_with_too_few_wires(self): From 22c02b92bae18301501af2391839c6ab73361e80 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 15:04:43 -0500 Subject: [PATCH 16/21] Fix import ordering --- pennylane_qiskit/__init__.py | 2 +- tests/test_converter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane_qiskit/__init__.py b/pennylane_qiskit/__init__.py index 20afa07b3..1c364a1c1 100644 --- a/pennylane_qiskit/__init__.py +++ b/pennylane_qiskit/__init__.py @@ -18,6 +18,6 @@ from .basic_aer import BasicAerDevice from .ibmq import IBMQDevice from .remote import RemoteDevice -from .converter import load, load_qasm, load_qasm_from_file, convert_sparse_pauli_op +from .converter import load, load_pauli_op, load_qasm, load_qasm_from_file from .runtime_devices import IBMQCircuitRunnerDevice from .runtime_devices import IBMQSamplerDevice diff --git a/tests/test_converter.py b/tests/test_converter.py index bd7c6dc6a..3917582dc 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -12,8 +12,8 @@ import pennylane as qml from pennylane import numpy as np from pennylane_qiskit.converter import ( - load_pauli_op, load, + load_pauli_op, load_qasm, load_qasm_from_file, map_wires, From b3f229f3eb193a5b1c6de2bf4ac3077f07913500 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Thu, 22 Feb 2024 16:04:56 -0500 Subject: [PATCH 17/21] Add PennyLane entry point for loading Qiskit operators --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5899470a4..d53fda25e 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ ], 'pennylane.io': [ 'qiskit = pennylane_qiskit:load', + 'qiskit_op = pennylane_qiskit:load_pauli_op', 'qasm = pennylane_qiskit:load_qasm', 'qasm_file = pennylane_qiskit:load_qasm_from_file', ], From d11e20282227932cb072a90181cb201f85bc1048 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 23 Feb 2024 12:22:27 -0500 Subject: [PATCH 18/21] Add `SparsePauliOp` notes to changelog --- CHANGELOG.md | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b84f7160..ddd86ff07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,15 @@ ### Improvements ๐Ÿ›  -* The UI for passing parameters to a ``qfunc`` generated when loading a Qiskit ``QuantumCircuit`` - into PennyLane is updated to allow passing parameters as args or kwargs, rather than as +* The UI for passing parameters to a ``qfunc`` generated when loading a Qiskit ``QuantumCircuit`` + into PennyLane is updated to allow passing parameters as args or kwargs, rather than as a dictionary. The old dictionary UI continues to be supported. [(#406)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/406) [(#428)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/428) * Measurement operations are now added to the PennyLane template when a ``QuantumCircuit`` is converted using `load`. Additionally, one can override any existing terminal - measurements by providing a list of PennyLane + measurements by providing a list of PennyLane `measurements `_ themselves. [(#405)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/405) @@ -29,6 +29,13 @@ ``Barrier``, ``CYGate``, ``CHGate``, ``CPhase``, ``CCZGate``, ``ECRGate``, and ``GlobalPhaseGate``. [(#449)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/449) +* Added the ability to convert a Qiskit `SparsePauliOp` instance into a PennyLane `Operator`. + [(#401)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/401) + +* Added a `pennylane.io` entry point for converting Qiskit operators. + [(#453)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/453) + + ### Breaking changes ๐Ÿ’” ### Deprecations ๐Ÿ‘‹ @@ -41,6 +48,7 @@ This release contains contributions from (in alphabetical order): +Mikhail Andrenkov Utkarsh Azad Lillian Frederiksen @@ -49,18 +57,18 @@ Lillian Frederiksen ### Bug fixes ๐Ÿ› -* The kwargs `job_tags` and `session_id` are passed to the correct arguments in the - `circuit_runner` device so that they will be used in the Qiskit backend; these +* The kwargs `job_tags` and `session_id` are passed to the correct arguments in the + `circuit_runner` device so that they will be used in the Qiskit backend; these were previously ignored. [(#358)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/358) -* The `generate_samples` method for the `IBMQSamplerDevice` is updated to get counts - from the nearest probability distribution rather than the quasi-distribution (which - may contain negative probabilities and therefore raise errors). +* The `generate_samples` method for the `IBMQSamplerDevice` is updated to get counts + from the nearest probability distribution rather than the quasi-distribution (which + may contain negative probabilities and therefore raise errors). [(#357)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/357) - -* The `generate_samples` method for the `IBMQSamplerDevice` now avoids raising an - indexing error when some states are not populated, and labels states according to + +* The `generate_samples` method for the `IBMQSamplerDevice` now avoids raising an + indexing error when some states are not populated, and labels states according to the Pennylane convention instead of Qiskit convention. [(#357)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/357) @@ -83,7 +91,7 @@ Francesco Scala ### Bug fixes ๐Ÿ› -* Update conversion of PennyLane to Qiskit operators to accommodate +* Update conversion of PennyLane to Qiskit operators to accommodate the addition of Singleton classes in the newest version of Qiskit. [(#347)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/347) @@ -315,10 +323,10 @@ Mikhail Andrenkov, Christina Lee, Romain Moyard, Antal Szรกva * Fix runtime sampler due to changes on Qiskit side. [(#201)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/201) - + * Pin `jinja2` to 3.0.3 because of sphinx incompatibility. [(#207)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/207) - + ### Contributors This release contains contributions from (in alphabetical order): @@ -326,7 +334,7 @@ This release contains contributions from (in alphabetical order): Samuel Banning, Romain Moyard --- - + # Release 0.22.0 ### Improvements @@ -334,7 +342,7 @@ Samuel Banning, Romain Moyard * Changed a validation check such that it handles qubit numbers represented as strings. [(#184)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/184) - + * Changed the VQE callback function for SciPy optimizers. [(#187)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/187) @@ -346,7 +354,7 @@ Samuel Banning, Romain Moyard * Changed the access to Hamiltonian terms `hamiltonian.terms()` as a method. [(#190)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/190) - + ### Contributors This release contains contributions from (in alphabetical order): From 3ed49bacb5a2d6eac9c4259a05b555572d9cd794 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 23 Feb 2024 16:03:46 -0500 Subject: [PATCH 19/21] Add missing check for `pauli_op` type --- pennylane_qiskit/converter.py | 3 +++ tests/test_converter.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index a04976800..a9a738469 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -640,6 +640,9 @@ def load_pauli_op( >>> load_pauli_op(wired_qiskit_op, wires=[3, 5, 7]) Y(5) @ Z(3) @ X(7) """ + if not isinstance(pauli_op, SparsePauliOp): + raise ValueError(f"The operator {pauli_op} is not a valid Qiskit SparsePauliOp.") + if wires is not None and len(wires) != pauli_op.num_qubits: raise RuntimeError( f"The specified number of wires - {len(wires)} - does not match the " diff --git a/tests/test_converter.py b/tests/test_converter.py index 5fc8ca3ef..8e66fd2e2 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -1937,3 +1937,11 @@ def test_convert_with_too_many_wires(self): ) with pytest.raises(RuntimeError, match=match): load_pauli_op(SparsePauliOp("II"), wires=[0, 1, 2]) + + def test_convert_with_invalid_operator(self): + """Tests that a ValueError is raised if an attempt is made to convert an object which is not + a SparsePauliOp into a PennyLane operator. + """ + match = r"The operator 123 is not a valid Qiskit SparsePauliOp\." + with pytest.raises(ValueError, match=match): + load_pauli_op(123) From 91664b7c9764b77ee796daaf5c0ea98dbb40d168 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 23 Feb 2024 16:04:05 -0500 Subject: [PATCH 20/21] Synchronize docstrings between PennyLane and plugin --- pennylane_qiskit/converter.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pennylane_qiskit/converter.py b/pennylane_qiskit/converter.py index a9a738469..3fdcf5af3 100644 --- a/pennylane_qiskit/converter.py +++ b/pennylane_qiskit/converter.py @@ -557,16 +557,17 @@ def load_pauli_op( params: Any = None, wires: Union[Sequence, None] = None, ) -> qml.operation.Operator: - """Loads a PennyLane operator from a Qiskit SparsePauliOp. + """Loads a PennyLane ``Operator`` from a Qiskit `SparsePauliOp + `_. Args: - pauli_op (qiskit.quantum_info.SparsePauliOp): the SparsePauliOp to be converted - params (Any): optional assignment of coefficient values for the SparsePauliOp; see the + pauli_op (qiskit.quantum_info.SparsePauliOp): the ``SparsePauliOp`` to be converted + params (Any): optional assignment of coefficient values for the ``SparsePauliOp``; see the `Qiskit documentation `__ to learn more about the expected format of these parameters - wires (Sequence | None): optional assignment of wires for the converted SparsePauliOp; if - the original SparsePauliOp acted on :math:`N` qubits, then this must be a sequence of - length :math:`N` + wires (Sequence | None): optional assignment of wires for the converted ``SparsePauliOp``; + if the original ``SparsePauliOp`` acted on :math:`N` qubits, then this must be a + sequence of length :math:`N` Returns: pennylane.operation.Operator: The equivalent PennyLane operator. @@ -575,8 +576,10 @@ def load_pauli_op( The wire ordering convention differs between PennyLane and Qiskit: PennyLane wires are enumerated from left to right, while the Qiskit convention is to enumerate from right to - left. A ``SparsePauliOp`` term defined by the string ``"XYZ"`` applies ``Z`` on wire 0, - ``Y`` on wire 1, and ``X`` on wire 2. + left. This means a ``SparsePauliOp`` term defined by the string ``"XYZ"`` applies ``Z`` on + wire 0, ``Y`` on wire 1, and ``X`` on wire 2. For more details, see the + `String representation `_ + section of the Qiskit documentation for the ``Pauli`` class. **Example** @@ -594,7 +597,7 @@ def load_pauli_op( SparsePauliOp(['II', 'XY'], coeffs=[1.+0.j, 1.+0.j]) - To convert the ``SparsePauliOp`` into a PennyLane operator, use: + To convert the ``SparsePauliOp`` into a PennyLane ``Operator``, use: >>> from pennylane_qiskit import load_pauli_op >>> load_pauli_op(qiskit_op) From 28b41d0118f0ca194164dc2444d34b87c49b0039 Mon Sep 17 00:00:00 2001 From: Mikhail Andrenkov Date: Fri, 23 Feb 2024 16:07:26 -0500 Subject: [PATCH 21/21] Tag PR in existing changelog note --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 755d554e3..e45e06ece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ * Added the ability to convert a Qiskit `SparsePauliOp` instance into a PennyLane `Operator`. [(#401)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/401) + [(#453)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/453) * Added a `pennylane.io` entry point for converting Qiskit operators. [(#453)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/453)