ABSTRACT (original article): We introduce an open source software package UniversalQCompiler written in Mathematica that allows the decomposition of arbitrary quantum operations into a sequence of single-qubit rotations (with arbitrary rotation angles) and controlled-NOT (C-NOT) gates. Together with the existing package QI, this allows quantum information protocols to be analysed and then compiled to quantum circuits. Our decompositions are based on Phys. Rev. A 93, 032318 (2016), and hence, for generic operations, they are near optimal in terms of the number of gates required. UniversalQCompiler allows the compilation of any isometry (in particular, it can be used for unitaries and state preparation), quantum channel, positive-operator valued measure (POVM) or quantum instrument, although the run time becomes prohibitive for large numbers of qubits. The resulting circuits can be displayed graphically within Mathematica or exported to LaTeX. We also provide functionality to translate the circuits to OpenQASM, the quantum assembly language used, for instance, by the IBM Q Experience. CITATION (original article): Raban Iten, Oliver Reardon-Smith, Emanuel Malvetti, Luca Mondada, Gabrielle Pauvert, Ethan Redmond, Ravjot Singh Kohli, Roger Colbeck (2021), Introduction to UniversalQCompiler, arXiv:1904.01072. https://doi.org/10.48550/arXiv.1904.01072​
GitHub: https://github.com/Q-Compiler/UniversalQCompiler
UniversalQCompiler provides a Mathematica package that allows to decompose generic quantum computations into sequences of C-NOT gates and arbitrary single-qubit rotations. In particular, the package allows for the following:
◼
  • Decomposing isometries from m to n qubits, and hence, in particular decomposing arbitrary unitaries on m qubits and to do state preparation
  • ◼
  • Decomposing quantum channels from m to n qubits
  • ◼
  • Decomposing POVMs on m qubits
  • ◼
  • Simplifying gate sequences
  • ◼
  • Drawing quantum circuits within Mathematica
  • ◼
  • Exporting quantum circuits to LATEX
  • ◼
  • Running quantum circuits on the IBM Q Experience (using the OpenQASM converter)
  • ◼
  • Compilation for trapped ions, i.e., converting between gate sequences using CNOT (and single-qubit rotation) to those that use Molmer-Sorensen gates
  • A detailed documentation of the Mathematica package can be found on our webpage. The notebook Examples.nb helps the user to get started quickly and provides a short overview over the methods provided by UniversalQCompiler.
    ​
    This project also contains a converter (based on ProjectQ) to translate gate sequences from the Mathematica package UniversalQCompiler to OpenQASM, the quantum assembly language used, for instance, by the IBM Q Experience.
    ​
    Moreover, we provide bindings to directly link Python to Mathematica. This is however provided without any guarantees or support in the directory UNSTABLE

    Overview

    The diagram at the top indicates how the Mathematica packages QI and UniversalQCompiler can be used to analyse quantum information protocols for running on some quantum hardware.

    Getting started

    To use our Mathematica package UniversalQCompiler.m, you need to have installed Wolfram Mathematica (we tested the package for Mathematica 11.1 and 11.3). The code relies on the package QI.m which can be downloaded from https://github.com/rogercolbeck/QI. The package can then be loaded in any Mathematica notebook (see our documentation for more details).
    Note that the Makefile is only used for the unstable bindings to Python. You do not need any Makefile for the Mathematica package or the QASM converter.

    Basic Gate types

    This package decomposes arbitrary gates into a sequence of CNOTs and single qubit rotations. The latter are expressed using the convention that an rotation by angle t about the x-axis of the Bloch sphere is
    In[]:=
    RxM[t,1]//MatrixForm
    Out[]//MatrixForm=
    Cos
    t
    2
    
    Sin
    t
    2
    
    Sin
    t
    2
    
    Cos
    t
    2
    
    A y-rotation by angle t is:
    In[]:=
    RyM[t,1]//MatrixForm
    Out[]//MatrixForm=
    Cos
    t
    2
    
    Sin
    t
    2
    
    -Sin
    t
    2
    
    Cos
    t
    2
    
    And a z-rotation is
    In[]:=
    RzM[t,1]//MatrixForm
    Out[]//MatrixForm=
    t
    2
    
    0
    0
    -
    t
    2
    
    A CNOT with qubit 1 as the control and 2 as the target is
    In[]:=
    CNOTM[1,2]//MatrixForm
    Out[]//MatrixForm=
    1
    0
    0
    0
    0
    1
    0
    0
    0
    0
    0
    1
    0
    0
    1
    0

    Single-qubit unitaries

    Choose a random 2x2 unitary and decompose it into a Z rotation, a Y rotation then a Z rotation
    u=PickRandomUnitary[2]
    {{-0.35917-0.548312,0.536212-0.531815},{-0.184883+0.732235,0.654784+0.0301123}}
    gates=ZYZDec[u]
    {{3,4.91376,1},{2,1.71197,1},{3,5.45595,1}}
    This is the standard form for gate outputs. The first gate in the list is an Rz on qubit 1, etc. We can construct the unitary from these (up to phase) as follows. To get a reminder of the conventions, use GateTypes[]
    GateTypes[]
    Gate types for UniversalQCompiler
    {-2,diag,act}: diagonal gate with entries diag on the qubits listed in act
    {-1,n,m}: CZ with qubit n the control and m the target
    {0,n,m}: CNOT with qubit n the control and m the target
    {1,t,n}: x-rotation by angle t for qubit n
    {2,t,n}: y-rotation by angle t for qubit n
    {3,t,n}: z-rotation by angle t for qubit n
    {4,0,n}: trace out qubit n
    {4,1,n}: measure qubit n in the computational basis
    {5,0,n}: qubit n starts in state |0>
    {5,1,n}: qubit n starts in state |1>
    {6,0,n}: qubit n is postselected on |0>
    {6,1,n}: qubit n is postselected on |1>
    {-10,t,{n,m}}: XX-gate with angle t on qubits n,m
    {-11,{t,p},n}: R-gate with angles t,p on qubit n
    Confirm the result (note that decompositions are always up to phase)
    outu=CreateOperationFromGateList[gates]
    {{0.298295-0.583669,0.727634+0.202237},{-0.727634+0.202237,0.298295+0.583669}}
    This is equal to u up to phase:
    Chop[outu/outu[[1,1]]*u[[1,1]]-u]
    {{0,0},{0,0}}
    We get the same result from the Quantum Shannon Decomposition
    gatelist=QSD[u]
    {{3,4.91376,1},{2,1.71197,1},{3,5.45595,1}}
    We can also re-express the output in string form
    ListFormToStr[gatelist]
    {Rz(4.91376)(1),Ry(1.71197)(1),Rz(5.45595)(1)}
    Or as matrices
    Map[MatrixForm,ListFormToOp[gatelist]]
    
    -0.774599+0.632452
    0.+0.
    0.+0.
    -0.774599-0.632452
    ,
    0.655477+0.
    0.755216+0.
    -0.755216+0.
    0.655477+0.
    ,
    -0.915672+0.401926
    0.+0.
    0.+0.
    -0.915672-0.401926
    
    Or draw the topology for it:
    PrintCircuit[gatelist]
    If you use the package QCircuit in
    L
    A
    T
    E
    X
    then the command LatexQCircuit can be used to generate the circuit in a compatible form:
    LatexQCircuit[gatelist]
    {& \qw & \gate{R_z} & \gate{R_y} & \gate{R_z} & \qw }
    Using the option AnglePrecision2 gives the angles to two significant figures.
    PrintCircuit[gatelist,AnglePrecision2]
    LatexQCircuit[gatelist,AnglePrecision2]
    {​& \qw & \gate{R_z(\textnormal{$4.9$})} & \gate{R_y(\textnormal{$1.7$})} & \gate{R_z(\textnormal{$5.5$})} & \qw ​}

    Two qubit unitaries

    Using QSD

    Now choose a real two-qubit gate and do the Quantum Shannon Decomposition. The decomposition achieves the minimal number of required CNOT gates for arbitrary (and not necessarily generic) two qubit gates (and is equivalent to calling DecUnitary2Qubits in the considered case).
    u=RPickRandomUnitary[4];u//MatrixForm
    0.27403
    0.60353
    0.0491122
    0.747159
    -0.431234
    -0.223208
    0.826736
    0.284118
    0.854943
    -0.229555
    0.437897
    -0.156918
    -0.0895389
    0.730229
    0.349774
    -0.580006
    gatelist=QSD[u]
    {{3,4.71239,2},{2,1.5708,2},{3,3.04245,2},{3,3.14159,1},{2,2.0275,1},{3,1.5708,1},{0,2,1},{3,5.63206,1},{1,6.02488,1},{1,5.12899,2},{3,3.18062,2},{0,2,1},{2,1.5708,1},{1,1.5708,1},{2,1.5708,2},{3,1.5708,2},{0,1,2},{3,6.28319,2}}
    PrintCircuit[gatelist]
    We can check the result as before:
    We can also apply u to other qubits using the second argument of QSD to specify. E.g. to the second and fourth qubits
    Or, reversing the gates to the fourth and second qubits

    Using the Knill decomposition

    We could also use another decomposition
    In this case, the output is much longer, but still gives the same result

    Using the column by column decomposition

    The other decomposition we can do is the column-by-column decomposition

    Decomposition of isometries from m to n qubits

    We provide different methods to decompose arbitrary isometries from m to n qubits. Recall that an isometry from m to n qubits corresponds to a unitary operation on n qubits where n-m qubits start in a fixed state |0>.
    The method DecIsometry choses automatically the method that achieves the lowest CNOT count. Let’s consider how to decompose a random isometry from one qubit to three qubits.
    We can also use CreateOperationFromGateList to directly give the isometry

    CNOT counts for different decompositions

    Instead of using DecIsometry, a specific method to decompose an isometry can be chosen (in particular, to lower the run time). Which method is best (and chosen by DecIsometry) depends on the situation
    For a full unitary on 4 qubits:
    For an isometry from 1 qubit to 4 qubits
    For generic isometries, the CNOT count for the column-by-column decomposition can be improved by using a different way to decompose the first column (this may increase the running time for large isometries significantly):
    By default simplifications are done automatically. To switch them off use Simp->False. For the considered isometry, this gives the same CNOT count of the column-by-column decomposition, but with more single-qubit rotations.
    DecIsometryGeneric automatically picks the method with the lowest CNOT count for a generic isometry (which is the column by column decomposition with the option FirstColumn”StatePreparation” in the considered case).
    As an example for an non generic isometry, we consider an isometry of the following simple form:
    Note that DecIsometryGeneric doesn’t get the best count here, because it is based on generic isometries, but here we have a special case. DecIsometry recovers the best count.
    In the following case the Knill decomposition does best
    Again DecIsometryGeneric does not give the best, but DecIsometry does

    State preparation

    State preparation is just a special case of an isometry from m to n qubits, with m=0. We provide a method StatePreparation, which achieves the lowest known CNOT counts for generic states.
    Note that one gets the same output calling DecIsometry[state] in the considered case.
    This is equal to the state up to phase
    Alternatively, one can decompose a state using uniformly controlled gates by calling the column by column decomposition. This requires one CNOT gate more in the considered case (but lowers the running time for large states).
    We can also use a special decomposition for sparse states
    Dense state preparation is worse:

    Analytic calculations

    We can also do analytic calculations
    Print Circuit can also be used to show the output
    The option AnglePrecision->1 shows the angles (because they are exact, any precision above 0 can be used)
    If non-exact output is desired, can use NGateList
    The exact computations rapidly become time consuming
    The output matches the original isometry (up to phase)
    Generating the operator involves attempts to simplify so can be slow. The option FullSimp->False sometimes yields just as good a result after a final simplification (and can be faster, even including the final simplification):
    Using the option Simp->False can be faster (but gives a longer sequence):
    By comparison, the numerical version is almost instantaneous
    Again, the output matches:

    Using the Knill decomposition for analytic calculations

    The Knill decomposition is not good at handling analytic calculations at present. However, unitaries of a very simple form can be decomposed within a reasonable time.

    Sparse states analytically

    For sparse states we can generate an exact one using FPickRandomSparsePsi

    Decomposing channels

    We can also decompose channels. Take a channel from 1 qubit to 2 qubits with 10 Kraus operators
    Check that it is a valid channel
    Its action on a density operator rho is
    This channel has more than the maximum number of Kraus operators, so we can compress it to a smaller representation of the same channel
    The action on rho is the same:
    The output is quite long, but the first and last 20 gates are displayed below. Note the trace out circuit elements, and the indication of ancilla.
    We can reconstruct the channel as follows.
    First remove the traces from the gatelist
    We have a channel from 1 to 2 qubits, so the first 4 qubits of the input start in state 0. The partial traces can be used to generate Kraus operators by taking the projectors onto the computational basis:
    Check that it has the same action
    We can also use CreateOperationFromGateList

    Decomposing POVMs

    Similarly we can implement a POVM. Choose a random POVM on 1 qubit with 3 elements
    Check that it is a valid POVM: its elements should sum to identity and all operators should be positive
    We now decompose it into a list of gates
    Printing the last part of the circuit shows the measured qubits
    To get back the POVM from the gatelist:
    The zero POVM element can be dropped. We then get the same POVM as we started with
    We could also use CreatePOVMFromGateList
    CreatePOVMFromGateList automatically removes zero POVM elements if they occur at the end of the list of POVM elements. To avoid this, use the option DropZero->”None”.
    The option DropZero->”All” removes all POVM elements. Note that this can make it difficult to identify POVM elements with outcomes (in the case where zero elements are in the middle of the list of POVM elements).

    Constructing circuits

    We can also build our own circuits and use the code to simplify them
    We could also simplify the gate sequence analytically, by calling SimplifyGateList[gatelist]]. But this takes much longer:
    For simple constructions, ColumnByColumnDec is often best

    We can also generate quantum instruments

    If we both trace out and measure, we get a quantum instrument
    Each part of this should be a trace decreasing completely positive map such that the combined channel is trace preserving:
    If we care about the measurement, we can instead construct the POVM
    These agree with the channels
    The following command provides a convenient way to see the action of an instrument:
    We can also generate random instruments. The following gives an instrument from 1 qubit to 2 qubits comprising two channels each with 4 Kraus operators
    The start of the circuit is shown below
    And here is the end of the circuit
    To get back the instrument
    To check these are the same instrument we can extract the individual channels and confirm that they have the same Choi states:
    We can also observe the same action on a randomly chosen input
    To generate an instrument from 1 qubit to 2 comprising three channels with 1, 2 and 3 Kraus operators, use

    Random circuits can be constructed for isomteries

    To generate a random circuit for an isometry from 1 qubit to 2 qubits with 4 CNOTs. Note that after each CNOT there are 4 rotations, and at the start of the circuit there are also rotations.
    With TotGates->True this generates an isometry with the total number of gates, rather than the number of CNOTs

    CITE THIS NOTEBOOK

    UniversalQCompiler - an opensource software package for decomposing generic quantum computations​
    by Raban Iten, Oliver Reardon-Smith, Emanuel Malvetti, Luca Mondada, Gabrielle Pauvert, Ethan Redmond, Ravjot Singh Kohli, Roger Colbeck​
    Wolfram Community, STAFF PICKS, November 14, 2024
    ​https://community.wolfram.com/groups/-/m/t/3320494