In the last month, I was mainly working on the integration with YaoLang.jl
for ZXCalculus.jl
. It was my first time to work on Julia metaprogramming in practice. I want to appreciate my mentor Roger Luo for teaching me basic notions and useful methods about metaprogramming.
According to Wikipedia, metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data. To understand metaprogramming in Julia, it’s necessary to know how Julia compiler works.
How Julia works
All Julia codes are essential Strings
which are stored in disks. When we run Julia codes, Julia will first parse these code into ASTs (abstract syntax trees). The AST will be stored as expressions in the data structure Expr
in Julia. On this parsing level, we call these expressions surface-level IR (intermediate representation).
1 |
|
In this example, ex
is an AST. Its head
is :call
which means it is a function call. It calls the function +
with arguments 1
and another Expr
.
Then the next level is lowering. On this level, macros will be expanded and Julia’s “syntactic sugar” will be transformed into function calls. For example, a[i]
will be replaced with getindex(a, i)
. After a series of transformations, the surface-level IR will be transformed into SSA (static single assignment) IR also called “lowered” IR. In the SSA IR, each variable can be assigned only once. In Julia, we can use the macro @code_lowed
to see the SSA IR of an Expr
.
1 |
|
Julia will do type inference on SSA IR and optimize it. And then transform it into LLVM codes. We can use macros @code_typed
and @code_llvm
to see these IRs.
1 |
|
Finally, LLVM will transform these codes into native machine codes.
1 |
|
Here is a picture from JuliaCon 2018 that demonstrates how Julia compiler works.
How YaoLang.jl works
The goal of YaoLang.jl
is to construct a user-friendly quantum compiler for hybrid quantum-classical programs in Julia. That is by only using a few macros and add them to native Julia functions, one can define quantum programs. In YaoLang.jl
, a function decorated with the macro @device will be regarded as a function with quantum operations. In these functions, macros @ctrl
, @measure
, @expect
and “syntax sugar” locs => gate
are available for defining quantum operations. For example, this represents a circuit for quantum Fourier transformation of n
qubits.
1 |
|
Similar to the compiling procedures of Julia, the macro @device will parse a function into a surface-level IR in YaoLang.jl
. Then, all macros and syntax sugar for quantum operators will be replaced by function calls. These function calls will be marked with the label :quantum
. Now the surface-level IR will be transformed into lowered SSA IR. In YaoLang.jl
, the SSA IR will be stored in the data structure YaoIR
.
The remaining parts are optimization of YaoIR
and transformation from YaoIR
to hardware-level codes. ZXCalculus.jl
is for quantum circuit optimization and should be integrated on the optimization level.
Integration of ZXCalculus.jl
Now, we only consider pure quantum programs. Once we get a YaoIR
, to optimize the quantum circuit, all we need to do is the following steps.
- Convert it into a ZX-diagram
- Simplify the ZX-diagram with
ZXCalculus.jl
- Convert the simplified ZX-diagram back to
YaoIR
The second step is already implemented in ZXCalculus.jl
. We only need to implement the conversion between the ZXDiagram
and the YaoIR
.
YaoIR to ZXDiagram
As each YaoIR
is an SSA, we can traverse all statements to get information about gates and its location. We can regard the largest location as the number of qubits. To construct the corresponding ZX-diagram, we can construct an empty circuit and push gates into it sequentially when traversing the YaoIR
. And the code is like
1 |
|
As the parameterization of ZX-diagrams and quantum circuits may be different up to a global phase, the ZX-diagram we get may be different up to a global phase. The information on the global phase should be recorded.
ZXDiagram to YaoIR
We assume that we get a ZXDiagram
representing a quantum circuit. To transform it into a YaoIR
, we need to extract the sequence of quantum gates in the ZXDiagram
.
This can be extracted from the layout information of the ZXDiagram
. From the layout, we can know how the spiders sorted from input to output and the location of qubits for each spider. If a spider is of degree 2, it represents a single-qubit gate. Otherwise, it represents a multi-qubits gate. By traverse all spiders from input to output, we can get a sequence of quantum gates. And then we can construct a new YaoIR with the sequence.
Optimization options
There are multiple circuit simplification methods in ZXCalculus.jl
. And we propose to implement other simplification methods that are not based on ZX-calculus. It is necessary to allow the user to choose which optimization methods will be applied.
We added these options in the macro @device
. The optimization options can be set as
1 |
|
The optimizer
can be a subset of [:zx_clifford, zx_teleport]
. And we will add more methods in the future.
Examples
By now, ZXCalculus.jl
has been integrated with YaoLang.jl
. We can use YaoLang.jl
to check the correctness of algorithms in ZXCalculus.jl
. This example is an arithmetic circuit.
We first define two circuits. One is the original circuit, the other is optimized.
1 |
|
By using the package YaoArrayRegister.jl
, we can compute the matrix for each circuit.
1 |
|
Comparing these two matrices, we can see that they are the same matrices. Hence, the algorithm returns an equivalent simplified circuit.
1 |
|
Summary
During the second coding phase, I implemented the conversion between the ZXDiagram
and the YaoIR
, which ensures the integration of ZXCalculus.jl
with YaoLang.jl
. Also, the documentation is now available here. And during the test of ZXCalculus.jl
with YaoLang.jl
and YaoArrayRegister.jl
, I find a few bugs in the implementation of circuit extraction and phase teleportation. These bugs have been fixed by now.
In the next phase, I will work on compiling OpenQASM codes into YaoIR
s. It will enable us to read circuits from OpenQASM code. And I will test the performance of ZXCalculus.jl
on some benchmark circuits and compare it with PyZX
.