Skip to content

Conversation

@david-pl
Copy link
Collaborator

@david-pl david-pl commented May 27, 2025

Addresses #283

For now, I went with the syntax squin.load_circuit(circuit: cirq.Circuit).

So you can e.g. do

q0 = cirq.NamedQubit("q0")
q1 = cirq.NamedQubit("q1")

circuit = cirq.Circuit(
    cirq.H(q1),
    cirq.X(q0).controlled_by(q1),
    cirq.Rx(rads=math.pi / 4).on(q0).controlled_by(q1),
    cirq.measure(q0, q1),
)

main = squin.load_circuit(circuit)

@Roger-luo @weinbe58 one open question here: cirq support arbitrary powers of gates, e.g. cirq.H(q0)**0.123. Should we add a Pow statement to squin to properly support this? I think pyqrack can also handle it, but I'll have to check again. Also, not sure what this should look like when running on hardware, but we can still error in the runtime.

Edit: turns out arbitrary powers are quite easy since we can just use the cirq helper in_su2 to get the corresponding rotation and lower that. Also, for multi-qubit gates they usually mean "apply a controlled single qubit gate with an exponent", which again corresponds to a rotation. Thanks @kaihsin for pointing to the helper method.

TODOs:

@david-pl david-pl linked an issue May 27, 2025 that may be closed by this pull request
@kaihsin
Copy link
Contributor

kaihsin commented May 27, 2025

For single qubit gate with arbitrary power, my understand is it can always decomposed into 1Q rotation gate (cirq has a unitary to angle helper) for arbitrary multi-qubit power I am not sure.

@david-pl
Copy link
Collaborator Author

For single qubit gate with arbitrary power, my understand is it can always decomposed into 1Q rotation gate (cirq has a unitary to angle helper) for arbitrary multi-qubit power I am not sure.

Good point! For controlled gates to a power we can do something similar, I think, although I couldn't find a similar helper method (for single qubit gates it's .in_su2())

@codecov
Copy link

codecov bot commented May 27, 2025

Codecov Report

Attention: Patch coverage is 92.47312% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/bloqade/squin/cirq/lowering.py 92.68% 12 Missing ⚠️
src/bloqade/squin/__init__.py 66.66% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Contributor

github-actions bot commented May 27, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
8043 7046 88% 0% 🟢

New Files

File Coverage Status
src/bloqade/squin/cirq/_init_.py 100% 🟢
src/bloqade/squin/cirq/lowering.py 93% 🟢
TOTAL 96% 🟢

Modified Files

File Coverage Status
src/bloqade/squin/_init_.py 75% 🟢
src/bloqade/squin/op/stmts.py 99% 🟢
TOTAL 87% 🟢

updated for commit: fd9b36a by action🐍

@jon-wurtz
Copy link
Contributor

When transpling to squin, could you also give an option that moments are transpiled correctly to preserve ordering? This follows the PR that I made a few days ago #288 where I parallelize, represented by cirq moments.

Copy link
Collaborator

@Roger-luo Roger-luo left a comment

Choose a reason for hiding this comment

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

I think the PR itself LGTM. I left a comment to improve in the future PR. We should get this basic API in first.

Comment on lines +123 to +127
def visit_Moment(
self, state: lowering.State[CirqNode], node: cirq.Moment
) -> lowering.Result:
for op_ in node.operations:
state.lower(op_)
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe in a separate PR, to address @jon-wurtz 's comment, we should:

  • lower moment with same gate into broadcast(op, ...)
  • lower moment with different gate into parallel statement (which will be added on Kirin side)

to facilitate this we may want to just add a Moment statement as an IR to temperarily perserve this highlevel structure coming from cirq and then implement a rewrite from Moment to squin.

Copy link
Collaborator

Choose a reason for hiding this comment

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

or a Moment should be rewrite into something like a Parallel with a region?

# TODO: can there be a circuit without qubits?
n_qubits = cirq.num_qubits(self.circuit)
n = frame.push(py.Constant(n_qubits))
self.qreg = frame.push(qubit.New(n_qubits=n.result))
Copy link
Contributor

Choose a reason for hiding this comment

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

Might it be better to have the register be an input to the kernel instead of inside of the kernel? Otherwise, the kernel generated cannot be used as a subroutine elsewhere. Likewise, returning the qubit register. See QuEraComputing/bloqade#249.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@jon-wurtz see my comment in the RFC issue (let's leave the discussion in on place). Ideally we support both, loading a "standalone" circuit as well as passing the register as an argument, controlled by a kwarg.

