quantum2/
other.rs

1//! (public for pedagogical reasons).
2
3/// Convenience macros for qubit module.
4mod macros {
5    #![macro_use]
6
7    /// Square a numeric value efficiently by multiplying it with itself.
8    #[macro_export]
9    macro_rules! square {
10        ($x:expr) => {
11            $x * $x
12        };
13    }
14
15    /// Compute a complex number's absolute value, i.e. _|x + iy|^2_.
16    #[macro_export]
17    macro_rules! abs_square {
18        ($re:expr, $im:expr) => {
19            square!($re) + square!($im)
20        };
21    }
22}
23
24/// Single qubit library code.
25pub mod qubit {
26    use float_cmp::ApproxEqUlps;
27
28    /// Represents a single (pure, not entangled) qubit state of the form `a|0> + b|1>`.
29    ///
30    /// The qubit is the linear superposition of the computational basis of `|0>_ and _|1>`.
31    ///
32    /// We encode the complex coeffients as tuples of their real and imaginary parts,
33    /// each represented as a 64-bit floating points.  This gives high accuracy, while
34    /// allowing word-size arithmetic on 64-bit systems.
35    ///
36    /// The theoretical state should always satisfy the equations:
37    ///
38    ///  - `a = a_re + i * a_im`
39    ///  - `b = b_re + i * b_im`
40    ///  - `1 = |a|^2+ |b|^2`
41    ///
42    /// This representation of that state should approximately satisfy them, subject to floating
43    /// point imprecision.
44    #[derive(Clone, Copy, Debug)]
45    pub struct NonEntangledQubit {
46        a_re: f64,
47        a_im: f64,
48        b_re: f64,
49        b_im: f64,
50    }
51
52    impl NonEntangledQubit {
53        /// Safely construct a qubit, given the real and imaginary parts of both coefficients.
54        ///
55        /// This function validates that the given state is possible.
56        pub fn new(a_re: f64, a_im: f64, b_re: f64, b_im: f64) -> NonEntangledQubit {
57            let candidate = NonEntangledQubit {
58                a_re: a_re,
59                a_im: a_im,
60                b_re: b_re,
61                b_im: b_im,
62            };
63
64            assert!(candidate.validate());
65
66            candidate
67        }
68
69        /// Validate that this qubit's state is possible.
70        ///
71        /// In our imperfect floating point model, this means computing `|a|^2+ |b|^2` and
72        /// comparing it to `1` with some leeway.
73        ///
74        /// That leeway is arbitrarily chosen as 10 units of least precision.
75        #[cfg(not(feature = "optimize"))]
76        pub fn validate(&self) -> bool {
77            let sample_space_sum: f64 = abs_square!(self.a_re, self.a_im) +
78                                        abs_square!(self.b_re, self.b_im);
79
80            sample_space_sum.approx_eq_ulps(&1.0f64, 10)
81        }
82
83        /// Skip state validation for speed.
84        #[cfg(feature = "optimize")]
85        #[inline(always)]
86        pub fn validate(&self) -> bool {
87            true
88        }
89    }
90
91    #[test]
92    fn initialization_test() {
93        let sqrt2inv = 2.0f64.sqrt().recip();
94
95        let q1: NonEntangledQubit = NonEntangledQubit::new(0.5, 0.5, 0.5, 0.5);
96        let q2: NonEntangledQubit = NonEntangledQubit::new(sqrt2inv, sqrt2inv, 0.0, 0.0);
97        let q3: NonEntangledQubit = NonEntangledQubit::new(0.0, 0.0, sqrt2inv, sqrt2inv);
98
99        assert!(q1.validate());
100        assert!(q2.validate());
101        assert!(q3.validate());
102    }
103
104    #[test]
105    #[should_panic(expected = "assertion failed")]
106    #[cfg(not(feature = "optimize"))]
107    fn bad_initialization_test() {
108        NonEntangledQubit::new(0.0, 0.0, 0.0, 0.0);
109    }
110}