Skip to content

Conversation

@johnzl-777
Copy link
Contributor

@johnzl-777 johnzl-777 commented Dec 4, 2025

This is my implementation of #540 , there is one thing that I've held off on after talking with @weinbe58 that's a bit tricky: handling measurements. My understanding is we don't need it for now but in the future it will be relevant.

While 95% of qasm2 maps very nicely to existing squin infrastructure (with the exception of some of the less common 2 and 3 qubit gates I had to skip - unless we want to do some implicit conversion for them) you run into problems trying to map the qasm2 measurement semantics.

Take the following examples:

  1. A user wants to measure out all the qubits to the entirety of the declared register. Your qasm2 looks something like this
qreg q[5]
creg c[5]
measure q -> c

You could potentially just have creg be an empty ilist and then when the measure happens produce the ilist of measurements - making sure that all subsequent references point to the new ilist. This seems acceptable but it gets tricky if a user wants to do something like

  1. a user only wants to measure a single qubit and save that to a slot in the classical register. This looks like
qreg q[5]
creg c[5]
measure q[0] -> c[0]

Now the classical register somehow has to already be get_item-able which is tricky considering there's no notion of a fixed-length list. Things get even trickier if a user wants to save at a different index:

measure q[1] -> c[3]

P.S. The sub kernel rewrite seems "off" to me, I'm sure there must be a better way to do it but I'm not aware of what the proper API for it is 😅

@johnzl-777 johnzl-777 linked an issue Dec 4, 2025 that may be closed by this pull request
@codecov
Copy link

codecov bot commented Dec 4, 2025

Codecov Report

❌ Patch coverage is 98.52217% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...c/bloqade/squin/passes/qasm2_gate_func_to_squin.py 86.95% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Contributor

github-actions bot commented Dec 4, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
10759 9494 88% 0% 🟢

New Files

File Coverage Status
src/bloqade/squin/passes/_init_.py 100% 🟢
src/bloqade/squin/passes/qasm2_gate_func_to_squin.py 87% 🟢
src/bloqade/squin/passes/qasm2_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/_init_.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/core_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/glob_parallel_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/id_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/noise_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/parametrized_uop_1q_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/uop_1q_to_squin.py 100% 🟢
src/bloqade/squin/rewrite/qasm2/uop_2q_to_squin.py 100% 🟢
TOTAL 99% 🟢

Modified Files

File Coverage Status
src/bloqade/qasm2/dialects/uop/stmts.py 100% 🟢
TOTAL 100% 🟢

updated for commit: aea7567 by action🐍

@johnzl-777 johnzl-777 marked this pull request as draft December 5, 2025 13:02
@johnzl-777 johnzl-777 marked this pull request as ready for review December 5, 2025 14:43
@johnzl-777
Copy link
Contributor Author

Used Callgraph pass after talking to Phil, I originally had a pretty funky rewrite rule to handle subkernels 😅

@johnzl-777
Copy link
Contributor Author

johnzl-777 commented Dec 5, 2025

Latest round of feedback (from Phil): use dictionary style mapping (faster than pattern matching) + can use pre-existing QASM2 to py expr rewriterule.

Should also try to avoid multi-layer matching logic, easier to reason through/test smaller rules.

Copy link
Collaborator

@david-pl david-pl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good and the tests are quite extensive, which is great. I have some questions and small comments.

else:
return RewriteResult()

