The purpose of this notebook is to give a quick introduction to Qubiter's CSD quantum compiler capabilities.
By a quantum complier, we mean a computer program that takes as input an arbitrary unitary matrix $U$ of dimension $N=2^n$ and returns a SEO (sequence of elementary operations, in this case CNOTs and single qubit rotations) that equals $U$. There are various kinds of quantum compilers. Suppose $U$ is of the form $U=e^{-itH}$, where $t$ is time and $H$ is the Hamiltonian operator. If $H$ has a form which is known a priori, a situation that is common in Physics and Chemistry, then a popular way of expanding $U$ is by using the Trotter-Suzuki approximation. If the form of $H$ is not known a priori as is common in Artificial Intelligence, then we recommend using the CS (Cosine-Sine) decomposition of Linear Algebra. Both methods are already implemented in Qubiter, but this notebook is about the CSD.
Doing CSD quantum compiling with Qubiter requires using the classes in the quantum_CSD_compiler folder, which will only work properly if you install, besides all the Qubiter Python files and a Python distro that includes numpy and scipy (for example, Anaconda), some binary libraries prepared by Artiste-q.net which include a Python wrapper for a LAPACK subroutine called cuncsd.f that performs CSDs. How to install those binary libraries is explained elsewhere in this site. Henceforth, we will assume all the necessary files have been installed on your computer if you want to redo the calculations.
The quantum_CSD_compiler folder includes a pdf called csd-intro.pdf that gives an introduction to the CSD.
Some external references:
R.R. Tucci, A Rudimentary Quantum Compiler(2cnd Ed.) https://arxiv.org/abs/quant-ph/9902062
Qubiter 1.11, a C++ program whose first version was released together with Ref.1 above. Qubiter 1.11 is included in the quantum_CSD_compiler/LEGACY folder of this newer, pythonic version of Qubiter
R.R. Tucci, Quantum Fast Fourier Transform Viewed as a Special Case of Recursive Application of Cosine-Sine Decomposition, https://arxiv.org/abs/quant-ph/0411097
Qubiter applies CSD recursively to build a tree of node matrices. The product of those node matrices, if read in the correct order, is equal to the input matrix $U$.
As an example, let us use for $U$ a 3 qubit quantum Fourier matrix. We can create an object of class Tree with $U$ as input as follows
import os
import sys
print(os.getcwd())
os.chdir('../../')
print(os.getcwd())
sys.path.insert(0,os.getcwd())
/home/rrtucci/PycharmProjects/qubiter/qubiter/jupyter_notebooks /home/rrtucci/PycharmProjects/qubiter
import numpy as np
import cuncsd_sq as csd
import math
from qubiter.FouSEO_writer import *
from qubiter.quantum_CSD_compiler.Tree import *
from qubiter.quantum_CSD_compiler.DiagUnitarySEO_writer import *
from qubiter.quantum_CSD_compiler.MultiplexorSEO_writer import *
loaded OneQubitGate, WITHOUT autograd.numpy
num_qbits = 3
init_unitary_mat = FouSEO_writer.fourier_trans_mat(1 << num_qbits)
emb = CktEmbedder(num_qbits, num_qbits)
file_prefix = 'csd_test'
t = Tree(True, file_prefix, emb, init_unitary_mat, verbose=False)
t.close_files()
The above code automatically creates an expansion of $U$ into DIAG and MP_Y lines. Next we print the Picture file that was created.
t.print_pic_file(jup=True)
1 | %---%---% | 2 | %---%---Ry | 3 | %---%---% | 4 | %---Ry--% | 5 | %---%---% | 6 | %---%---Ry | 7 | %---%---% | 8 | Ry--%---% | 9 | %---%---% | 10 | %---%---Ry | 11 | %---%---% | 12 | %---Ry--% | 13 | %---%---% | 14 | %---%---Ry | 15 | %---%---% |
Let us also print the corresponding English file that was created.
t.print_eng_file(jup=True)
1 | DIAG IF 2:2 1:1 0:0 BY 0.000000 67.500000 0.000000 -112.499998 0.000000 -112.499998 0.000000 67.500000 | 2 | MP_Y AT 0 IF 2:1 1:0 BY 44.999981 45.000022 44.999991 45.000008 | 3 | DIAG IF 2:2 1:1 0:0 BY 0.000000 180.000005 135.000000 134.999987 0.000000 0.000000 135.000028 -44.999994 | 4 | MP_Y AT 1 IF 2:1 0:0 BY 17.632183 72.367794 17.632188 72.367815 | 5 | DIAG IF 2:2 1:1 0:0 BY 0.000000 -179.999991 0.000000 0.000009 0.000000 0.000002 0.000000 -179.999964 | 6 | MP_Y AT 0 IF 2:1 1:0 BY 45.000011 45.000001 45.000005 45.000015 | 7 | DIAG IF 2:2 1:1 0:0 BY -179.999991 179.999991 179.999991 179.999991 90.000003 -89.999996 89.999962 -90.000003 | 8 | MP_Y AT 2 IF 1:1 0:0 BY 3.749999 26.249993 63.750008 86.249996 | 9 | DIAG IF 2:2 1:1 0:0 BY 0.000000 -89.999996 0.000000 -90.000023 0.000000 89.999996 0.000000 90.000003 | 10 | MP_Y AT 0 IF 2:1 1:0 BY 45.000008 45.000001 44.999991 45.000001 | 11 | DIAG IF 2:2 1:1 0:0 BY 0.000000 0.000000 0.000019 179.999978 180.000005 180.000005 -179.999991 -0.000019 | 12 | MP_Y AT 1 IF 2:1 0:0 BY 17.632203 72.367801 17.632198 72.367801 | 13 | DIAG IF 2:2 1:1 0:0 BY 0.000000 0.000002 0.000000 179.999991 0.000000 0.000017 0.000000 -179.999991 | 14 | MP_Y AT 0 IF 2:1 1:0 BY 44.999988 44.999991 44.999994 44.999988 | 15 | DIAG IF 2:2 1:1 0:0 BY 67.500014 -44.999998 22.500001 89.999982 -22.500001 44.999994 -67.500014 -180.000005 |
In the Picture files, a DIAG appears as a chain of percents, whereas an MP_Y appears as a chain of percents and an additional Ry gate. As you can see, the Picture file gives a nice picture of the DIAG and MP_Y gates, but the English file is much more specific. Look at Qubiter's Rosetta Stone (qubiter_rosetta_stone.pdf) if you want to understand how to interpret the parameters of DIAG and MP_Y lines.
A DIAG represents a diagonal matrix with diagonal entries that are unit magnitude complex numbers, making the matrix unitary.
An MP_Y represents a matrix of the form
$\left[\begin{array}{cc} cc & ss \\ -ss & cc \end{array}\right]$
where $cc$ and $ss$ are real diagonal matrices of the same size such that $cc^2 + ss^2 = 1$.
The class DiagUnitaryExpander will take any English file and write new English and Picture files wherein
multiline expansion.
Likewise, the class MultiplexorExpander will take any English file and write new English and Picture files wherein
multiline expansion.
We could use these 2 expander classes to construct new English and Picture files from the English file printed above. This would lead to an English file that consisted of only CNOTs and qubit rotations. If the gates of that new English file were multiplied, the product would equal the original $U$. Such an English file would be very long and not too instructive so we won't show it here. Instead, we will show an exact expansion of a single DIAG and of a single MP_Y.
Next, we create English and Picture files containing an expansion of the 4 qubit gate
%---%---%---%
This represents a diagonal unitary matrix. The angles are chosen at random and stored in the variable rad_angles. We then print the Picture file.
file_prefix = "d_unitary_exact_check"
num_qbits = 4
num_angles = (1 << num_qbits)
emb = CktEmbedder(num_qbits, num_qbits)
rad_angles = list(np.random.rand(num_angles)*2*np.pi)
wr = DiagUnitarySEO_writer(file_prefix, emb, 'exact', rad_angles)
wr.write()
wr.close_files()
wr.print_pic_file(jup=True)
1 | | | | Ph | 2 | | | | Rz | 3 | | | @---X | 4 | | | | Rz | 5 | | | @---X | 6 | | | Rz | | 7 | | @---X | | 8 | | | Rz | | 9 | | @---X | | 10 | | | @---X | 11 | | @---+---X | 12 | | | | Rz | 13 | | | @---X | 14 | | | | Rz | 15 | | @---+---X | 16 | | Rz | | | 17 | @---X | | | 18 | | Rz | | | 19 | @---X | | | 20 | | @---+---X | 21 | @---+---+---X | 22 | | | | Rz | 23 | | | @---X | 24 | | | | Rz | 25 | | | @---X | 26 | | @---+---X | 27 | @---+---+---X | 28 | | @---X | | 29 | @---+---X | | 30 | | | Rz | | 31 | | @---X | | 32 | | | Rz | | 33 | @---+---X | | 34 | | | @---X | 35 | @---+---+---X | 36 | | | | Rz | 37 | | | @---X | 38 | | | | Rz | 39 | @---+---+---X | 40 | Rz | | | |
We can check that our exact expansion is correct as follows. We can multiply the gates of the expansion using the class SEO_MatrixProduct. Call the gate product matpro.prod_arr. Using the angles rad_angles that we stored, we can construct the exact diagonal unitary, call it exact_mat. Call err the norm of matpro.prod_arr - exact_mat, and print err.
matpro = SEO_MatrixProduct(file_prefix, num_qbits)
exact_mat = DiagUnitarySEO_writer.du_mat(rad_angles)
err = np.linalg.norm(matpro.prod_arr - exact_mat)
print("diag unitary error=", err)
diag unitary error= 8.097806875329685e-08
Next, we create English and Picture files containing an expansion of the 4 qubit gate
Ry--%---%---%
This represents a multiplexor matrix. The angles are chosen at random and stored in the variable rad_angles. We then print the Picture file.
file_prefix = "plexor_exact_check"
num_qbits = 4
num_angles = (1 << (num_qbits-1))
emb = CktEmbedder(num_qbits, num_qbits)
rad_angles = list(np.random.rand(num_angles)*2*np.pi)
wr = MultiplexorSEO_writer(file_prefix, emb, 'exact', rad_angles)
wr.write()
wr.close_files()
wr.print_pic_file(jup=True)
1 | Ry | | | | 2 | X---+---+---@ | 3 | Ry | | | | 4 | X---+---@ | | 5 | Ry | | | | 6 | X---+---+---@ | 7 | Ry | | | | 8 | X---@ | | | 9 | Ry | | | | 10 | X---+---+---@ | 11 | Ry | | | | 12 | X---+---@ | | 13 | Ry | | | | 14 | X---+---+---@ | 15 | Ry | | | | 16 | X---@ | | |
We can check that our exact expansion is correct as follows. We can multiply the gates of the expansion using the class SEO_MatrixProduct. Call the gate product matpro.prod_arr. Using the angles rad_angles that we stored, we can construct the exact multiplexor matrix, call it exact_mat. Call err the norm of matpro.prod_arr - exact_mat, and print err.
matpro = SEO_MatrixProduct(file_prefix, num_qbits)
exact_mat = MultiplexorSEO_writer.mp_mat(rad_angles)
err = np.linalg.norm(matpro.prod_arr - exact_mat)
print("multiplexor error=", err)
multiplexor error= 3.855018685550896e-08
A moral of the above calculations is that using CSD quantum compiling blindly will give a SEO for a quantum Fourier Transform QFT that is exponential in the number of qubits $n$. And yet we know that Coppersmith came up with an expansion for the QFT that is polynomial in $n$. But there is hope: CSD is not a unique decomposition. Ref.3 explains how one can coax a CSD compiler to yield Coppersmith's decompostion.
Let $U$ be N dimensional, with $N = 2^n$, where $n$ = number of qubits. A general N dimensional unitary matrix has $N^2$ dofs (real degrees of freedom). That's because it has $N^2$ complex entries, so $2N^2$ real parameters, but those parameters are subject to N real constraints and N(N-1)/2 complex constraints, for a total of $N^2$ real constraints. So $2N^2$ real parameters minus N^2 real constraints gives $N^2$ dofs.
(a) Each DIAG (MP_Y, resp.) line of the CS decomp of $U$ depends on N (N/2, resp.) angles and there are about N DIAG and N MP_Y lines. So the DIAG lines alone have enough dofs, $N^2$ of them, to cover all $N^2$ dofs of $U$. So clearly, there is a lot of redundancy in the CS decomp used by Qubiter. But, there is hope: the CS decomp is not unique, and it might be possible to choose a CS decomp that makes zero many of the angles in the DIAG and MP_Y lines.
(b) The CS decomp as used here leads to order $N^2 = 2^{2n}$ cnots and qubit rotations so it is impractical for large N. But for small N, it can be useful. For large N, it might be possible to discover approximations to individual MP_Y and DIAG lines.
Clearly, there is much room for future research to improve (a) and (b).