AutoQ 2.0 is a command-line utility written in C++ for verifying partial correctness of quantum programs automatically based on non-deterministic finite tree automata (NFTA) along with the concept of Hoare-style proof systems. Consider a triple
The following is a mini illustration of the functionality of this tool.
OPENQASM 3;
include "stdgates.inc";
qubit[2] qb; // quantum bits
// P: {|00>, 0.5|00> + 0.5|01> + 0.5|10> + 0.5|11>}
x qb[0];
// {|10>, 0.5|10> + 0.5|11> + 0.5|00> + 0.5|01>}
x qb[1];
// {|11>, 0.5|11> + 0.5|10> + 0.5|01> + 0.5|00>}
h qb[1];
// C(P): {|10>/√2 - |11>/√2, |10>/√2 + |00>/√2}
/******************************************************/
// Q1: {|10>/√2 - |11>/√2, |10>/√2 + |00>/√2} -> pass
// Q2: {|10>/√2 - |11>/√2, |10>/√2 - |00>/√2} -> fail
The program starts from an initial set of quantum states
Our program currently supports
-
Install the following dependencies via the command line.
$ sudo apt install git make cmake g++ libboost-filesystem-dev libboost-test-dev
. -
We need the Z3 solver to solve the constraints when the NFTA contains some symbolic variables. Since the installation process of Z3 may take a lot of work, I have provided the prebuilt shared library of z3-4.12.5 and the corresponding header files for users' convenience.
-
Finally, in the project's root directory, choose one of the following command to run.
-
$ make release -j8
(with8
replaced with the number of threads you want):
For compiling the source code of the library and the command-line interface with compiler optimizations turned on. -
$ make debug -j8
(with8
replaced with the number of threads you want):
For compiling the library into a form suitable for debugging (i.e., with optimizations turned off and some additional runtime checks enabled).
-
Since $ make release -j8
will ignore assertions in source codes, one should always use $ make release -j8
for better performance when conducting experiments.
It is recommended to run $ make test
in the repository's root directory after compiling the source codes to run several unit tests and check that the implementation is correct. This project has also been tested on Ubuntu 22.04.3 LTS. It should work on other Linux distributions as well.
The compiled command-line binaries are located in ${PROJECT_ROOT}/build/cli/
. The corresponding source codes are located in ${PROJECT_ROOT}/cli/
. The major one is ${PROJECT_ROOT}/cli/autoq_cav24.cc
. The others are auxiliary tools but not well-documented.
The usage is $ ./build/cli/autoq_cav24 P.{hsl|spec} C.qasm Q.{hsl|spec}
.
$ ./build/cli/autoq_cav24 benchmarks/Grover_while/03/pre.spec benchmarks/Grover_while/03/circuit.qasm benchmarks/Grover_while/03/post.spec
The quantum program has [7] qubits and [50] gates.
The verification process [passed] with a running time of [0.6s] and a memory usage of [44MB].
This binary also supports the -l
option, which is used for printing the statistics whose format is suitable for tables in LaTeX.
AutoQ 2.0 provides two file extensions *.hsl
and *.spec
for users to indicate which format they use to describe a set of quantum states. The easiest one is *.hsl
, which does not require users to have a background in NFTA. This file may contain multiple lines. Each line represents a quantum state. A quantum state is naturally described by a linear combination of computational basis states with complex coefficients. Coefficients here can be expressed in [addition +
], [subtraction -
], [multiplication *
] operations on [rationals], ^
] operation with "nonnegative" exponents. Operator precedence follows the standard convention. You can also do / sqrt2 ^ k
after the above operations are already done if you wish. Nevertheless, due to the automatic scaling in the verification process, users do not need
Here is one example.
Extended Dirac
(-1 + -1 * ei2pi(2/8) + -2 * ei2pi(3/8)) |10> + ei2pi(3/8) |11> - ei2pi(1/8) |01>
ei2pi(1/8) |00> + (1 + 1 * ei2pi(2/8) + -2 * ei2pi(3/8)) |11> - ei2pi(3/8) |10>
This file describes two quantum states
For simplicity, one can define some complex constants in the Constants section before the Extended Dirac section, and the example becomes
Constants
c1 := ei2pi(1/8)
c2 := ei2pi(2/8)
c3 := ei2pi(3/8)
Extended Dirac
(-1 + -1 * c2 + -2 * c3) |10> + c3 |11> - c1 |01>
c1 |00> + (1 + 1 * c2 + -2 * c3) |11> - c3 |10>
Nonconstant tokens not defined in the Constants section are automatically regarded as free symbolic variables. These variables may have some constraints such as being nonzero. One can impose some constraints on these variables in the Constraints section after the Extended Dirac section. For instance,
Constants
c0 := 0
Extended Dirac
c0 |00> + c0 |11> + v |*>
Constraints
imag(v) = 0
the above file describes (at least) all quantum states which are linear combinations of
The Constraints section may contain multiple lines. Each line consists of a boolean formula that will be automatically conjoined (with the and operator) eventually. Each formula is expressed in logical operations [not !
], [and &
], [or |
] on boolean subformulae. These subformulae are expressed in comparison operations [greater than >
], [less than <
] on real numbers and the [equal =
] operation on complex numbers. Operator precedence follows the standard convention. AutoQ 2.0 also supports two functions real(.)
and imag(.)
to extract the real part and the imaginary part of a complex number.
One may want to take the absolute value of a real number in some applications. Due to the branching nature of this operation, the SMT solver may not be able to solve constraints involving this operation. Please use (.) ^ 2
as an alternative instead.
We say a description file contains a quantum state
For convenience, AutoQ 2.0 also supports the tensor product operator #
. The usage is very easy: just put #
between two quantum states \/ |i|=n :
over all
One more example to get a closer look at *.hsl
.
Extended Dirac
\/ |i|=3 : |i> # vH |i> + vL |*> # |000>
Constraints
imag(vH) = 0
imag(vL) = 0
vH > vL
vL > 0
describes the set of states
where
Finally, we should be noticed that not all strings described by *.hsl
are valid quantum states. For instance, the sum of absolute squares of amplitudes of all computational basis states may not be
Our current implementation of *.hsl
has not been optimized yet. If the number of qubits is greater than 10, we strongly recommend you use *.spec
. The explanation of *.spec
is left in appendices.
AutoQ 1.0 only supports quantum circuits, but AutoQ 2.0 also supports quantum programs in addition. The extension focuses on the additional support for control flow statements such as branching (if-else) and looping (while). In this section, we will introduce these two kinds of control flow statements, along with measurement, which is used to decide the control flow path.
...
qubit[1] qb; // quantum bit
bit[1] outcome; // classical bit
...
// S: {|0>/√2 + |1>/√2}
outcome[0] = measure qb[0];
// S0: {|0>/√2}
// S1: {|1>/√2}
The usage of a measurement operator should be [a classical bit: c] = measure [a quantum bit: q];
.
The evolution of the set of quantum states in AutoQ 2.0 is described as follows. Let q=0
and q=1
of this operator, so after the measurement we define one set
This operator cannot be used standalone in AutoQ 2.0, it can only be used along with branching (if-else) and looping (while) introduced below. Please refer to them for more details.
...
qubit[1] qb; // quantum bit
bit[1] outcome; // classical bit
...
// S: {|0>/√2 + |1>/√2}
outcome[0] = measure qb[0];
if (!outcome[0]) { // S0: {|0>/√2}
x qb[0];
} // S0': {|1>/√2}
else { // S1: {|1>/√2}
h qb[0];
} // S1': {|0>/2 - |1>/2}
// (S0')∪(S1'): {|1>/√2, |0>/2 - |1>/2}
The usage of an if-else block in general should be
[a classical bit: c] = measure [a quantum bit: q];
if (c) {
...
}
else {
...
}
, but sometimes if (c)
may be replaced with if (!c)
and sometimes the else {...}
block may be omitted. The reason why we need a measurement operator at the beginning is that we need to produce
AutoQ 2.0 will execute the if
block with else
block with if (c)
is replaced with if (!c)
, then the if
(resp., else
) block will be executed with
If the else
block is omitted, AutoQ 2.0 simply sees that block as an identity gate.
A subtle thing should be noticed is that the statement else {
cannot be on the same line with the closing bracket }
of the previous if
block since AutoQ 2.0 needs to detect the termination of the previous if
block. Please refer to this example for its usage.
...
qubit[1] qb; // quantum bit
bit[1] outcome; // classical bit
...
// S: {|0>/√2 + |1>/√2}
outcome[0] = measure qb[0];
while (outcome[0]) { // I: {|0>/√2 + |1>/√2}
h qb[0];
z qb[0];
outcome[0] = measure qb[0];
}
// {|0>/√2}
The usage of a while loop in general should be
[a classical bit: c] = measure [a quantum bit: q];
while (c) { // invariant.{hsl|spec}
...
c = measure q;
}
, but sometimes while (c)
may be replaced with while (!c)
.
Unlike the if-else block, the NFTA does not split into two after a while loop. Instead, AutoQ 2.0 first checks whether the set of quantum states invariant.{hsl|spec}
(i.e.,
If while (c)
is replaced with while (!c)
, then
The file paths for specifying loop invariants are relative to the circuit file's location. Please refer to this example for its usage.
The following figure demonstrates how we use a tree to represent a
Please refer to Example 1.1.3 for a better understanding of NFTA. In short, the only difference between NFTA and NFA is that NFAs must have a starting state, but NFTAs do not. The tree language recognition procedure checks if an NFTA
to a (unary) state
Since the underlying structure of a set of quantum states is still an NFTA in AutoQ 2.0, we reserve the *.spec
format for users to describe a set of quantum states with an NFTA. The Constants and Constraints sections remain, but the Extended Dirac section should be replaced with two new sections Root States and Transitions now. (Unary) states in an NFTA can be arbitrary strings (no need to enclose with double quotation marks).
This section is responsible for specifying a set of root states. It should contain only one line starting with "Root States" and ending with a set of root states separated by whitespaces.
This section is responsible for specifying a set of transitions. One transition per line. A (bottom-up) transition [f](q1, q2, ..., qn) -> q
. Note that in this tool, a symbol can only be a positive integer
We close this section with the following example.
Constants
c0 := 0
Root States aR bR
Transitions
[1](aL1, aL1) -> aR
[2](qLow, q0) -> aL1
[1](bL1, bL1) -> bR
[2](q0, qHigh) -> bL1
[c0] -> q0
[p1] -> qLow
[p2] -> qHigh
Constraints
imag(p1) = 0
p1 ^ 2 < 1/8
imag(p2) = 0
p2 ^ 2 > 7/8