new_stmt = func.Invoke(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like you could just do this within the if above, since the args have to match the node type anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

if isinstance(node, (core_stmts.QRegGet, core_stmts.QRegNew, core_stmts.Reset):
    node.replace_by(func.Invoke(node.args, callee = callees[type(note))
else:
    return RewriteResult()


def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:

if isinstance(node, glob.UGate):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this seems a bit redundant: you might as well just assign squin_equivalent_stmt in each case here rather than using the map above.

Copy link
Contributor Author

@johnzl-777 johnzl-777 Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dictionary was something that @weinbe58 recommended, I could be doing the pattern wrong here but in cases where I do see a dictionary used it only turns out nice if the attribute you're accessing exits across all the statements.

Like for arithmetic operation conversion, you'll always have an lhs and rhs attribute. Here the number and kinds of attributes change.

I actually realize if I wanted to be clever I could add some strings to the values in the dictionary and then __getattribute__ things which would resolve the clunkiness at the expense of making things a little uglier.

Wish I could still do pattern matching but I'm told the performance would take a hit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you do not need to unpack the arguments, unless they do not match the signature of the standard library function in squin but I think we have been consistent on this front so you should not need to do this @johnzl-777 see my comment above.

return RewriteResult()

squin_noise_stmt = NOISE_TO_SQUIN_MAP[type(node)]
invoke_stmt = func.Invoke(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you know that you are going for the broadcast version, why not just rewrite to the statement directly instead of adding an invoke to the stdlib?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the comment above

Copy link
Member

@weinbe58 weinbe58 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good I just have some small updates to the code to reduce the number of special cases.

In general you could wrap all the "easy" rewrites with statements that match the stdlib signature exactly into a single rewrite pass that using one master dictionary to map the statement to their equivalent stdlib function and then handle the special cases like U1 and U2 in a separate rewrite. as their own separate rewrites and that might be a bit cleaner then spreading out the rewrites over

else:
return RewriteResult()

new_stmt = func.Invoke(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

if isinstance(node, (core_stmts.QRegGet, core_stmts.QRegNew, core_stmts.Reset):
    node.replace_by(func.Invoke(node.args, callee = callees[type(note))
else:
    return RewriteResult()


def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:

if isinstance(node, glob.UGate):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you do not need to unpack the arguments, unless they do not match the signature of the standard library function in squin but I think we have been consistent on this front so you should not need to do this @johnzl-777 see my comment above.

return RewriteResult()

squin_noise_stmt = NOISE_TO_SQUIN_MAP[type(node)]
invoke_stmt = func.Invoke(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the comment above

Comment on lines +24 to +37
if isinstance(node, (uop_stmts.RX, uop_stmts.RY, uop_stmts.RZ)):
args = (node.theta, node.qarg)
elif isinstance(node, (uop_stmts.UGate)):
args = (node.theta, node.phi, node.lam, node.qarg)
elif isinstance(node, (uop_stmts.U1)):
zero_stmt = py.Constant(value=0.0)
zero_stmt.insert_before(node)
args = (zero_stmt.result, zero_stmt.result, node.lam, node.qarg)
elif isinstance(node, (uop_stmts.U2)):
half_pi_stmt = py.Constant(value=pi / 2)
half_pi_stmt.insert_before(node)
args = (half_pi_stmt.result, node.phi, node.lam, node.qarg)
else:
return RewriteResult()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(node, (uop_stmts.RX, uop_stmts.RY, uop_stmts.RZ)):
args = (node.theta, node.qarg)
elif isinstance(node, (uop_stmts.UGate)):
args = (node.theta, node.phi, node.lam, node.qarg)
elif isinstance(node, (uop_stmts.U1)):
zero_stmt = py.Constant(value=0.0)
zero_stmt.insert_before(node)
args = (zero_stmt.result, zero_stmt.result, node.lam, node.qarg)
elif isinstance(node, (uop_stmts.U2)):
half_pi_stmt = py.Constant(value=pi / 2)
half_pi_stmt.insert_before(node)
args = (half_pi_stmt.result, node.phi, node.lam, node.qarg)
else:
return RewriteResult()
if type(node) not in PARAMETRIZED_1Q_GATES_TO_SQUIN_MAP:
return RewriteResult()
args = node.args
if isinstance(node, (uop_stmts.U1)):
zero_stmt = py.Constant(value=0.0)
zero_stmt.insert_before(node)
args = (zero_stmt.result, zero_stmt.result) + args
elif isinstance(node, (uop_stmts.U2)):
half_pi_stmt = py.Constant(value=pi / 2)
half_pi_stmt.insert_before(node)
args = (half_pi_stmt.result, ) + args

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rewrite QASM2 dialect to SQUIN

4 participants