Qu Language - m-marini/qucomp GitHub Wiki
The qu language is used to simulate quantum computing.
The language allows to define qubits, quantum gates and handle the linear algebra of quantum computing.
Let's start from the expression of a 1-qubit, qu language uses the notation bra ket so the state of a qubit is defined by a ket
let a = |0>;
let b = |1>;
the command assigns the value |0>
to the variable a
and the value |1>
to the variable b
.
The qu language define also the standard kets of a 1-qubit.
/*
* This is a comment
*/
|0>; // (1.0) |0>
|1>; // (1.0) |1>
|+>; // (0.70710677) |0> + (0.70710677) |1>
|->; // (0.70710677) |0> + (-0.70710677) |1>
|i>; // (0.70710677) |0> + (0.70710677 i) |1>
|-i>; // (0.70710677) |0> + (-0.70710677 i) |1>
from the example we can see the use of C-style comments in the code using // ...
or '/* ... */
'
The state of a multi-qubits system is defined as the tensor product of states of the individual qubits.
In the qu language the tensor product of two qubits is performed by the operator x
which produces a ket with all possible states.
|0> x |0>; // (1.) |0>
|0> x |1>; // (1.) |1>
|1> x |0>; // (1.) |2>
|1> x |1>; // (1.) |3>
The language also recognizes simplified expressions of multi-qubit states using binary base notation.
|2>; // |1> x |0>
|3>; // |1> x |1>
|4>; // |1> x |0> x |0>
|5>; // |1> x |0> x |1>
|6>; // |1> x |1> x |0>
|7>; // |1> x |1> x |1>
// ... etc.
Let's continue with the use of operators.
The language uses basic algebraic binary operators such as +
, -
for addition or subtraction, *
, /
for multiplication and division and the operator x
that we have already seen for the tensor product.
Three natural constants are also defined, such as the imaginary unit i
, the constant pi
(3.1415927) and Euler's number e
(2.7182817).
These operators allow us to construct any state of a system of qubits.
0.5 * |0> + 0.5 * |1> + 0.5 * i * |2> + 0.5 * |3>;
Then there are two unary operators prefixed +
and -
and a postfix ^
dagger.
The dagger operator calculates the conjugate transpose matrix thus allowing to transform a ket expression into a bra and vice versa.
|0>^; // (1.0) <0|
|1>^; // (1.0) <1|
|i>^; // (0.70710677) <0| + (-0.70710677 i) <1|
Of course the language allows to define bra operators in a similar way to ket so there are equivalent bra expressions
<0|; // (1.0) <0|
<1|; // (1.0) <1|
<+|; // (0.70710677) <0| + (0.70710677) <1|
<-|; // (0.70710677) <0| + (-0.70710677) <1|
<i|; // (0.70710677) <0| + (-0.70710677 i) <1|
<-i|; // (0.70710677) <0| + (0.70710677 i) <1|
Another fundamental notion for linear algebra is the matrix. That is, a set of n by m complex numbers normally represented by tabular notation
a(0,0) | a(0,1) | ... | a(0,m-1) |
a(1,0) | a(0,1) | ... | a(1,m-1) |
... | ... | ... | ... |
a(n-1,0) | a(n-1,1) | ... | a(n-1,m-1) |
Matrix multiplication allows us to linearly transform the state of one system into another.
Matrices are generated by predefined functions in the language.
Some functions generate base matrices that are needed to construct all other matrices.
ary(2,2);
/*
[ 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 1.0
0.0, 0.0, 0.0, 0.0 ]
*/
The ary
function generates the matrix in which only the cell (i, j) has the value one while the others are zero.
By linearly combining the cell matrices it is possible to obtain any matrix.
ary(2,2) + 2 * ary(1,0);
/*
[ 0.0, 0.0, 0.0, 0.0
2.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 0.0 ]
*/
sim(1,2);
/*
[ 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0 ]
*/
The sim
function generates the symmetric base matrix in which only the cell (i, j) and cell (j,i) has the value one while the others are zero.
Symmetric matrices are matrices that have the property that cell(i, j) = cell(j, i)
.
By linearly combining the symmetric cell matrices it is possible to obtain any symmetric matrix.
sim(2,2) + 2 * sim(1,3) + 3 * sim(0,2);
/*
[ 0.0, 0.0, 3.0, 0.0
0.0, 0.0, 0.0, 2.0
3.0, 0.0, 1.0, 0.0
0.0, 2.0, 0.0, 0.0 ]
*/
eps(1,2);
/*
[ 0.0, 0.0, 0.0, 0.0
0.0, 0.0, -1.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0 ]
*/
The eps
function generates the antisymmetric base matrix in which only the cell (i, j) and cell (j, i) has the value one or minus one while the others are zero.
Antisymmetric matrices are matrices that have the property that cell(i, j) = -cell(j, i)
.
By linearly combining the antisymmetric cell matrices it is possible to obtain any antisymmetric matrix.
eps(1,2) + 2 * eps(1,3) + 3 * eps(0,2);
/*
[ 0.0, 0.0, 3.0, 0.0
0.0, 0.0, -1.0, 2.0
-3.0, 1.0, 0.0, 0.0
0.0, -2.0, 0.0, 0.0 ]
*/
Let us now see the quantum gates implemented in qu through transformation matrix generation functions.
The first basic quantum gate is I or identity.
I(1);
/*
[ 1.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 1.0 ]
*/
The I function generates the identity matrix which leaves the states of all the bits unchanged. It is a square matrix with diagonal values to 1 and oher to 0. The argument of the function is the bit index of the qubit to apply the identity and defines the size of the identity matrix 2^(i+1).
The X gate also named NOT gate
X(0);
/*
[ 0.0, 1.0
1.0, 0.0 ]
*/
The matrix applies a change in the state by inverting the state of i-th bit so
X(0) * |0>; // (1.0) |1>
X(0) * |1>; // (1.0) |0>
The H gate also named Hadamard gate
H(0);
/*
[ 0.70710677, 0.70710677
0.70710677, -0.70710677 ]
*/
The matrix applies a change in the state transforming state of i-th qubit from |0>
to |+>
and |1>
to |->
H(0) * |0>; // |+> = (0.70710677) |0> + (0.70710677) |1>
H(0) * |1>; // |-> = (0.70710677) |0> + (-0.70710677) |1>
Y(0);
/*
[ 0.0, -i
i, 0.0 ]
*/
The matrix applies a change in the state transforming state of i-th qubit from |0>
to i |1>
and |1>
to -i |0>
Y(0) * |0>; // (i) |1>
Y(0) * |1>; // (-i) |0>
Z(0);
/*
[ 1.0, 0.0
0.0, -1.0 ]
*/
The matrix applies a change in the state transforming state of i-th qubit from |+>
to |->
and |->
to |+>
Z(0) * |+>; // |-> = (0.70710677) |0> + (0.70710677) |1>
Z(0) * |->; // |+> = (0.70710677) |0> + (-0.70710677) |1>
S(0);
/*
[ 1.0, 0.0
0.0, i ]
*/
The matrix applies a change in the state transforming state of i-th qubit from |0>
to |0>
and |1>
to |i>
.
S(0) * |0>; // (1.0) |0>
S(0) * |1>; // (i) |1>
T(0);
/*
[ 1.0, 0.0
0.0, 0.70710677 +0.70710677 i ]
*/
The matrix applies a change in the state transforming state of i-th qubit from |0>
to |0>
and |+>
to (0.70710677 + 0.70710677 i) |1>
T(0) * |0>; // (1.0) |0> + (-0.70710677 i) |1>
T(0) * |1>; // (0.70710677 + 0.70710677 i) |1>
SWAP(0, 1);
/*
[ 1.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 1.0 ]
*/
The matrix applies a change in the state swapping the state of two qubits so SWAP(0, 1) * (|a> x |b) = |b> x |a>
CNOT(data, control) Control NOT .
CNOT(0, 1);
/*
[ 1.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 1.0
0.0, 0.0, 1.0, 0.0 ]
*/
The matrix applies a change in the state inverting the state of data qubit if the state of control qubit is |1>
so if right qubit 0 is the data and left qubit 1 is the control we have
CNOT(0, 1) * (|0> x |0>); // |0> x |0>
CNOT(0, 1) * (|0> x |1>); // |0> x |1>
CNOT(0, 1) * (|1> x |0>); // |1> x |1>
CNOT(0, 1) * (|1> x |1>); // |1> x |0>
CCNOT(data, control, control) Control control NOT or Toffoli gate. This is a 3-qubits gate.
CCNOT(0, 1, 2);
/*
[ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 ]*/
The matrix applies a change in the state inverting the state of data qubit if both the control bits states are |1>
CCNOT(0, 1, 2) * (|0> x |0> x |0>); // |0> x |0> x |0>
CCNOT(0, 1, 2) * (|0> x |0> x |1>); // |0> x |0> x |1>
// ...
CCNOT(0, 1, 2) * (|1> x |1> x |0>); // |1> x |1> x |1>
CCNOT(0, 1, 2) * (|1> x |1> x |1>); // |1> x |1> x |0>
qubit0(index, numBits)
generate the matrix for projection along a specific qubit for |0>
state.
qubit0(0, 2);
/*
[ 1.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 0.0 ]
*/
qubit0(1, 2);
/*
[ 1.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0 ]
*/
qubit1(index, numBits)
generate the matrix for projection along a specific qubit for |1>
state.
qubit1(0, 2);
/*
[ 0.0, 0.0, 0.0, 0.0
0.0, 1.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 1.0 ]
*/
qubit1(1, 2);
/*
[ 0.0, 0.0, 0.0, 0.0
0.0, 0.0, 0.0, 0.0
0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, 1.0 ]
*/
The matrices areis used to extract only the states corresponding to the specific state of a given qubit.
These are useful for calculating the probability of a state of a given qubit in multi-qubit systems since
the probability to have the i-th qubit to state |0>
is <a| * qubit0(1, n) * |a>
let b0 = |0>;
let b1 = |+>;
let ket = b1 x b0;
let p00 = ket^ * qubit0(0, 2) * ket; // 1
let p10 = ket^ * qubit1(0, 2) * ket; // 0
let p01 = ket^ * qubit0(1, 2) * ket; // 0.5
let p11 = ket^ * qubit1(1, 2) * ket; // 0.5
Now we have all the elements to compose quantum circuits
The circuits are composed through multiplication following the order of application of the gates from right to left of the transformation matrices.
So if we wanted to apply an H gate to qubit 0 followed by an X gate we would have the complete transformation matrix given by X(0) * H(0)
.
X(0) * H(0);
/*
[ 0.70710677, -0.70710677
0.70710677, 0.70710677 ]
*/
To simplify the syntax of expressions, the dimensions of matrices are implicitly extended when necessary.
The extension rules are made keeping in mind that the algebra must remain valid after the extension.
The row (bra) and column (ket) matrices are extended while keeping the addition operations valid, then they are extended by adding zero-valued elements.
|0> + |3>;
/*
| 1 | + | 0 | = | 1 | + | 0 | = | 1 |
| 0 | | 0 | | 0 | | 0 | | 0 |
| 0 | | 0 | | 0 | | 0 |
| 1 | | 1 | | 0 | | 1 |
*/
The square matrices are extended while keeping the transformation operations of the additional qubits valid, and then they are extended by left-multiplying tensorly by the identity matrix of size equal to the number of missing qubit states.
Z(0) * X(1);
/*
(I(1) x Z(0) * H(1))
| 1 0 | * | 0 0 1 0 | =
| 0 -1 | | 0 0 0 1 |
| 1 0 0 0 |
| 0 1 0 0 |
= ( | 1 0 | x | 1 0 | ) * | 0 0 1 0 |
| 0 1 | | 0 -1 | | 0 0 0 1 |
| 1 0 0 0 |
| 0 1 0 0 |
= | 1 0 0 0 | * | 0 0 1 0 |
| 0 -1 0 0 | | 0 0 0 1 |
| 0 0 1 0 | | 1 0 0 0 |
| 0 0 0 -1 | | 0 1 0 0 |
[ 0.0, 0.0, 1.0, 0.0
0.0, 0.0, 0.0, -1.0
1.0, 0.0, 0.0, 0.0
0.0, -1.0, 0.0, 0.0 ]
*/
The language implements several basic functions.
sqrt(arg)
implements the square root function of complex numbers;
sqrt(4); // 2.0
sqrt(-1); // i
normalise(arg)
implements the normalization of argument returning that || normalise(arg) || = 1
normalise(|0>+i*|1>); (0.70710677) |0> + (0.70710677 i) |1>
The qu language supports variable storing values such as integer, complex number or matrices.
The value of variable is assigned with assignment statement let
and can be used in expression by using the variable identifier.
let a = 1; // store 1 into variable a
a + 2; // read variable value (1) a and add 2
// 3
The command clear
allows to clear all the variables.
clear();
The following is the BNF syntax of qu language
<code-unit> ::= <statement> | <code-unit>
<statement> ::= <clear-stm>
| <assign-stm>
| <exp-stm>
<clear-stm> ::= "clear" "(" ")" ";"
<assign-stm> ::= "let" <assign-var-identifier> "=" <exp> ";"
<exp-stm> ::= <exp> ";"
<exp> ::= <add-exp>
<add-exp> ::= <multiply-exp>
| <add-exp> "+" <multiply-exp>
| <add-exp> "-" <multiply-exp>
<multiply-exp> ::= <cross-exp>
| <multiply-exp> "*" <cross-exp>
| <multiply-exp> "/" <cross-exp>
<cross-exp> ::= <unary-exp>
| <cross-exp> "x" <unary-exp>
<unary-exp> ::= <dagger-exp>
| "+" <unary-exp>
| "-" <unary-exp>
<dagger-exp> ::= <primary-exp>
| <dagger-exp> "^"
<primary-exp> ::= "i"
| "pi"
| "e"
| <int-literal>
| <real-literal>
| <bra-exp>
| <ket-exp>
| <function-exp>
| <identifier>
| "(" <exp> ")"
<bra-exp> ::= "<" "+" "|"
| "<" "-" "|"
| "<" "i" "|"
| "<" "-i" "|"
| "<" <exp> "|"
<bra-exp> ::= "<" "+" "|"
| "<" "-" "|"
| "<" "i" "|"
| "<" "-i" "|"
| "<" <exp> "|"
<ket-exp> ::= "|" "+" ">"
| "|" "-" ">"
| "|" "i" ">"
| "|" "-i" ">"
| "|" <exp> ">"
<function-exp> ::= <function-id> "(" <args-exp>? ")"
<function-id> ::= "sqrt"
| "I"
| "H"
| "S"
| "T"
| "X"
| "Y"
| "Z"
| "SWAP"
| "CNOT"
| "CCNOT"
| "ary"
| "sim"
| "eps"
| "qubit0"
| "qubit1"
| "normalise"
<args-exp> ::= <exp>
| <args-exp> "," <exp>
_Release 0.3.1