-
Notifications
You must be signed in to change notification settings - Fork 631
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
Qualtran-pl interoperability #6921
Open
austingmhuang
wants to merge
41
commits into
master
Choose a base branch
from
qualtran_pl
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+374
−2
Open
Changes from 40 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
e9dd736
initial draft
austingmhuang 0c1caf7
prototype
austingmhuang f68c56d
edge case fix
austingmhuang 0ca5cd0
hacky way to work for atomic gates
austingmhuang ab573bc
Merge branch 'master' into qualtran_pl
Jaybsoni 025add2
fix
austingmhuang 68bb8ba
small things
austingmhuang 2e45299
black
austingmhuang d4dfd6e
some comments
austingmhuang e2039ed
add another todo
austingmhuang 1acb8ae
Merge branch 'master' into qualtran_pl
austingmhuang 3f7d882
flatten is not ideal
austingmhuang df5cf90
ask about recursive solution in cirq
austingmhuang 8cf199a
small fix
austingmhuang 30e01e4
use as_pl_op
austingmhuang a241dc4
temporary
austingmhuang 66ac6cd
ready for rev
austingmhuang 984629d
codefactor
austingmhuang e7f86a7
codefactor
austingmhuang 287b59a
Merge branch 'master' into qualtran_pl
austingmhuang 552635c
tests
austingmhuang 675bca9
small tests
austingmhuang 3bd0cd6
yay
austingmhuang 4ce5298
black
austingmhuang 59ea823
Merge branch 'master' into qualtran_pl
austingmhuang 9c128c2
repr test
austingmhuang f9398ac
fix bug
austingmhuang f233754
helper function
austingmhuang 38543ef
example
austingmhuang 7a5eb2d
black
austingmhuang 5a90c98
black/pylint
austingmhuang 7ec5144
black/pylint
austingmhuang af0aa26
Merge branch 'master' into qualtran_pl
austingmhuang 2b32ebf
one more test
austingmhuang 9d6f390
one more test
austingmhuang b18e6bb
one more test
austingmhuang c9f67e3
temporary fix
austingmhuang 2a7b23f
temporary fix
austingmhuang b1cd809
one more test
austingmhuang a8355a0
one more complicated test
austingmhuang 1937dff
fix to handle allocate
austingmhuang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
""" | ||
This module contains functions to load circuits from other frameworks as | ||
PennyLane templates. | ||
""" | ||
|
||
from .io import * | ||
from .qualtran_io import * |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,188 @@ | ||||||||
# Copyright 2018-2025 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 submodule contains the adapter class for Qualtran-PennyLane interoperability. | ||||||||
""" | ||||||||
# pylint:disable= | ||||||||
from qualtran import ( | ||||||||
Bloq, | ||||||||
CompositeBloq, | ||||||||
Soquet, | ||||||||
LeftDangle, | ||||||||
Side, | ||||||||
DecomposeNotImplementedError, | ||||||||
DecomposeTypeError, | ||||||||
) | ||||||||
|
||||||||
import numpy as np | ||||||||
import pennylane as qml | ||||||||
|
||||||||
from pennylane.operation import Operation | ||||||||
from pennylane.wires import WiresLike | ||||||||
|
||||||||
|
||||||||
def get_bloq_registers_info(bloq): | ||||||||
"""Returns a `qml.registers` object associated with all named and unnamed registers and wires | ||||||||
in the bloq. | ||||||||
|
||||||||
Args: | ||||||||
bloq: the bloq to get the registers info of | ||||||||
|
||||||||
Returns: | ||||||||
dict: A dictionary that has all the named and un-named registers with default wire | ||||||||
ordering. | ||||||||
|
||||||||
**Example** | ||||||||
|
||||||||
Given a qualtran bloq: | ||||||||
|
||||||||
from qualtran.bloqs.basic_gates import Swap | ||||||||
|
||||||||
>>> qml.get_bloq_registers_info(Swap(3)) | ||||||||
{'x': Wires([0, 1, 2]), 'y': Wires([3, 4, 5])} | ||||||||
""" | ||||||||
|
||||||||
cbloq = bloq.decompose_bloq() if not isinstance(bloq, CompositeBloq) else bloq | ||||||||
|
||||||||
temp_register_dict = {} | ||||||||
for reg in cbloq.signature.rights(): | ||||||||
temp_register_dict[reg.name] = reg.bitsize | ||||||||
|
||||||||
return qml.registers(temp_register_dict) | ||||||||
|
||||||||
|
||||||||
def _get_named_registers(registers): | ||||||||
"""Returns a `qml.registers` object associated with the named registers in the bloq""" | ||||||||
|
||||||||
temp_register_dict = {} | ||||||||
for reg in registers: | ||||||||
temp_register_dict[reg.name] = reg.bitsize | ||||||||
|
||||||||
return qml.registers(temp_register_dict) | ||||||||
|
||||||||
|
||||||||
class FromBloq(Operation): | ||||||||
r""" | ||||||||
A shim for using bloqs as a PennyLane operation. | ||||||||
Comment on lines
+76
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to use an adapter instead of a shim -
Suggested change
|
||||||||
|
||||||||
Args: | ||||||||
bloq: the bloq to wrap | ||||||||
wires: the wires to act on | ||||||||
|
||||||||
**Example** | ||||||||
|
||||||||
Given a qualtran bloq: | ||||||||
|
||||||||
from qualtran.bloqs.basic_gates import CNOT | ||||||||
|
||||||||
>>> qualtran_toffoli = qml.FromBloq(CNOT(), [0, 1]) | ||||||||
>>> qualtran_toffoli.matrix() | ||||||||
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], | ||||||||
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j], | ||||||||
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j], | ||||||||
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]]) | ||||||||
|
||||||||
A simple example showcasing how to use `qml.FromBloq` inside a device: | ||||||||
|
||||||||
.. code-block:: | ||||||||
|
||||||||
dev = qml.device("default.qubit") | ||||||||
|
||||||||
@qml.qnode(dev) | ||||||||
def circuit(): | ||||||||
qml.FromBloq(XGate(), [0]) | ||||||||
return qml.expval(qml.Z(wires=[0])) | ||||||||
|
||||||||
>>> circuit() | ||||||||
-1.0 | ||||||||
""" | ||||||||
|
||||||||
def __init__(self, bloq: Bloq, wires: WiresLike): | ||||||||
self._hyperparameters = {"bloq": bloq} | ||||||||
super().__init__(wires=wires, id=None) | ||||||||
|
||||||||
def __repr__(self): # pylint: disable=protected-access | ||||||||
return f'FromBloq({self._hyperparameters["bloq"]}, wires={self.wires})' | ||||||||
|
||||||||
def compute_decomposition( | ||||||||
self, wires, **kwargs | ||||||||
): # pylint: disable=arguments-differ, unused-argument | ||||||||
ops = [] | ||||||||
bloq = self._hyperparameters["bloq"] | ||||||||
|
||||||||
try: | ||||||||
cbloq = bloq.decompose_bloq() if not isinstance(bloq, CompositeBloq) else bloq | ||||||||
temp_registers = _get_named_registers(cbloq.signature.lefts()) | ||||||||
soq_to_wires = { | ||||||||
Soquet(LeftDangle, idx=idx, reg=reg): list(temp_registers[reg.name]) | ||||||||
for reg in cbloq.signature.lefts() | ||||||||
for idx in reg.all_idxs() | ||||||||
} | ||||||||
|
||||||||
for binst, pred_cxns, succ_cxns in cbloq.iter_bloqnections(): | ||||||||
in_quregs = { | ||||||||
reg.name: np.empty((*reg.shape, reg.bitsize), dtype=object).flatten() | ||||||||
for reg in binst.bloq.signature.lefts() | ||||||||
} | ||||||||
out_quregs = { | ||||||||
reg.name: np.empty((*reg.shape, reg.bitsize), dtype=object).flatten() | ||||||||
for reg in binst.bloq.signature.rights() | ||||||||
} | ||||||||
|
||||||||
# TODO: list has inconsistent shapes | ||||||||
try: | ||||||||
soq_to_wires_len = list(soq_to_wires.values())[-1][-1] + 1 | ||||||||
except TypeError: | ||||||||
soq_to_wires_len = list(soq_to_wires.values())[-1] + 1 | ||||||||
|
||||||||
for pred in pred_cxns: | ||||||||
soq = pred.right | ||||||||
soq_to_wires[soq] = soq_to_wires[pred.left] | ||||||||
in_quregs[soq.reg.name][soq.idx] = soq_to_wires[soq] | ||||||||
|
||||||||
for succ in succ_cxns: | ||||||||
soq = succ.left | ||||||||
if soq.reg.side == Side.RIGHT: | ||||||||
# If in_quregs is not equal to out_quregs, we insert key, value pair where the key is | ||||||||
# the register name, and the value is the list of wires associated with it | ||||||||
if len(in_quregs) != len(out_quregs) and soq.reg.side == Side.RIGHT: | ||||||||
total_elements = np.prod(soq.reg.shape) * soq.reg.bitsize | ||||||||
ascending_vals = np.arange( | ||||||||
soq_to_wires_len, | ||||||||
total_elements + soq_to_wires_len, | ||||||||
dtype=object, | ||||||||
) | ||||||||
in_quregs[soq.reg.name] = ascending_vals.reshape( | ||||||||
(*soq.reg.shape, soq.reg.bitsize) | ||||||||
) | ||||||||
soq_to_wires[soq] = in_quregs[soq.reg.name][soq.idx] | ||||||||
|
||||||||
total_wires = [w for ws in in_quregs.values() for w in list(ws.flatten())] | ||||||||
|
||||||||
mapped_wires = [] | ||||||||
for idx in total_wires: | ||||||||
mapped_wires.append(wires[idx]) | ||||||||
op = binst.bloq.as_pl_op(mapped_wires) | ||||||||
|
||||||||
if op: | ||||||||
ops.append(op) | ||||||||
except (DecomposeNotImplementedError, DecomposeTypeError): | ||||||||
pass | ||||||||
|
||||||||
return ops | ||||||||
|
||||||||
def compute_matrix(*params, **kwargs): # pylint: disable=unused-argument, no-self-argument | ||||||||
bloq = params[0]._hyperparameters["bloq"] | ||||||||
|
||||||||
return bloq.tensor_contract() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Copyright 2018-2025 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. | ||
""" | ||
Unit tests for the :mod:`pennylane.io.qualtran_io` module. | ||
""" | ||
import numpy as np | ||
import pennylane as qml | ||
|
||
|
||
class TestFromBloq: | ||
"""Test that FromBloq accurately wraps around Bloqs.""" | ||
|
||
def test_repr(self): | ||
"""Tests that FromBloq has the correct __repr__""" | ||
|
||
from qualtran.bloqs.basic_gates import XGate | ||
|
||
assert qml.FromBloq(XGate(), 1).__repr__() == "FromBloq(XGate, wires=Wires([1]))" | ||
|
||
def test_composite_bloq_advanced(self): | ||
"""Tests that a composite bloq with higher level abstract bloqs has the correct | ||
decomposition after wrapped with `FromBloq`""" | ||
from qualtran import BloqBuilder | ||
from qualtran import QUInt | ||
from qualtran.bloqs.arithmetic import Product, Add | ||
from pennylane.wires import Wires | ||
|
||
bb = BloqBuilder() | ||
|
||
w1 = bb.add_register("p1", 3) | ||
w2 = bb.add_register("p2", 3) | ||
w3 = bb.add_register("q1", 3) | ||
w4 = bb.add_register("q2", 3) | ||
|
||
w1, w2, res1 = bb.add(Product(3, 3), a=w1, b=w2) | ||
w3, w4, res2 = bb.add(Product(3, 3), a=w3, b=w4) | ||
p1p2, p1p2_plus_q1q2 = bb.add(Add(QUInt(bitsize=6), QUInt(bitsize=6)), a=res1, b=res2) | ||
|
||
cbloq = bb.finalize(p1=w1, p2=w2, q1=w3, q2=w4, p1p2=p1p2, p1p2_plus_q1q2=p1p2_plus_q1q2) | ||
|
||
expected = [ | ||
qml.FromBloq(Product(3, 3), wires=Wires([0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16, 17])), | ||
qml.FromBloq(Product(3, 3), wires=Wires([6, 7, 8, 9, 10, 11, 18, 19, 20, 21, 22, 23])), | ||
qml.FromBloq( | ||
Add(QUInt(bitsize=6), QUInt(bitsize=6)), | ||
wires=Wires([12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]), | ||
), | ||
] | ||
assert qml.FromBloq(cbloq, wires=range(24)).decomposition() == expected | ||
|
||
def test_composite_bloq(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider adding a test that compares the unitary matrices
using a non-atomic bloq b |
||
"""Tests that a simple composite bloq has the correct decomposition after wrapped with `FromBloq`""" | ||
from qualtran import BloqBuilder | ||
from qualtran.bloqs.basic_gates import Hadamard, CNOT, Toffoli | ||
|
||
bb = BloqBuilder() # bb is the circuit like object | ||
|
||
w1 = bb.add_register("wire1", 1) | ||
w2 = bb.add_register("wire2", 1) | ||
aux = bb.add_register("aux_wires", 2) | ||
|
||
aux_wires = bb.split(aux) | ||
|
||
w1 = bb.add(Hadamard(), q=w1) | ||
w2 = bb.add(Hadamard(), q=w2) | ||
|
||
w1, aux1 = bb.add(CNOT(), ctrl=w1, target=aux_wires[0]) | ||
w2, aux2 = bb.add(CNOT(), ctrl=w2, target=aux_wires[1]) | ||
|
||
ctrl_aux, w1 = bb.add(Toffoli(), ctrl=(aux1, aux2), target=w1) | ||
ctrl_aux, w2 = bb.add(Toffoli(), ctrl=ctrl_aux, target=w2) | ||
aux_wires = bb.join(ctrl_aux) | ||
|
||
circuit_bloq = bb.finalize(wire1=w1, wire2=w2, aux_wires=aux_wires) | ||
|
||
decomp = qml.FromBloq(circuit_bloq, wires=list(range(4))).decomposition() | ||
expected_decomp = [ | ||
qml.H(0), | ||
qml.H(1), | ||
qml.CNOT([0, 2]), | ||
qml.CNOT([1, 3]), | ||
qml.Toffoli([2, 3, 0]), | ||
qml.Toffoli([2, 3, 1]), | ||
] | ||
assert decomp == expected_decomp | ||
|
||
mapped_decomp = qml.FromBloq(circuit_bloq, wires=[3, 0, 1, 2]).decomposition() | ||
mapped_expected_decomp = [ | ||
qml.H(3), | ||
qml.H(0), | ||
qml.CNOT([3, 1]), | ||
qml.CNOT([0, 2]), | ||
qml.Toffoli([1, 2, 3]), | ||
qml.Toffoli([1, 2, 0]), | ||
] | ||
assert mapped_decomp == mapped_expected_decomp | ||
|
||
def test_atomic_bloqs(self): | ||
"""Tests that atomic bloqs have the correct PennyLane equivalent after wrapped with `FromBloq`""" | ||
from qualtran.bloqs.basic_gates import Hadamard, CNOT, Toffoli | ||
|
||
assert Hadamard().as_pl_op(0) == qml.Hadamard(0) | ||
assert CNOT().as_pl_op([0, 1]) == qml.CNOT([0, 1]) | ||
assert Toffoli().as_pl_op([0, 1, 2]) == qml.Toffoli([0, 1, 2]) | ||
|
||
assert np.allclose(qml.FromBloq(Hadamard(), 0).matrix(), qml.Hadamard(0).matrix()) | ||
assert np.allclose(qml.FromBloq(CNOT(), [0, 1]).matrix(), qml.CNOT([0, 1]).matrix()) | ||
assert np.allclose( | ||
qml.FromBloq(Toffoli(), [0, 1, 2]).matrix(), qml.Toffoli([0, 1, 2]).matrix() | ||
) | ||
|
||
def test_bloqs(self): | ||
"""Tests that bloqs with decompositions have the correct PennyLane decompositions after | ||
being wrapped with `FromBloq`""" | ||
|
||
from qualtran.bloqs.basic_gates import Swap | ||
|
||
assert qml.FromBloq(Swap(3), wires=range(6)).decomposition() == [ | ||
qml.SWAP(wires=[0, 3]), | ||
qml.SWAP(wires=[1, 4]), | ||
qml.SWAP(wires=[2, 5]), | ||
] | ||
|
||
def test_get_bloq_registers_info(self): | ||
"""Tests that get_bloq_registers_info returns the expected dictionary with the correct | ||
registers and wires.""" | ||
|
||
from qualtran import BloqBuilder | ||
from qualtran import QUInt | ||
from qualtran.bloqs.arithmetic import Product, Add | ||
from pennylane.wires import Wires | ||
|
||
bb = BloqBuilder() | ||
|
||
w1 = bb.add_register("p1", 3) | ||
w2 = bb.add_register("p2", 3) | ||
w3 = bb.add_register("q1", 3) | ||
w4 = bb.add_register("q2", 3) | ||
|
||
w1, w2, res1 = bb.add(Product(3, 3), a=w1, b=w2) | ||
w3, w4, res2 = bb.add(Product(3, 3), a=w3, b=w4) | ||
p1p2, p1p2_plus_q1q2 = bb.add(Add(QUInt(bitsize=6), QUInt(bitsize=6)), a=res1, b=res2) | ||
|
||
circuit_bloq = bb.finalize( | ||
p1=w1, p2=w2, q1=w3, q2=w4, p1p2=p1p2, p1p2_plus_q1q2=p1p2_plus_q1q2 | ||
) | ||
|
||
expected = { | ||
"p1": Wires([0, 1, 2]), | ||
"p2": Wires([3, 4, 5]), | ||
"q1": Wires([6, 7, 8]), | ||
"q2": Wires([9, 10, 11]), | ||
"p1p2": Wires([12, 13, 14, 15, 16, 17]), | ||
"p1p2_plus_q1q2": Wires([18, 19, 20, 21, 22, 23]), | ||
} | ||
actual = qml.get_bloq_registers_info(circuit_bloq) | ||
|
||
assert actual == expected |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit confusing but what does
bitsize=3
in this case? Do we get a SWAP on three wires?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Bloq Swap basically swaps N bit registers. So in this case you would get
qml.SWAP([0, 3]), qml.SWAP([1. 4]), qml.SWAP([2, 5])
Maybe SWAP is not the best example since the qualtran Swap is different from the QML Swap.