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}