This repository is a collection of c-optimized computation primitive available as python module. When possible, the low level function uses INTEL SIMD primitives.
It is recommended to create a virtual environment for instance with virtualenv.
virtualenv -p python3 venv
source venv/bin/activate
pip install pip --upgradeDependencies are: pytest, numpy:
pip install pytest numpy --upgradepip install quandelibc
This will install the latest released version with precompiled builds.
Alternatively, you can checkout code from gitlab and compile the module:
git clone git@github.com:Quandela/QuandeLibC.git --recurse-submodules
cd quandelibcThen compile, install and test the module:
pip install .
pytest testsimport quandelibc as qc
qc.permanent_fl([[1]])The functions permanent_in/permanent_re/permanent_cx compute the permanent of a 2D-matrix:
permanent_fl(M, nthreads=0)Where:
Mhas to be a square int/float/complex matrixnthreadsis indicating the number of threads to use for the calculation.nthreads=0will usethread::hardware_concurrency()from hardware configuration. To be tuned based on other tasks running on the server.
Computation uses Ryser algorithm with graycode index optimization, uses AVX primitives for number multiplication, and run on multiple threads. This has a complexity of O(n.2^n).
Note that for 1 or 2 threads, Glynn algorithm will be used (https://en.wikipedia.org/wiki/Computing_the_permanent#Balasubramanian–Bax–Franklin–Glynn_formula), for 3+ threads Ryser algorithm will be used (https://en.wikipedia.org/wiki/Computing_the_permanent#Ryser_formula).
TBD
FockState is a class to manipulate efficiently fock states - it provides the following methods:
- Space-insensitive constructor from strings like:
|s1, ..., sm>or alternative representations using[],()or|〉. - constructor of vaccum state
FockState(m)=>|0,...,0> - constructor of elementary state
FockState(m,mk)=>|0, 0, ..., 1, ..., 0> +and+=operators - to combine two fock states++or+(int) to produce following fockstates in (m,n)-FSArrayorder with end condition|0,0,0,...,m>+1==|0,0,0,...0>- cast to python iterator e.g:
list(fs) => [s1,...,sm] - string serialization
str(fs) => |s1,s2,...,sm> __hash__function allowing them to be indexed- comparison operators to check egality or differences of two fock states
__getitem__methods providing direct access to mode:fs[4]__copy__function allowing shallow and deepcopymandnproperties:fs.m,fs.nprodnfactequals toprod(mi=1;mi<=m) !s_mi
FSArray is a class representing all the possible fock states for given in (m,n) fock space.
An FSArray is simply built given these 2 parameters.
>>> fsa = qc.FSArray(10,5)
>>> print(fsa)
<quandelibc.FSArray object at 0x109d7d830>The internal structure is built as soon as methods __getitem__, find, save is called, or when a FSArray is used to build a FSMap (see below).
It is therefore possible to request the size in bytes of the structure in memory, or the count of different fock states using size or count methods before an actual building of the object.
>>> print(fsa.size(), fsa.count())
10010 2002Note that due the nature of the fock state spaces - the size can quickly goes far beyond available memory.
>>> fsa = qc.FSArray(36,18)
>>> print("size=%fGb" % (fsa.size()/1024./1024/1024))
size=1083236.358076GbInternally, the fock states are represented by a sequence M1M2M3M4...Mn where each Mi represents the mode of i-th photon, and the Mis are sorted in increasing order. This representation is unique and more dense than the reverse fock representation s1...sn.
Practically, the number of modes is restricted to 255 allowing to represent each mode as a char. For readibility, mode 0 is represented by letter A, mode 1 letter B, ...
NOTE: The (m,n) fock-state array size is n.C(m+n-1,n) - by restricting to 4 bits only (32 modes maximum), the fock states could be compacted 50% - however as we will see below
FSMaprepresentations are actually far larger so this optimisation is not necessary.
When a FSArray is built, the generated state list is in lexicographic order on that internal representation. For instance:
>>> fsa = qc.FSArray(4,2)
>>> for i in range(fsa.count()):
... print(fsa[i])
[2, 0, 0, 0] # corresponding to AA
[1, 1, 0, 0] # corresponding to AB
[1, 0, 1, 0] # corresponding to AC
[1, 0, 0, 1] # corresponding to AD
[0, 2, 0, 0] # corresponding to BB
[0, 1, 1, 0] # corresponding to BC
[0, 1, 0, 1] # corresponding to BD
[0, 0, 2, 0] # corresponding to CC
[0, 0, 1, 1] # corresponding to CD
[0, 0, 0, 2] # corresponding to DDThis lexicographic order is useful to retrieve efficiently (in O(log_2 Mn) operations) the index of a given state through the find method:
>>> print(fsa.find([0, 2, 0, 0]))
4Last, FSArray objects can be serialized with save(path) method. If path is a directory, the object will create an object named layer-mM-nN.fsa containing a binary representation of the object. Otherwise, the provided filename will be used instead.
To retrieve a serialized object, you can use following constructors:
>>> fsa=FSArray(filename, m, n)or passing a directory, the generic name will be looked for:
>>> fsa=FSArray(dirname, m, n)It is also possible to iterate through all states of a FSArray without building it through iterators:
fsa=FSArray(20,10)
for fs in fsa:
print(fs)FSMap is a class doing the mapping between a (m,k) FSArray and a (m,k+1) FSArray.
For each of the state of the k-FSA, it is possible to add a photon in one of the m mode which can be represented as followed for (m,k)=(2,2)
AA+A=AAAAA+B=AABAB+A=AABAB+B=ABBBB+A=ABBBB+B=BBB
A (m,k)->(m,k+1) FSMap is built given only the (m,k+1)-FSArray. As for FSArray it is built lazily meaning that it is possible to get required size without actually trying to build the objects...
>>> fsa_3216 = qc.FSArray(32,16)
>>> fsm_3215_3216 = qc.FSMap(fsa_3216)
>>> print("%.2fGb" % (fsa_3216.size()/1024./1024/1024))
22399.91Gb
>>> print("%.2fGb" % (fsm_3215_3216.size()/1024./1024/1024))
91506.02GbThe number of keys is M_k, for each m possible modes, and each index is compressed to the minimal number of bytes able to address M_(k+1) states. In the following example, we have 32 modes, and the pointers are 6-bytes long:
>>> print(fsm_3215_3216.size()/fsm_3215_3216.count()/32)
6NOTE: The memory needed to keep in memory the full stack of (m,0)-(m,n)
FSArrays and the final layerFSArrayis represented in the following image. In white and green the configurations that will fit on any laptop (less than 8Gb of memory), in yellow and orange configurations that could run on a large memory server (upto 128Gb memory), in red configuration that could run on very large servers. In black - configurations that cannot be realistically simulated.
As for FSArray, objects can be serialized to files with save(file_or_dir_path) and deserialized with constructor FSMap(file_or_dir_path, m, n).
To find mapping between (m,k) fock state index and (m,k+1) fock state index - use get(idx, m) method:
>>> idx_kp1 = fsm.get(idx_k,mk)