@jon-wurtz
Copy link
Contributor

Could this handle cirq feed forward, e.g.

q0, q1 = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
    cirq.H(q0),  # Prepare qubit q0 in superposition
    cirq.measure(q0, key='m'),  # Measure qubit q0
    cirq.X(q1).with_classical_controls('m')  # Apply X gate to q1 if measurement 'm' is 1
)

which creates a circuit like

0: ───H───M───────
          ║
1: ───────╫───X───
          ║   ║
m: ═══════@═══^═══

or more complex classical control like https://quantumai.google/cirq/build/classical_control

@david-pl
Copy link
Collaborator Author

@weinbe58 @Roger-luo I think we need to add a new operator statement in order to support the GeneralizedAmplitudeDampingChannel from cirq.

Basically, this channel maps |0> to |1> and vise-versa, but at different probabilities. So essentially I need both processes separately. Mapping from |1> to |0> can be done via Reset, but there is no statement that maps |0> to |1> (the projector can potentially lead to 0 amplitude of the state).

@david-pl
Copy link
Collaborator Author

@jon-wurtz classical control is still missing. That needs to be lowered to an if statement. I updated the TODO list in the PR description.

@Roger-luo
Copy link
Collaborator

Roger-luo commented May 27, 2025

@jon-wurtz classical control is still missing. That needs to be lowered to an if statement. I updated the TODO list in the PR description.

can we limit this PR to have the basic only? otherwise it will take forever to merge...

I think we need to add a new operator statement in order to support the GeneralizedAmplitudeDampingChannel from cirq.

I think that's ok? I don't see a reason why no. but in a separate PR maybe I think the cirq feature covered in this PR already good enough for most people.

@david-pl
Copy link
Collaborator Author

@Roger-luo Okay, then I'll leave this PR open a bit longer to give people some more time to respond on the RFC. I'll create follow-up issues for the missing features. Then we can merge this PR.

@david-pl david-pl marked this pull request as ready for review May 28, 2025 09:40
Copy link
Collaborator

@Roger-luo Roger-luo left a comment

Choose a reason for hiding this comment

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

would be nice if we can add a docstring to the entry before merging.

@david-pl david-pl merged commit 71c6ef3 into main May 28, 2025
11 checks passed
@david-pl david-pl deleted the david/283-cirq-interop branch May 28, 2025 15:19
david-pl added a commit that referenced this pull request May 28, 2025
Addresses #283 

For now, I went with the syntax `squin.load_circuit(circuit:
cirq.Circuit)`.

So you can e.g. do

```python
q0 = cirq.NamedQubit("q0")
q1 = cirq.NamedQubit("q1")

circuit = cirq.Circuit(
    cirq.H(q1),
    cirq.X(q0).controlled_by(q1),
    cirq.Rx(rads=math.pi / 4).on(q0).controlled_by(q1),
    cirq.measure(q0, q1),
)

main = squin.load_circuit(circuit)
```


~~@Roger-luo @weinbe58 one open question here: cirq support arbitrary
powers of gates, e.g. `cirq.H(q0)**0.123`. Should we add a `Pow`
statement to squin to properly support this? I think pyqrack can also
handle it, but I'll have to check again. Also, not sure what this should
look like when running on hardware, but we can still error in the
runtime.~~

**Edit**: turns out arbitrary powers are quite easy since we can just
use the cirq helper `in_su2` to get the corresponding rotation and lower
that. Also, for multi-qubit gates they usually mean "apply a controlled
single qubit gate with an exponent", which again corresponds to a
rotation. Thanks @kaihsin for pointing to the helper method.

## TODOs:

- [x] support (arbitrary) powers of operators?
- ~~[ ] Lowering of noisy circuits~~ done, except #301 
- [x] Add missing gates (e.g. three-qubit controls are still missing)
- [ ] Make sure the set of supported gates is complete
- ~~ [ ] Optionally return the quantum register (and possibly other
things) ~~ see #302
- ~~ [ ] Optionally pass in the quantum register as argument to the
method ~~ see #302
- ~~[ ] Support classical control by lowering to `if` ~~ see #303
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.

interop with cirq

5 participants