Matrices
The last component we need to describe a quantum gate is a unitary matrix. DQCsim internally represents all normal gates (that is, everything except measurements and custom gates) using such matrices as a universal format that all plugins must be able to deal with. Note that Gate maps can help you with converting between this format and the format your plugin uses, if they differ.
To prevent DQCsim from turning into a math library, its matrix API is very basic. Matrices are constructed from a C array of its elements and are subsequently immutable.
dqcs_mat_new()
Constructs a new gate matrix.
Constructs a new gate matrix.
dqcs_handle_t dqcs_mat_new(
size_t num_qubits,
const double *matrix
)
num_qubits
must be set to the number of qubits mutated by this matrix.
It must be greater than or equal to zero.
matrix
must point to an appropriately sized array of doubles. The matrix
is specified in row-major form, using pairs of doubles for the real vs.
imaginary component of each entry. The size must be 4**num_qubits
complex
numbers = 2*4**num_qubits
doubles = 16*4**num_qubits
bytes,
representing a 2**num_qubits
by 2**num_qubits
matrix.
This function returns the constructed matrix handle, or 0 if an error
occurs.
While not enforced at this level, the matrix is normally unitary, or approximately so within some floating-point error margin.
This function returns the handle to the matrix, or 0 to indicate failure.
The following functions can be used to query the size of a matrix.
dqcs_mat_len()
Returns the number of complex entries in the given matrix.
Returns the number of complex entries in the given matrix.
ssize_t dqcs_mat_len(dqcs_handle_t mat)
This function returns -1 when an error occurs.
dqcs_mat_dimension()
Returns the dimension (number of rows == number of columns) of the given
matrix.
Returns the dimension (number of rows == number of columns) of the given matrix.
ssize_t dqcs_mat_dimension(dqcs_handle_t mat)
This function returns -1 when an error occurs.
dqcs_mat_num_qubits()
Returns the number of qubits targeted by the given matrix.
Returns the number of qubits targeted by the given matrix.
ssize_t dqcs_mat_num_qubits(dqcs_handle_t mat)
This function returns -1 when an error occurs.
The C array can of course also be recovered again.
dqcs_mat_get()
Returns a copy of the contained matrix as a C array.
Returns a copy of the contained matrix as a C array.
double *dqcs_mat_get(dqcs_handle_t mat)
If this function succeeds, the matrix is returned in row-major form, using
pairs of doubles for the real vs. imaginary component of each entry. The
size will be 4**num_qubits
complex numbers = 2*4**num_qubits
doubles =
16*4**num_qubits
bytes. A newly allocated matrix is returned; free it
with free()
when you're done with it to avoid memory leaks. On failure,
this function returns NULL
.
The primary use of this is to put all the complexity of converting between the C and internal DQCsim representation of such a matrix in a single place. This is particularly important for some of the gate map detector and constructor callbacks. However, DQCsim does provide some matrix operations that are common when dealing with gate detection and construction, but not so much anywhere else.
Matrix equality
A very common operation in DQCsim is matrix equality. An operator plugin may for instance want to detect whether a matrix is an X matrix. Getting this right is unfortunately difficult, due to floating point roundoff errors, numerical instability here and there, or (specifically to quantum gates) differences in global phase. For this reason, DQCsim provides an equality check function.
dqcs_mat_approx_eq()
Approximately compares two matrices.
Approximately compares two matrices.
dqcs_bool_return_t dqcs_mat_approx_eq(
dqcs_handle_t a,
dqcs_handle_t b,
double epsilon,
bool ignore_gphase
)
a
and b
are borrowed matrix handles.
epsilon
specifies the maximum element-wise root-mean-square error
between the matrices that results in a positive match. ignore_gphase
specifies whether the check should ignore global phase.
If ignore_gphase is set, this checks that the following holds for some x:
\[ A \cdot e^{ix} \approx B \]
This function returns DQCS_TRUE
if the matrices match according to the
aforementioned criteria, or DQCS_FALSE
if not. DQCS_BOOL_ERROR
is used
when either handle is invalid or not a matrix. If the matrices differ in
dimensionality, DQCS_FALSE
is used.
Unitary check
DQCsim also exposes a unitary check, which it uses internally here and there anyway.
dqcs_mat_approx_unitary()
Returns whether the matrix is approximately unitary.
Returns whether the matrix is approximately unitary.
dqcs_bool_return_t dqcs_mat_approx_unitary(
dqcs_handle_t matrix,
double epsilon
)
matrix
is a borrowed handle to the matrix to check.
epsilon
specifies the maximum element-wise root-mean-square error
between the product of the matrix and its hermetian compared to the
identity matrix.
This function returns DQCS_TRUE
if the matrix is approximately unitary,
or DQCS_FALSE
if not. DQCS_BOOL_ERROR
is used when either handle is
invalid or not a matrix.
Predefined matrices
DQCsim provides a number of predefined gate matrices. These are identified by
the dqcs_predefined_gate_t
enumeration.
dqcs_predefined_gate_t
Enumeration of gates defined by DQCsim.
Enumeration of gates defined by DQCsim.
typedef enum { ... } dqcs_predefined_gate_t;
Variants:
\[
I = \sigma_0 = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\] \[
X = \sigma_1 = \begin{bmatrix}
0 & 1 \\
1 & 0
\end{bmatrix}
\] \[
Y = \sigma_2 = \begin{bmatrix}
0 & -i \\
i & 0
\end{bmatrix}
\] \[
Z = \sigma_3 = \begin{bmatrix}
1 & 0 \\
0 & -1
\end{bmatrix}
\] \[
H = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
1 & -1
\end{bmatrix}
\] \[
S = \begin{bmatrix}
1 & 0 \\
0 & i
\end{bmatrix}
\] \[
S^\dagger = \begin{bmatrix}
1 & 0 \\
0 & -i
\end{bmatrix}
\] \[
T = \begin{bmatrix}
1 & 0 \\
0 & e^{i\frac{\pi}{4}}
\end{bmatrix}
\] \[
T^\dagger = \begin{bmatrix}
1 & 0 \\
0 & e^{-i\frac{\pi}{4}}
\end{bmatrix}
\] \[
R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -i \\
-i & 1
\end{bmatrix}
\] \[
R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
R_x(\pi) = \begin{bmatrix}
0 & -i \\
-i & 0
\end{bmatrix}
\] This matrix is equivalent to the Pauli X gate, but differs in global
phase. Note that this difference is significant when it is used as a
submatrix for a controlled gate. \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & 1 \\
-1 & 1
\end{bmatrix}
\] \[
R_y(\pi) = \begin{bmatrix}
0 & -1 \\
1 & 0
\end{bmatrix}
\] This matrix is equivalent to the Pauli Y gate, but differs in global
phase. Note that this difference is significant when it is used as a
submatrix for a controlled gate. \[
R_z\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1-i & 0 \\
0 & 1+i
\end{bmatrix}
\] This matrix is equivalent to the S gate, but differs in global phase.
Note that this difference is significant when it is used as a submatrix
for a controlled gate. \[
R_z\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix}
1+i & 0 \\
0 & 1-i
\end{bmatrix}
\] This matrix is equivalent to the S-dagger gate, but differs in global
phase. Note that this difference is significant when it is used as a
submatrix for a controlled gate. \[
R_z(\pi) = \begin{bmatrix}
-i & 0 \\
0 & i
\end{bmatrix}
\] This matrix is equivalent to the Pauli Z gate, but differs in global
phase. Note that this difference is significant when it is used as a
submatrix for a controlled gate. \[
R_x(\theta) = \begin{bmatrix}
\cos{\frac{\theta}{2}} & -i\sin{\frac{\theta}{2}} \\
-i\sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}}
\end{bmatrix}
\] θ is specified or returned through the first binary string argument
of the parameterization ArbData object. It is represented as a
little-endian double floating point value, specified in radians. \[
R_y(\theta) = \begin{bmatrix}
\cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} \\
\sin{\frac{\theta}{2}} & \cos{\frac{\theta}{2}}
\end{bmatrix}
\] θ is specified or returned through the first binary string argument
of the parameterization ArbData object. It is represented as a
little-endian double floating point value, specified in radians. \[
R_z(\theta) = \begin{bmatrix}
e^{-i\frac{\theta}{2}} & 0 \\
0 & e^{i\frac{\theta}{2}}
\end{bmatrix}
\] θ is specified or returned through the first binary string argument
of the parameterization ArbData object. It is represented as a
little-endian double floating point value, specified in radians. \[
\textit{PhaseK}(k) = \textit{Phase}\left(\frac{\pi}{2^k}\right) = \begin{bmatrix}
1 & 0 \\
0 & e^{i\pi / 2^k}
\end{bmatrix}
\] k is specified or returned through the first binary string argument
of the parameterization ArbData object. It is represented as a
little-endian unsigned 64-bit integer. \[
\textit{Phase}(\theta) = \begin{bmatrix}
1 & 0 \\
0 & e^{i\theta}
\end{bmatrix}
\] θ is specified or returned through the first binary string argument
of the parameterization ArbData object. It is represented as a
little-endian double floating point value, specified in radians. This matrix is equivalent to the Rz gate, but differs in global phase.
Note that this difference is significant when it is used as a submatrix
for a controlled gate. Specifically, controlled phase gates use the
phase as specified by this gate, whereas Rz follows the usual algebraic
notation. The full matrix is specified or returned through the first binary string
argument of the parameterization ArbData object. It is represented as an
array of little-endian double floating point values, structured as
real/imag pairs, with the pairs in row-major order. \[
R(\theta, \phi, \lambda) = \begin{bmatrix}
\cos{\frac{\theta}{2}} & -\sin{\frac{\theta}{2}} e^{i\lambda} \\
\sin{\frac{\theta}{2}} e^{i\phi} & \cos{\frac{\theta}{2}} e^{i\phi + i\lambda}
\end{bmatrix}
\] This is equivalent to the following: \[
R(\theta, \phi, \lambda) = \textit{Phase}(\phi) \cdot R_y(\theta) \cdot \textit{Phase}(\lambda)
\] The rotation order and phase is taken from Qiskit's U3 gate. Ignoring
global phase, any unitary single-qubit gate can be represented with this
notation. θ, φ, and λ are specified or returned through the first three binary
string arguments of the parameterization ArbData object. They are
represented as little-endian double floating point values, specified in
radians. \[
\textit{SWAP} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\] \[
\sqrt{\textit{SWAP}} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & \frac{i+1}{2} & \frac{i-1}{2} & 0 \\
0 & \frac{i-1}{2} & \frac{i+1}{2} & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
\] The full matrix is specified or returned through the first binary string
argument of the parameterization ArbData object. It is represented as an
array of little-endian double floating point values, structured as
real/imag pairs, with the pairs in row-major order. The full matrix is specified or returned through the first binary string
argument of the parameterization ArbData object. It is represented as an
array of little-endian double floating point values, structured as
real/imag pairs, with the pairs in row-major order.DQCS_GATE_INVALID = 0
DQCS_GATE_PAULI_I = 100
DQCS_GATE_PAULI_X = 101
DQCS_GATE_PAULI_Y = 102
DQCS_GATE_PAULI_Z = 103
DQCS_GATE_H = 104
DQCS_GATE_S = 105
DQCS_GATE_S_DAG = 106
DQCS_GATE_T = 107
DQCS_GATE_T_DAG = 108
DQCS_GATE_RX_90 = 109
DQCS_GATE_RX_M90 = 110
DQCS_GATE_RX_180 = 111
DQCS_GATE_RY_90 = 112
DQCS_GATE_RY_M90 = 113
DQCS_GATE_RY_180 = 114
DQCS_GATE_RZ_90 = 115
DQCS_GATE_RZ_M90 = 116
DQCS_GATE_RZ_180 = 117
DQCS_GATE_RX = 150
DQCS_GATE_RY = 151
DQCS_GATE_RZ = 152
DQCS_GATE_PHASE_K = 153
DQCS_GATE_PHASE = 154
DQCS_GATE_U1 = 190
DQCS_GATE_R = 191
DQCS_GATE_SWAP = 200
DQCS_GATE_SQRT_SWAP = 201
DQCS_GATE_U2 = 290
DQCS_GATE_U3 = 390
Given such a variant and an ArbData
object with the parameters described in
the enum variant documentation, a matrix can be constructed.
dqcs_mat_predef()
Constructs a new gate matrix for one of DQCsim's predefined gates.
Constructs a new gate matrix for one of DQCsim's predefined gates.
dqcs_handle_t dqcs_mat_predef(
dqcs_predefined_gate_t gate_type,
dqcs_handle_t param_data
)
gate_type
specifies which kind of gate should be constructed.
param_data
takes an optional ArbData
object used to parameterize the
matrix if necessary. If not specified, an empty object is used. The
ArbData
representation for each gate can be found in the docs for
dqcs_predefined_gate_t
. If nothing is specified, no ArbData
is used.
This function returns the handle to the matrix, or 0 to indicate failure. The parameterization data (if specified) is consumed/deleted by this function if and only if it succeeds.
DQCsim also provides the reverse operation: going from a matrix matching a
given gate type to its parameterization. This matrix detection uses the
internal equivalent of dqcs_mat_approx_eq
, so its parameters are also needed
here.
dqcs_mat_is_predef()
Returns whether this matrix is of the given predefined form and, if it is,
any parameters needed to describe it.
Returns whether this matrix is of the given predefined form and, if it is, any parameters needed to describe it.
dqcs_bool_return_t dqcs_mat_is_predef(
dqcs_handle_t mat,
dqcs_predefined_gate_t gate_type,
dqcs_handle_t *param_data,
double epsilon,
bool ignore_gphase
)
mat
is a borrowed handle to the matrix to check.
gate_type
specifies which kind of gate should be detected.
param_data
, if non-null, receives a new ArbData
handle with
parameterization data, or an empty ArbData
if the gate is not
parameterized; the caller must delete this object when it is done with
it. This function always writes the 0 handle to this return parameter if
it fails. The ArbData
representation can be found in the documentation
for dqcs_predefined_gate_t
.
epsilon
specifies the maximum element-wise root-mean-square error
between the matrices that results in a positive match. ignore_gphase
specifies whether the check should ignore global phase.
This function returns DQCS_TRUE
if the matrices match according to the
aforementioned criteria, or DQCS_FALSE
if not. DQCS_BOOL_ERROR
is used
when either handle is invalid or not a matrix. If the matrices differ in
dimensionality, DQCS_FALSE
is used.
Note that these two functions are only the most basic form for constructing and detecting gates using some higher abstraction level. If you feel like you're using these functions a lot, you should probably use a gate map instead.
Control normalization
DQCsim allows controlled quantum gates to be specified either with an explicit set of control qubits and the non-controlled submatrix, or the full controlled matrix. The canonical form within DQCsim is the former, as operating on only the submatrices may improve performance, and gives you controlled gates for free. In some cases however, the user may wish to convert between the two representations. DQCsim provides higher-level functions to do this as part of the gate API, but you can also call the low-level matrix conversion functions manually as follows.
dqcs_mat_add_controls()
Constructs a controlled matrix from the given matrix.
Constructs a controlled matrix from the given matrix.
dqcs_handle_t dqcs_mat_add_controls(
dqcs_handle_t mat,
size_t number_of_controls
)
mat
specifies the matrix to use as the non-controlled submatrix. This
is a borrowed handle. number_of_controls
specifies the number of control
qubits to add. This function returns a new matrix handle with the
constructed matrix, or 0 if it fails.
dqcs_mat_strip_control()
Splits a controlled matrix into its non-controlled submatrix and the
indices of the control qubits.
Splits a controlled matrix into its non-controlled submatrix and the indices of the control qubits.
dqcs_handle_t dqcs_mat_strip_control(
dqcs_handle_t mat,
double epsilon,
bool ignore_global_phase,
ssize_t **control_indices
)
mat
specifies the matrix to modify. This is a borrowed handle.
epsilon
specifies the maximum magitude of the difference between the
column vectors of the input matrix and the identity matrix (after
dephasing if ignore_gphase
is set) for the column vector to be
considered to not affect the respective entry in the quantum state
vector. Note that if this is greater than zero, the resulting gate may
not be exactly equivalent. If ignore_global_phase
is set, any global
phase in the matrix is ignored, but note that if control qubits are
stripped the "global" phase of the resulting submatrix is always
significant.
control_indices
is a return argument through which DQCsim will pass
the indices of the qubits that were removed in the process of constructing
the submatrix. This is represented as an array of indices terminated by
a -1 entry. The returned matrix must be freed using free()
when you
are done with it to avoid memory leaks. This function returns a new
matrix handle with the submatrix, or 0 if it fails. In this case,
control_indices
is not mutated.
This function assumes that the incoming matrix is unitary (within
epsilon
) without verifying that this is the case. The results may
thus be invalid if it was not.
Basis matrices
TODO: someone who knows what they're talking about should check/correct this section at some point. I'm mostly working off of hunches. - Jeroen
DQCsim uses 2x2 matrices to represent the basis for a measurement or prep gate to operate on. The intuitive nature of these matrices is as follows for measurements:
- apply the Hermetian/conjugate transpose/inverse of the basis matrix as a gate to each measured qubit;
- do a Z-basis measurement;
- apply the basis matrix as a gate to each measured qubit.
Basically, the application of the inverse of the matrix rotates the state of the qubits from the desired basis to the Z basis, then a Z measurement is applied, then the application of the basis matrix rotates the state back to the desired basis.
The semantics for prep gates are basically the same:
- initialize the state of each qubit to |0>;
- apply the basis matrix as a gate to each targeted qubit.
With this definition, the basis matrices can be written as follows.
dqcs_basis_t
Enumeration of Pauli bases.
Enumeration of Pauli bases.
typedef enum { ... } dqcs_basis_t;
Variants:
\[
\psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & -1 \\
1 & 1
\end{bmatrix}
\] \[
\psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix}
1 & i \\
i & 1
\end{bmatrix}
\] \[
\psi_Z = \begin{bmatrix}
1 & 0 \\
0 & 1
\end{bmatrix}
\]DQCS_BASIS_INVALID = 0
DQCS_BASIS_X = 1
DQCS_BASIS_Y = 2
DQCS_BASIS_Z = 3
... and constructed as follows:
dqcs_mat_basis()
Constructs a matrix with the eigenvectors of one of the Pauli matrices
as column vectors.
Constructs a matrix with the eigenvectors of one of the Pauli matrices as column vectors.
dqcs_handle_t dqcs_mat_basis(dqcs_basis_t basis)
This can be used for constructing measurement or prep gates with the given basis. Returns a new handle to the constructed matrix or returns 0 if an error occurs.
Basis matrices can be compared with the following function. This function ignores any of the phase differences that don't affect the basis in its approximate equality function.
dqcs_mat_basis_approx_eq()
Approximately compares two basis matrices.
Approximately compares two basis matrices.
dqcs_bool_return_t dqcs_mat_basis_approx_eq(
dqcs_handle_t a,
dqcs_handle_t b,
double epsilon
)
a
and b
are borrowed matrix handles.
epsilon
specifies the maximum element-wise root-mean-square error
between the matrices that results in a positive match.
This checks that the following holds for some x and y:
\[ A \cdot \begin{bmatrix} e^{ix} & 0 \\ 0 & e^{iy} \end{bmatrix} \approx B \]
This function returns DQCS_TRUE
if the matrices match according to the
aforementioned criteria, or DQCS_FALSE
if not. DQCS_BOOL_ERROR
is used
when either handle is invalid or not a matrix. If either matrix is not
2x2, DQCS_FALSE
is used.