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.

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.

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.

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.

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.

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.

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.

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.

typedef enum { ... } dqcs_predefined_gate_t;

Variants:

DQCS_GATE_INVALID = 0
Invalid gate. Used as an error return value.
DQCS_GATE_PAULI_I = 100
The identity gate for a single qubit.

\[ I = \sigma_0 = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

DQCS_GATE_PAULI_X = 101
The Pauli X matrix.

\[ X = \sigma_1 = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Y = 102
The Pauli Y matrix.

\[ Y = \sigma_2 = \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} \]

DQCS_GATE_PAULI_Z = 103
The Pauli Z matrix.

\[ Z = \sigma_3 = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \]

DQCS_GATE_H = 104
The hadamard gate matrix. That is, a 180-degree Y rotation, followed by a 90-degree X rotation.

\[ H = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \]

DQCS_GATE_S = 105
The S matrix, also known as a 90 degree Z rotation.

\[ S = \begin{bmatrix} 1 & 0 \\ 0 & i \end{bmatrix} \]

DQCS_GATE_S_DAG = 106
The S-dagger matrix, also known as a negative 90 degree Z rotation.

\[ S^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & -i \end{bmatrix} \]

DQCS_GATE_T = 107
The T matrix, also known as a 45 degree Z rotation.

\[ T = \begin{bmatrix} 1 & 0 \\ 0 & e^{i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_T_DAG = 108
The T-dagger matrix, also known as a negative 45 degree Z rotation.

\[ T^\dagger = \begin{bmatrix} 1 & 0 \\ 0 & e^{-i\frac{\pi}{4}} \end{bmatrix} \]

DQCS_GATE_RX_90 = 109
Rx(90°) gate.

\[ R_x\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i \\ -i & 1 \end{bmatrix} \]

DQCS_GATE_RX_M90 = 110
Rx(-90°) gate.

\[ R_x\left(-\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_GATE_RX_180 = 111
Rx(180°) gate.

\[ 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.

DQCS_GATE_RY_90 = 112
Ry(90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_M90 = 113
Ry(-90°) gate.

\[ R_y\left(\frac{\pi}{2}\right) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & 1 \\ -1 & 1 \end{bmatrix} \]

DQCS_GATE_RY_180 = 114
Ry(180°) gate.

\[ 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.

DQCS_GATE_RZ_90 = 115
Rz(90°) 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.

DQCS_GATE_RZ_M90 = 116
Rz(-90°) 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.

DQCS_GATE_RZ_180 = 117
Rz(180°) 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.

DQCS_GATE_RX = 150
The matrix for an arbitrary X rotation.

\[ 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.

DQCS_GATE_RY = 151
The matrix for an arbitrary Y rotation.

\[ 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.

DQCS_GATE_RZ = 152
The matrix for an arbitrary Z rotation.

\[ 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.

DQCS_GATE_PHASE_K = 153
The matrix for a Z rotation with angle π/2^k.

\[ \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.

DQCS_GATE_PHASE = 154
The matrix for an arbitrary Z rotation.

\[ \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.

DQCS_GATE_U1 = 190
Any single-qubit unitary gate, parameterized as a full unitary matrix.

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_R = 191
Arbitrary rotation matrix.

\[ 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.

DQCS_GATE_SWAP = 200
The swap gate matrix.

\[ \textit{SWAP} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]

DQCS_GATE_SQRT_SWAP = 201
The square-root of a swap gate matrix.

\[ \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} \]

DQCS_GATE_U2 = 290
Any two-qubit unitary gate, parameterized as a full unitary matrix.

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_U3 = 390
Any three-qubit unitary gate, parameterized as a full unitary matrix.

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.

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.

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.

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.

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.

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.

typedef enum { ... } dqcs_basis_t;

Variants:

DQCS_BASIS_INVALID = 0
Invalid basis. Used as an error return value.
DQCS_BASIS_X = 1
The X basis.

\[ \psi_X = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} \]

DQCS_BASIS_Y = 2
The Y basis.

\[ \psi_Y = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & i \\ i & 1 \end{bmatrix} \]

DQCS_BASIS_Z = 3
The Z basis.

\[ \psi_Z = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

... and constructed as follows:

dqcs_mat_basis()

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.

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.