Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SparsePauliOp converter #401

Merged
merged 23 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a4c59d3
save draft of SparsePauliOp conversion
lillian542 Feb 5, 2024
09cdefe
Merge branch 'master' into sparse_pauli_op
lillian542 Feb 14, 2024
1b021d3
Add docstring to `convert_sparse_pauli_op_to_pl()`
Mandrenkov Feb 21, 2024
1891c48
Add tests for `convert_sparse_pauli_op_to_pl()`
Mandrenkov Feb 21, 2024
8cbe0e7
Synchronize `qiskit` and `qiskit-terra` requirements
Mandrenkov Feb 21, 2024
717f169
Merge branch 'master' into sparse_pauli_op
Mandrenkov Feb 21, 2024
53fe08d
Remove unused requirement `tweedledum`
Mandrenkov Feb 22, 2024
3c66e1a
Add `wires` argument to `convert_sparse_pauli_op_to_pl()`
Mandrenkov Feb 22, 2024
16269f8
Swap order of `params` and `wires` arguments
Mandrenkov Feb 22, 2024
531e845
Merge branch 'master' into sparse_pauli_op
Mandrenkov Feb 22, 2024
e47512b
Pass `coeffs` by keyword argument in docstring example
Mandrenkov Feb 22, 2024
3d494bb
Pass `params` by keyword argument in example
Mandrenkov Feb 22, 2024
abe6782
Add comment about wire ordering to Qiskit term `for` loop
Mandrenkov Feb 22, 2024
c528969
Rename function to `convert_sparse_pauli_op()`
Mandrenkov Feb 22, 2024
ad1e351
Reword note according to PR suggestion
Mandrenkov Feb 22, 2024
38b96e2
Add 'Usage Details' section to docstring
Mandrenkov Feb 22, 2024
51f6f71
Rename function to `load_pauli_op()`
Mandrenkov Feb 22, 2024
867024b
Rename `sparse_op` to `pauli_op`
Mandrenkov Feb 22, 2024
f3f745c
Merge branch 'master' into sparse_pauli_op
Mandrenkov Feb 22, 2024
22c02b9
Fix import ordering
Mandrenkov Feb 22, 2024
b33a627
Use output from latest version of PennyLane
Mandrenkov Feb 22, 2024
e7007f2
Add missing comma to docstring note
Mandrenkov Feb 22, 2024
9e35225
Replace 'Converts' with 'Loads' in docstring
Mandrenkov Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions pennylane_qiskit/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from qiskit.circuit.controlflow.switch_case import _DefaultCaseType
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
Expand Down Expand Up @@ -562,6 +563,74 @@ def load_qasm_from_file(file: str):
return load(QuantumCircuit.from_qasm_file(file))


def convert_sparse_pauli_op_to_pl(
Mandrenkov marked this conversation as resolved.
Show resolved Hide resolved
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 <https://docs.quantum.ibm.com/api/qiskit/qiskit.quantum_info.SparsePauliOp#assign_parameters>`__
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.
Mandrenkov marked this conversation as resolved.
Show resolved Hide resolved

**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]))
Mandrenkov marked this conversation as resolved.
Show resolved Hide resolved

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})
Mandrenkov marked this conversation as resolved.
Show resolved Hide resolved
((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}

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:
Mandrenkov marked this conversation as resolved.
Show resolved Hide resolved
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)


# pylint:disable=fixme, protected-access
def _conditional_funcs(ops, cargs, operation_class, branch_funcs, ctrl_flow_type):
"""Builds the conditional functions for Controlled flows
Expand Down
104 changes: 101 additions & 3 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import math
import sys

import pytest
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.circuit import library as lib
from qiskit.circuit import Parameter, ParameterVector
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators import Operator
from qiskit.circuit.library import DraperQFTAdder
from qiskit.exceptions import QiskitError
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,
Expand Down Expand Up @@ -1672,3 +1673,100 @@ def circuit_loaded_qiskit_circuit():
return qml.expval(qml.PauliZ(0))

assert circuit_loaded_qiskit_circuit() == circuit_native_pennylane()


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)
Loading