Skip to content

Commit b3a84c0

Browse files
david-pljohnzl-777
andauthored
Document cirq interoperability (#256)
This is a WIP. Also, requires QuEraComputing/bloqade-circuit#311 to be merged and released first. --------- Co-authored-by: John Long <jlong@quera.com>
1 parent a85b8d2 commit b3a84c0

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Converting cirq to squin
2+
3+
If you want to obtain a squin kernel from a circuit, you can use the `load_circuit` method in the `squin.cirq` submodule.
4+
What you're effectively doing is lowering a circuit to a squin IR.
5+
This IR can then be further lowered to eventually run on hardware.
6+
7+
## Basic examples
8+
9+
Here are some basic usage examples to help you get started.
10+
11+
```python
12+
from bloqade import squin
13+
import cirq
14+
15+
qubits = cirq.LineQubit.range(2)
16+
circuit = cirq.Circuit(
17+
cirq.H(qubits[0]),
18+
cirq.CX(qubits[0], qubits[1]),
19+
cirq.measure(qubits)
20+
)
21+
22+
# let's have a look
23+
print(circuit)
24+
25+
main_loaded = squin.cirq.load_circuit(circuit, kernel_name="main_loaded")
26+
```
27+
28+
The above is equivalent to writing the following kernel function yourself:
29+
30+
```python
31+
@squin.kernel
32+
def main():
33+
q = squin.qubit.new(2)
34+
H = squin.op.h()
35+
CX = squin.op.cx()
36+
squin.qubit.apply(H, q[0])
37+
squin.qubit.apply(CX, q)
38+
squin.qubit.measure(q)
39+
```
40+
41+
You can further inspect the lowered kernel as usual, e.g. by printing the IR.
42+
Let's compare the manually written version and the loaded version:
43+
44+
```python
45+
main.print()
46+
main_loaded.print()
47+
```
48+
49+
The resulting IR is equivalent, yet the loaded is a bit longer since the automated loading can make fewer assumptions about the code.
50+
Still, you can use the kernel as any other, e.g. by calling it from another kernel or running it via a simulator.
51+
52+
## Noise
53+
54+
Lowering a noisy circuit to squin is also supported.
55+
All common channels in cirq will be lowered to an equivalent noise statement in squin.
56+
57+
```python
58+
from bloqade import squin
59+
import cirq
60+
61+
qubits = cirq.LineQubit.range(2)
62+
noisy_circuit = cirq.Circuit(
63+
cirq.H(qubits[0]),
64+
cirq.CX(qubits[0], qubits[1]),
65+
cirq.depolarize(p=0.01).on_each(qubits),
66+
)
67+
68+
# let's have a look
69+
print(noisy_circuit)
70+
71+
noisy_kernel = squin.cirq.load_circuit(noisy_circuit)
72+
noisy_kernel.print()
73+
```
74+
75+
This becomes especially useful when used together with a `cirq.NoiseModel` that automatically adds noise to a circuit via `circuit.with_noise(model)`.
76+
77+
## Composability of kernels
78+
79+
You may also run into a situation, where you define a circuit that is used as part of a larger one, maybe even multiple times.
80+
In order to allow you to do something similar here, you can pass in and / or return the qubit register in a loaded kernel.
81+
Both these options are controlled by simple keyword arguments.
82+
83+
### Qubits as argument to the kernel function
84+
85+
Setting `register_as_argument=True` when loading a kernel, will result in a squin kernel function that accepts (and requires) a single argument of type `IList[Qubit]`.
86+
This means you can use a loaded circuit as part of another kernel function.
87+
Check it out:
88+
89+
```python
90+
from bloqade import squin
91+
import cirq
92+
93+
qubits = cirq.LineQubit.range(2)
94+
circuit = cirq.Circuit(
95+
cirq.H(qubits[0]),
96+
cirq.CX(qubits[0], qubits[1]),
97+
)
98+
99+
sub_kernel = squin.cirq.load_circuit(circuit, register_as_argument=True, kernel_name="sub_kernel")
100+
101+
102+
@squin.kernel
103+
def main():
104+
q = squin.qubit.new(4)
105+
106+
# entangle qubits 1 and 2
107+
sub_kernel([q[0], q[1]])
108+
109+
# entangle qubits 3 and 4
110+
sub_kernel([q[2], q[3]])
111+
112+
113+
main.print()
114+
```
115+
116+
Looking at the IR of the resulting kernel, you can see that there is are `invoke sub_kernel` statements present, which call the lowered circuit with the given arguments.
117+
118+
### Qubits as return value from the kernel
119+
120+
Similarly to above, you may also want to return a list of qubits from a loaded kernel.
121+
Let's adapt the above to instantiate and return a pair of entangled qubits using the same circuit:
122+
123+
```python
124+
125+
sub_kernel = squin.cirq.load_circuit(circuit, return_register=True, kernel_name="sub_kernel")
126+
127+
@squin.kernel
128+
def main():
129+
# instantiate and entangle a list of two qubits
130+
q1 = sub_kernel()
131+
132+
# do it again, to get another set
133+
q2 = sub_kernel()
134+
135+
# now we have 4 qubits to work with
136+
...
137+
138+
main.print()
139+
```
140+
141+
142+
!!! note
143+
You can also mix both options by setting `register_as_argument = True` and `return_register = True` in order to obtain a kernel function that both accepts and returns a list of qubits.
144+
145+
146+
## Limitations
147+
148+
There are some limitations when loading circuits.
149+
One, for example, is that custom gates are not supported as you can't generally know how to lower them to a squin statement.
150+
151+
If you find a missing feature, please feel free to [open a GitHub issue](https://github.com/QuEraComputing/bloqade-circuit/issues).

docs/digital/cirq_interop/index.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Interoperability with cirq
2+
3+
The [Cirq](https://quantumai.google/cirq) framework is a powerful tool for writing quantum circuits targeting near-term devices.
4+
Instead of reinventing the wheel, Bloqade offers convenient interoperability with Cirq that allows you to jointly use both libraries in order to develop your quantum program.
5+
6+
Specifically, you can turn a [`cirq.Circuit`](https://quantumai.google/reference/python/cirq/Circuit) object into a [squin](../dialects_and_kernels.md#squin) kernel function and vice versa.
7+
8+
For details on each of these, please see the documentation pages below:
9+
10+
* [Obtaining a squin kernel function from a `cirq.Circuit`](./cirq_to_squin.md)
11+
* [Emitting a `cirq.Circuit` from a squin kernel](./squin_to_cirq.md)
12+
13+
For the API reference, please see the `cirq` submodule in the [squin API docs](../../reference/squin.md).
14+
15+
## TL;DR
16+
17+
Here's a short example:
18+
19+
```python
20+
from bloqade import squin
21+
import cirq
22+
23+
q = cirq.LineQubit.range(2)
24+
circuit = cirq.Circuit(
25+
cirq.H(q[0]),
26+
cirq.CX(q[0], q[1])
27+
)
28+
print(circuit)
29+
30+
main = squin.cirq.load_circuit(circuit)
31+
main.print()
32+
33+
roundtrip_circuit = squin.cirq.emit_circuit(main)
34+
print(roundtrip_circuit)
35+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Converting squin to Cirq
2+
3+
You can convert a squin kernel function to a `cirq.Circuit` object.
4+
The output circuit will feature gates that most closely resemble the kernel you put in.
5+
6+
## Basic usage
7+
8+
You can obtain a circuit using the `squin.cirq.emit_circuit` function.
9+
10+
```python
11+
from bloqade import squin
12+
13+
@squin.kernel
14+
def main():
15+
q = squin.qubit.new(2)
16+
h = squin.op.h()
17+
squin.qubit.apply(h, q[0])
18+
cx = squin.op.cx()
19+
squin.qubit.apply(cx, q[0], q[1])
20+
squin.qubit.measure(q)
21+
22+
circuit = squin.cirq.emit_circuit(main)
23+
print(circuit)
24+
```
25+
26+
There is one crucial difference between a squin kernel and a cirq circuit:
27+
the qubits are defined inside a kernel, whereas for a circuit they are defined outside.
28+
29+
The default behavior here is to emit a set of `cirq.LineQubit`, which is of the correct length.
30+
They will be sorted by their `Qid` (position) according to the order they appear in the kernel.
31+
32+
## Customizing qubits
33+
34+
By default, a set of `cirq.LineQubit`s of the appropriate size is created internally, on which the resulting circuit operates.
35+
This may be undesirable sometimes, e.g. when you want to combine multiple circuits or if you want to have qubits of a different type.
36+
37+
To allow modifications here, you can simply pass in a list of qubits (a sequence of `cirq.Qid`s) into the emit function.
38+
39+
```python
40+
import cirq
41+
42+
qubits = cirq.GridQubit.rect(rows=1, cols=2)
43+
circuit = squin.cirq.emit_circuit(main, qubits=qubits)
44+
print(circuit)
45+
```
46+
47+
Note, that the qubits will be used in the resulting circuit in the order they appear in `squin.qubit.new` statements.
48+
49+
!!! warning
50+
51+
When passing in a list of qubits, you need to make sure there is sufficiently many qubits.
52+
Otherwise, you may get indexing errors.
53+
54+
## Limitations
55+
56+
Please note that there are some limitations, especially regarding control flow.
57+
Using `if` statements or loops inside a kernel function may lead to errors.
58+
59+
If you run into an issue that you think should be supported, please [report an issue on the GitHub repository](https://github.com/QuEraComputing/bloqade-circuit/issues).

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ nav:
4747
- Simulator Device:
4848
- digital/simulator_device/simulator_device.md
4949
- digital/simulator_device/tasks.md
50+
- Interoperability with Cirq:
51+
- "digital/cirq_interop/index.md"
52+
- "digital/cirq_interop/cirq_to_squin.md"
53+
- "digital/cirq_interop/squin_to_cirq.md"
5054
- Circuits:
5155
- Quantum Fourier Transform: "digital/examples/qft.py"
5256
- GHZ state preparation: "digital/examples/ghz.py"

0 commit comments

Comments
 (0)