qdk_sim/
utils.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use ndarray::{s, Array1, Array2, ArrayView1};
5use num_complex::Complex;
6
7/// A complex number with two 64-bit floating point fields.
8/// That is, the analogy of [`f64`] to complex values.
9pub type C64 = Complex<f64>;
10
11/// The real unit 1, represented as a complex number with two 64-bit floating
12/// point fields.
13pub const ONE_C: C64 = Complex::new(1f64, 0f64);
14
15/// The number zero, represented as a complex number with two 64-bit floating
16/// point fields.
17pub const ZERO_C: C64 = Complex::new(0f64, 0f64);
18
19/// The imaginary unit $i$, represented as a complex number with two 64-bit
20/// floating point fields.
21pub const I_C: C64 = Complex::new(0f64, 1f64);
22
23#[cfg(feature = "web-sys-log")]
24fn log_message(msg: &str) {
25    web_sys::console::log_1(&msg.into());
26}
27
28#[cfg(not(feature = "web-sys-log"))]
29fn log_message(msg: &str) {
30    println!("{}", msg);
31}
32
33/// Prints a message as an error, and returns it as a [`Result`].
34pub fn log_as_err<T>(msg: String) -> Result<T, String> {
35    log(&msg);
36    Err(msg)
37}
38
39/// Prints a message as an error.
40pub fn log(msg: &str) {
41    log_message(msg);
42}
43
44/// Given two columns in a two-dimensional array, swaps them in-place.
45pub fn swap_columns<T: Clone>(data: &mut Array2<T>, idxs: (usize, usize)) {
46    // FIXME[perf]: can be accelerated for bool by using three XORs.
47    // FIXME[perf]: should be possible with one tmp instead of two
48    let tmp_a = data.slice(s![.., idxs.0]).to_owned();
49    let tmp_b = data.slice(s![.., idxs.1]).to_owned();
50    data.slice_mut(s![.., idxs.0]).assign(&tmp_b);
51    data.slice_mut(s![.., idxs.1]).assign(&tmp_a);
52}
53
54/// Given a one-dimensional array, updates it to be the row-sum of that vector
55/// and the given row of a matrix, taking into account the phase product
56/// introduced by the binary symplectic product.
57pub fn set_vec_to_row_sum(data: &mut Array1<bool>, matrix: &Array2<bool>, idx_source: usize) {
58    // FIXME[perf]: change to ndarray broadcast op.
59    // NB: we use - 1 in the range here, so that we don't also iterate over phases.
60    for idx_col in 0..matrix.shape()[1] - 1 {
61        data[idx_col] ^= matrix[(idx_source, idx_col)];
62    }
63
64    let idx_phase = data.shape()[0] - 1;
65    data[idx_phase] = phase_product(&data.slice(s![..]), &matrix.slice(s![idx_source, ..]));
66}
67
68/// Given a two-dimensional array, updates a row of that matrix to be the
69/// row-sum of that row and the given row of another matrix, taking into
70/// account the phase product introduced by the binary symplectic product.
71pub fn set_row_to_row_sum(data: &mut Array2<bool>, idx_source: usize, idx_target: usize) {
72    // FIXME[perf]: change to ndarray broadcast op.
73    // NB: we use - 1 in the range here, so that we don't also iterate over phases.
74    for idx_col in 0..data.shape()[1] - 1 {
75        data[(idx_target, idx_col)] ^= data[(idx_source, idx_col)];
76    }
77
78    let idx_phase = data.shape()[1] - 1;
79    data[(idx_target, idx_phase)] = phase_product(
80        &data.slice(s![idx_target, ..]),
81        &data.slice(s![idx_source, ..]),
82    );
83}
84
85fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
86    match (x1, z1) {
87        (false, false) => 0,
88        (true, true) => (if z2 { 1 } else { 0 }) - (if x2 { 1 } else { 0 }),
89        (true, false) => (if z2 { 1 } else { 0 }) * (if x2 { 1 } else { -1 }),
90        (false, true) => (if x2 { 1 } else { 0 }) * (if z2 { 1 } else { -1 }),
91    }
92}
93
94/// Given a row of a binary symplectic matrix augmented with phase information,
95/// returns its $X$, $Z$, and phase parts.
96pub fn split_row(row: &ArrayView1<bool>) -> (Array1<bool>, Array1<bool>, bool) {
97    let n_qubits = (row.shape()[0] - 1) / 2;
98    // FIXME[perf]: relax to_owned call here.
99    (
100        row.slice(s![0..n_qubits]).to_owned(),
101        row.slice(s![n_qubits..]).to_owned(),
102        row[2 * n_qubits],
103    )
104}
105
106/// Returns the phase introduced by the binary symplectic product of two rows.
107pub fn phase_product(row1: &ArrayView1<bool>, row2: &ArrayView1<bool>) -> bool {
108    let mut acc = 0i32;
109    let (xs1, zs1, r1) = split_row(row1);
110    let (xs2, zs2, r2) = split_row(row2);
111
112    for idx_col in 0..xs1.shape()[0] {
113        acc += g(xs1[idx_col], zs1[idx_col], xs2[idx_col], zs2[idx_col]);
114    }
115
116    ((if r1 { 2 } else { 0 }) + (if r2 { 2 } else { 0 }) + acc) % 4 == 2
117}