Refactor Gate with new Matrix type and add GateMap
Closed this issue · 1 comments
To help plugin developers handle Gates in DQCsim it makes some sense to add some new types to the core which deals with matrices used in gates.
The dqcsim::common::types::Gate
type is used in the gatestream protocol to communicate gates. Their behaviour is defined by their unitary matrix, which currentlly is encoded as
Vec<Complex64>
This proposal introduces a new Matrix
type which wraps this Vec<Complex64>
and adds some utility functions to make plugin development easier.
A proposal for this type and it's API:
#[derive(Hash, PartialEq)]
struct Matrix {
data: Vec<Complex64>,
// pre-computed dimension which corresponds to sqrt(data.len())
dimension: usize,
}
impl Eq for Matrix {}
impl Matrix {
pub fn new(elements: Vec<Complexf64>) -> Self;
pub fn approx_equals(&self, other: &Matrix, epsilon: f64) -> bool;
// returns new matrix + set of control indices (remaining target order is maintained)
pub fn strip_control(self) -> (Self, HashSet<usize>); // or Vec<bool> ?
pub fn get(row: usize, column: usize) -> Option<Complex64>;
pub fn dimension(&self) -> usize;
}
impl Index<(usize, usize)> for Matrix {}
impl Index<usize> for Matrix {}
// Do we want this?
impl IndexMut<usize> for Matrix {}
impl From<Matrix> for Vec<Complexf64> {}
impl TryFrom<Vec<Complexf64>> for Matrix {}
The gate type internally uses the new Matrix
type. Beware this requires modification of the serde behaviour of Gate
.
The Gates
enum is moved behind the plugin
feature gate (#325).
We also need a MatrixMap
type to allow plugin developers to map incoming matrices to (our or their) gate definitions. A proposal for it's API:
struct MatrixMap<T, K> {
detectors: Vec<(K, Box<dyn Fn(Matrix) -> Option<T>>)>,
map: RefCell<HashMap<(Matrix, ArbData), (K, T)>>,
}
struct MatrixMapBuilder<T, K> {
map: MatrixMap<T, K>,
}
impl<T, K> MatrixMapBuilder<T, K> {
pub fn new() -> Self;
pub fn with_defaults(self) -> Self;
pub fn with_detector<F: Fn(Matrix) -> Option<T>>(self, key: K, callback: F) -> Self;
pub fn finish(self) -> GateMap<T, K>;
}
impl<T,K> MatrixMap<T, K> {
pub fn builder() -> MatrixMapBuilder<T, K>;
pub fn len(&self) -> usize;
pub fn detectors(&self) -> &[K];
pub fn detect(&self, key: &Matrix) -> Option<(K, T)>;
pub fn detect_with(&self, key: &Matrix, keys: &[K]) -> Option<(K, T)>; // this does not update the map
fn insert(&mut self, key: Matrix, value: T) -> Option<T>;
}
impl<T, K> Default for MatrixMap<T, K> {
fn default() -> Self {
MatrixMap::builder().with_defaults().finish()
}
}
C bindings for the above;
- add API object types for gate map builder and gate map objects.
- add a bunch of new externals:
/// Enumeration of gates defined by DQCsim.
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[allow(non_camel_case_types)]
pub enum dqcs_internal_gate_t {
/// Invalid gate.
DQCS_GATE_INVALID = 0,
/// Identity gate.
DQCS_GATE_PAULI_I = 100,
/// Identity gate.
DQCS_GATE_PAULI_X = 101,
/// Identity gate.
DQCS_GATE_PAULI_Y = 102,
/// Identity gate.
DQCS_GATE_PAULI_Z = 103,
/// Hadamard gate.
DQCS_GATE_H = 104,
/// S gate.
DQCS_GATE_S = 105,
/// S-dagger gate.
DQCS_GATE_S_DAG = 106,
/// T gate.
DQCS_GATE_T = 107,
/// T-dagger gate.
DQCS_GATE_T_DAG = 108,
/// RX(pi) gate.
DQCS_GATE_RX_90 = 109,
/// RX(-pi) gate.
DQCS_GATE_RX_M90 = 110,
/// RX(2pi) gate.
DQCS_GATE_RX_180 = 111,
/// RX(pi) gate.
DQCS_GATE_RY_90 = 112,
/// RX(-pi) gate.
DQCS_GATE_RY_M90 = 113,
/// RX(2pi) gate.
DQCS_GATE_RY_180 = 114,
/// RX(pi) gate.
DQCS_GATE_RZ_90 = 115,
/// RX(-pi) gate.
DQCS_GATE_RZ_M90 = 116,
/// RX(2pi) gate.
DQCS_GATE_RZ_180 = 117,
/// Parameterized RX gate with radian angle.
DQCS_GATE_RX = 150,
/// Parameterized RY gate with radian angle.
DQCS_GATE_RY = 151,
/// Parameterized RZ gate with pi/2^k angle, where k is an integer.
DQCS_GATE_RK = 152,
/// Parameterized RZ gate with radian angle.
DQCS_GATE_RZ = 153,
/// Any single-qubit unitary gate, parameterized as a full unitary matrix.
DQCS_GATE_U = 190,
/// Any single-qubit unitary gate, parameterized IBM-style with Z-Y-Z
/// angles.
DQCS_GATE_R = 191,
/// Swap gate.
DQCS_GATE_SWAP = 200,
/// Square-root of swap gate.
DQCS_GATE_SQRT_SWAP = 201,
}
/// Constructs a new matrix map builder.
///
/// Returns a handle to a matrix map builder with no detectors attached to it
/// yet. Use `dqcs_mmb_add_*()` to do that. The detectors are queried in the
/// order in which they are added, so be sure to add more specific gates first.
/// Then construct the matrix map object itself using `dqcs_mm_new()`.
#[no_mangle]
pub extern "C" fn dqcs_mmb_new() -> dqcs_handle_t {
}
/// Adds a set of default gate matrix detectors to the given matrix map
/// builder.
///
/// `mmb` must be a handle to a matrix map builder object (`dqcs_mmb_new()`).
/// `version` is reserved and should be set to zero; if the default set of
/// gates is changed in a future version of DQCsim, the different defaults will
/// be disambiguated with this numbed. `epsilon` specifies the maximum
/// element-wise root-mean-square error between the incoming matrix and the to
/// be detected matrix that results in a positive match. `ignore_phase`
/// specifies whether the aforementioned check should ignore global phase or
/// not.
///
/// The current (version 0) defaults are equivalent to calling:
///
/// ```C
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_PAULI_I, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_PAULI_X, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_PAULI_Y, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_PAULI_Z, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_H, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_S, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_S_DAG, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_T, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_T_DAG, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RX_90, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RX_M90, epsilon, ignore_gphase, 0);
/// if (!ignore_gphase) {
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RX_180, epsilon, ignore_gphase, 0);
/// }
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RY_90, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RY_M90, epsilon, ignore_gphase, 0);
/// if (!ignore_gphase) {
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RY_180, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RZ_90, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RZ_M90, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RZ_180, epsilon, ignore_gphase, 0);
/// }
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RX, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RY, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_RZ, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_R, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_SWAP, epsilon, ignore_gphase, 0);
/// dqcs_mmb_add_internal(mmb, NULL, NULL, DQCS_GATE_SQRT_SWAP, epsilon, ignore_gphase, 0);
/// ```
#[no_mangle]
pub extern "C" fn dqcs_mmb_add_defaults(
mmb: dqcs_handle_t,
version: usize,
epsilon: c_double,
ignore_gphase: c_bool,
) -> dqcs_return_t {
...
}
/// Adds a gate matrix detector for the given DQCsim-defined gate to the given
/// matrix map builder.
///
/// `mmb` must be a handle to a matrix map builder object (`dqcs_mmb_new()`).
/// `key_data` is a user-specified value that is returned as part of the match
/// function result when this detector is the first to match. `key_free` is an
/// optional callback function used to free `key_data` when the matrix map
/// (builder) is destroyed, or when this function fails. `gate` defines
/// which gate to detect. Some of the detectable gates are parameterized. Note
/// that there are no controlled gates, because they are normally encoded
/// separately in the DQCsim protocol and therefore not part of the matrix; to
/// detect gate matrices with explicit control qubits, preprocess the gates
/// with `dqcs_gate_reduce_control()` first. `epsilon` specifies the maximum
/// element-wise root-mean-square error between the incoming matrix and the to
/// be detected matrix that results in a positive match. `ignore_phase`
/// specifies whether the aforementioned check should ignore global phase or
/// not. `param_proto` optionally specifies an `ArbData` handle (consumed by
/// this function) used as a prototype for the gate parameter data returned by
/// the match function. The gate detectors will push the following parameters
/// as binary strings on top of any binary strings in the prototype object:
///
/// - all gate detectors push an unsigned 32-bit integer with the value of the
/// `dqcs_internal_gate_t` enum.
/// - `DQCS_GATE_RX`, `DQCS_GATE_RY`, and `DQCS_GATE_RZ` push a 64-bit double
/// floating point with the angle in addition to the enum.
/// - `DQCS_GATE_RK` pushes a 32-bit integer with the k parameter in addition
/// to the enum.
/// - `DQCS_GATE_R` pushes a the three double floating point angles in
/// addition to the enum (ordered theta, phi, lambda).
/// - `DQCS_GATE_U` pushes the entire matrix as a single argument consisting
/// of 2**N * 2**N * 2 doubles, in real-first row-major format (same as the
/// other matrix definitions in DQCsim).
#[no_mangle]
pub extern "C" fn dqcs_mmb_add_internal(
mmb: dqcs_handle_t,
key_free: Option<extern "C" fn(user_data: *mut c_void)>,
key_data: *mut key_data
gate: dqcs_internal_gate_t,
epsilon: c_double,
ignore_gphase: c_bool,
param_proto: dqcs_handle_t
) -> dqcs_return_t {
...
}
/// Adds a gate matrix detector for the given gate matrix to the given
/// matrix map builder.
///
/// `mmb` must be a handle to a matrix map builder object (`dqcs_mmb_new()`).
/// `key_data` is a user-specified value that is returned as part of the match
/// function result when this detector is the first to match. `key_free` is an
/// optional callback function used to free `key_data` when the matrix map
/// (builder) is destroyed, or when this function fails. `matrix` must
/// point to an appropriately sized array of doubles, representing the unitary
/// matrix that is to be detected. The matrix is specified in row-major form,
/// using pairs of doubles for the real vs. imaginary component of each entry.
/// `matrix_len` must be set to the number of complex numbers in the matrix
/// (4 for a one-qubit gate, 16 for a two-qubit gate, 256 for a three-qubit
/// gate and so on). `epsilon` specifies the maximum element-wise
/// root-mean-square error between the incoming matrix and the to be detected
/// matrix that results in a positive match. `ignore_phase` specifies whether
/// the aforementioned check should ignore global phase or not. `param_data`
/// optionally specifies an `ArbData` handle (consumed by this function) of
/// which a copy will be returned by the detector as the gate parameter data
/// object when a positive match occurs.
#[no_mangle]
pub extern "C" fn dqcs_mmb_add_fixed(
mmb: dqcs_handle_t,
key_free: Option<extern "C" fn(key_data: *mut c_void)>,
key_data: *mut c_void
matrix: *const c_double,
matrix_len: size_t,
epsilon: c_double,
ignore_gphase: c_bool,
param_data: dqcs_handle_t
) -> dqcs_return_t {
...
}
/// Adds a custom gate matrix detector to the given matrix map builder.
///
/// `mmb` must be a handle to a matrix map builder object (`dqcs_mmb_new()`).
/// `key_data` is a user-specified value that is returned as part of the match
/// function result when this detector is the first to match. `key_free` is an
/// optional callback function used to free `key_data` when the matrix map
/// (builder) is destroyed, or when this function fails.
///
/// The callback receives a matrix for the user to match. If the gate matches,
/// the function must return `DQCS_TRUE`. If it doesn't match, it must return
/// `DQCS_FALSE`. If an error occurs, it must call `dqcs_error_set()` with the
/// error message and return `DQCS_BOOL_FAILURE`.
///
/// When the gate matches, the function can return a gate parameter data object
/// by way of assigning `param_data` to a new `ArbData` handle. Ownership of
/// this handle is passed to the callee regardless of the return value (that is,
/// even if no match is reported, the callee will ensure that the `ArbData`
/// handle passed here, if any, is freed). `param_data` will always point to 0
/// when the function is called.
///
/// It is up to the user how to do this matching, but the function is assumed
/// to always return the same value for the same input. Otherwise, the caching
/// behavior of the `GateMap` will make the results inconsistent.
#[no_mangle]
pub extern "C" fn dqcs_mmb_add_user(
mmb: dqcs_handle_t,
key_free: Option<extern "C" fn(key_data: *mut c_void)>,
key_data: *mut c_void
callback: extern "C" fn(
user_data: *const c_void,
matrix: *const c_double,
matrix_len: size_t,
param_data: *mut dqcs_handle_t
) -> dqcs_bool_return_t,
user_free: Option<extern "C" fn(user_data: *mut c_void)>,
user_data: *mut c_void
) -> dqcs_return_t {
...
}
/// Finalizes a matrix map builder object into a matrix map object.
///
/// `mmb` must be a handle to a matrix map builder object (`dqcs_mmb_new()`).
/// This handle is consumed if the function succeeds.
///
/// This function returns a new handle to a matrix map object, which can then
/// be used to map matrices to user-specified keys.
#[no_mangle]
pub extern "C" fn dqcs_mm_new(
mmb: dqcs_handle_t,
) -> dqcs_handle_t {
...
}
/// Uses a matrix map object to map the given matrix to some key representing
/// the gate type.
///
/// `mm` must be a handle to a matrix map object (`dqcs_mm_new()`). `matrix`
/// must point to an appropriately sized array of doubles, representing the
/// unitary matrix that is to be matched. The matrix is specified in row-major
/// form, using pairs of doubles for the real vs. imaginary component of each
/// entry. `matrix_len` must be set to the number of complex numbers in the
/// matrix (4 for a one-qubit gate, 16 for a two-qubit gate, 256 for a
/// three-qubit gate and so on). `key_data` serves as an optional return
/// value; if non-NULL and a match is found, the `key_data` specified
/// when the respective matcher was added is returned here as a `const void *`.
/// If no match is found, `key_data` is not written to. `param_data` also
/// serves as an optional return value; if non-NULL and a match is found,
/// it is set to a handle to a new `ArbData` object representing the gate's
/// parameters. Ownership of this object is passed to the user, so it is up
/// to the user to eventually delete this object. If no match is found,
/// `param_data` is set to 0. `cache_hit` also serves as an optional return
/// value; if non-NULL and a match is found, `true` is written if the cache
/// was hit, and `false` if it was not. If no match is found, it is set to
/// `false` as well. This function returns `DQCS_TRUE` if a match was found,
/// `DQCS_FALSE` if no match was found, or `DQCS_BOOL_FAILURE` if an error
/// occurs.
#[no_mangle]
pub extern "C" fn dqcs_mm_map_matrix(
mm: dqcs_handle_t,
matrix: *const c_double,
matrix_len: size_t,
key_data: *mut *const c_void,
param_data: *mut dqcs_handle_t,
cache_hit: *mut c_bool,
) -> dqcs_bool_return_t {
...
}
/// Uses a matrix map object to map the matrix of the given gate to some key
/// representing the gate type.
///
/// `mm` must be a handle to a matrix map object (`dqcs_mm_new()`). `gate` must
/// be a handle to a gate that has a matrix (that is, either a unitary gate, or
/// a custom gate with a matrix). `key_data` serves as an optional return
/// value; if non-NULL and a match is found, the `key_data` specified
/// when the respective matcher was added is returned here as a `const void *`.
/// If no match is found, `key_data` is not written to. `param_data` also
/// serves as an optional return value; if non-NULL and a match is found,
/// it is set to a handle to a new `ArbData` object representing the gate's
/// parameters. Ownership of this object is passed to the user, so it is up
/// to the user to eventually delete this object. If no match is found,
/// `param_data` is set to 0. `cache_hit` also serves as an optional return
/// value; if non-NULL and a match is found, `true` is written if the cache
/// was hit, and `false` if it was not. If no match is found, it is set to
/// `false` as well. This function returns `DQCS_TRUE` if a match was found,
/// `DQCS_FALSE` if no match was found, or `DQCS_BOOL_FAILURE` if an error
/// occurs.
#[no_mangle]
pub extern "C" fn dqcs_mm_map_gate(
mm: dqcs_handle_t,
gate: dqcs_handle_t,
key_data: *mut *const c_void,
param_data: *mut dqcs_handle_t,
cache_hit: *mut c_bool,
) -> dqcs_bool_return_t {
...
}
/// Clears the cache of a matrix map object.
///
/// Matrix map objects internally cache detected matrices to increase detection
/// speed when a matrix is received a second time. This function clears the
/// cache.
#[no_mangle]
pub extern "C" fn dqcs_mm_clear_cache(
mm: dqcs_handle_t,
) -> dqcs_return_t {
...
}
/// Utility function that detects control qubits in the `targets` list of the
/// gate by means of the gate matrix, and reduces them into `controls` qubits.
///
/// This function borrows a handle to any gate with a matrix, and returns an
/// equivalent copy of said gate with any control qubits in the `targets` set
/// moved to the `controls` set. The associated gate matrix is accordingly
/// reduced in size. The control qubits are added at the end of the `controls`
/// set in the same order they appeared in the `targets` qubit set.
///
/// `epsilon` specifies the maximum element-wise deviation from the identity
/// matrix for the relevant array elements for a qubit to be considered a
/// control qubit. Note that if this is greater than zero, the resulting gate
/// may not be exactly equivalent. If `ignore_gphase` is set, any global phase
/// in the matrix is ignored, but the global phase of the non-control submatrix
/// is not changed.
///
/// This function returns a new gate handle with the modified gate, or a copy
/// of the input gate if the matrix could not be reduced. If the input gate
/// does not have a matrix (measurement gate, or custom gate without matrix) an
/// error is returned instead.
#[no_mangle]
pub extern "C" dqcs_gate_reduce_control(
gate: dqcs_handle_t,
epsilon: c_double,
ignore_gphase: c_bool,
) -> dqcs_handle_t {
...
}