Skip to content
60 changes: 53 additions & 7 deletions docs/digital/dialects_and_kernels.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
# Dialects and kernels

Bloqade provides a set of pre-defined dialects, with which you can write your programs and circuits.

Once you have your kernel, you can inspect their intermediate representation (IR), apply different optimizations using [compiler passes](../quick_start/circuits/compiler_passes/index.md), or run them on a [(simulator) device](./simulator_device/simulator_device.md).

!!! info
A **kernel** function is a piece of code that runs on specialized hardware such as a quantum computer.

A **dialect** is a domain-specific language (DSL) with which you can write such a kernel.
Each dialect comes with a specific set of statements and instructions you can use in order to write your program.

Bloqade provides a set of pre-defined dialects, with which you can write your programs and circuits.

Once you have your kernel, you can inspect their intermediate representation (IR), apply different optimizations using [compiler passes](../quick_start/circuits/compiler_passes/index.md), or run them on a [(simulator) device](./simulator_device/simulator_device.md).
When running code that targets a specialized execution environment, there are typically several layers involved.
At the surface, the programmer writes functions in a syntax that may resemble a host language (e.g., Python), but is actually expressed in a dialect— a domain-specific variant with its own semantics.
A decorator marks these functions so they can be intercepted before normal host-language execution.
All dialects can be used by decorating a function.

!!! info
Here's a short primer on decorators: a decorator in Python is simply a function (or any callable really) that takes in another function as argument and returns yet another function (callable).
Usually, the returned function will be a modified version of the input.
Decorators are used with the `@` syntax.


Instead of running directly, the kernel function body is parsed and translated (lowered) into an intermediate representation (IR).
This IR can be manipulated (e.g. to perform optimizations) and can later be executed by an interpreter that understands the dialect's semantics.
The interpreter uses an internal instruction set to execute the code on the intended backend, which may be a simulator, virtual machine, or physical device.
This separation lets developers write high-level, expressive code while the interpreter ensures it runs correctly in the target environment.
[QuEra's Kirin](https://queracomputing.github.io/kirin/latest/) infrastructure uses this concept by defining custom dialects that are tailored towards the needs to program neutral atom quantum computers.
While the dialects are not python syntax, Kirin still uses the python interpreter to execute the code.


!!! note
It is important to understand that when you are writing a kernel function in a dialect you are generally **not writing Python** code, even though it looks a lot like it.
Therefore, kernel functions can usually not be called directly.
Think of this as trying to execute another programming language with the Python interpreter: of course, that will error.


# Available dialects
Expand Down Expand Up @@ -128,18 +153,39 @@ main.print()

## squin

This dialect is, in a sense, more expressive than the qasm2 dialects: it allows you to specify operators rather than just gate applications.
That can be useful if you're trying to e.g. simulate a Hamiltonian time evolution.

!!! warning
The squin dialect is in an early stage of development.
Expect substantial changes to it in the near future.


This dialect is, in a sense, more expressive than the qasm2 dialects: it allows you to specify operators rather than just gate applications.
That can be useful if you're trying to e.g. simulate a Hamiltonian time evolution.

For simple circuits, however, gate applications also have short-hand standard library definitions defined in the `squin.gate` submodule.
Here's a short example:

```python
from bloqade import squin

@squin.kernel
def main():
q = squin.qubit.new(2)
squin.gate.h(q[0])
squin.gate.cx(q[0], q[1])
return squin.qubit.measure(q)

# have a look at the IR
main.print()
```


As mentioned above, you can also build up more complex "operators" that are then applied to any number of qubits.
To show how you can do that, here's an example on how to write the above kernel defining the gates as separate operators.
This isn't exactly a practical use-case, but serves as an example.

```python
from bloqade import squin

@squin.kernel
def main():
q = squin.qubit.new(2)
Expand All @@ -148,10 +194,10 @@ def main():
# apply a hadamard to only the first qubit
h1 = squin.op.kron(h, squin.op.identity(sites=1))

squin.qubit.apply(h1, q)
squin.qubit.apply(h1, q[0], q[1])

cx = squin.op.cx()
squin.qubit.apply(cx, q)
squin.qubit.apply(cx, q[0], q[1])

return squin.qubit.measure(q)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

# <div align="center">
# <picture>
# <img src="../ghz_linear_circuit.svg" >
# <img src="../../ghz_linear_circuit.svg" >
# </picture>
# </div>

Expand Down Expand Up @@ -60,13 +60,13 @@ def ghz_linear_program():
# <p>
# Before going any further, it's worth distinguishing between the concept of circuit depth and circuit execution depth.
# For example, in the following implementation, each CX gate instruction inside the for-loop is executed in sequence.
# So even thought the circuit depth is $log(N) = n$, the circuit execution depth is still $N$.
# So even though the circuit depth is $log(N) = n$, the circuit execution depth is still $N$.
# </p>
# </div>

# <div align="center">
# <picture>
# <img src="../ghz_log_circuit.svg" >
# <img src="../../ghz_log_circuit.svg" >
# </picture>
# </div>

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,11 @@
# our high-level programming features.
#
# To begin, we will import the `qasm2` module from the `bloqade` package and the `PyQrack`
# backend from the `bloqade.pyqrack` module, which can be installed via
#
# ```bash
# pip install bloqade-pyqrack[backend]
# ```
# with the `backend` being one of ` pyqrack`, `pyqrack-cpu`, `pyqrack-cuda` depending on
# the hardware and OS you have. see [README](https://github.com/QuEraComputing/bloqade-pyqrack?tab=readme-ov-file#which-extra-do-i-install)
# for mote details.
# backend from the `bloqade.pyqrack` module.
# %%
import math

from bloqade.pyqrack import PyQrack
from bloqade.pyqrack import StackMemorySimulator

from bloqade import qasm2

Expand Down Expand Up @@ -56,13 +49,13 @@ def main():
# to see the final state of the qubits after applying the QFT circuit.
# <div align="center">
# <picture>
# <img src="../qft.svg" >
# <img src="../../qft.svg" >
# </picture>
# </div>


# %%
device = PyQrack()
device = StackMemorySimulator(min_qubits=3)
qreg = device.run(main)
print(qreg)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@
# %%
@squin.kernel
def f_constant(q: ilist.IList[Qubit, Any]):
x = squin.op.x()

# flip the final (result) qubit -- every bit string is mapped to 1
squin.qubit.apply(x, [q[-1]])
squin.gate.x(q[-1])


# %% [markdown]
Expand All @@ -56,9 +54,7 @@ def f_constant(q: ilist.IList[Qubit, Any]):
# %%
@squin.kernel
def f_balanced(q: ilist.IList[Qubit, Any]):
x = squin.op.x()
cn_x = squin.op.control(x, n_controls=1)
squin.qubit.apply(cn_x, [q[0], q[-1]])
squin.gate.cx(q[0], q[-1])


# %% [markdown]
Expand All @@ -69,19 +65,16 @@ def f_balanced(q: ilist.IList[Qubit, Any]):
@squin.kernel
def deutsch_algorithm(f):
q = squin.qubit.new(n_qubits=n_bits + 1)
squin.gate.x(q[-1])

x = squin.op.x()
squin.qubit.apply(x, [q[-1]])

# broadcast for parallelism
h = squin.op.h()
for i in range(len(q)):
squin.qubit.apply(h, [q[i]])
squin.qubit.broadcast(h, q)

# apply the oracle function
f(q)

for i in range(n_bits):
squin.qubit.apply(h, [q[i]])
squin.qubit.broadcast(h, q[:-1])

return squin.qubit.measure(q[:-1])

Expand Down
Loading
Loading