use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum BlochMode {
#[default]
Spatial,
Planar,
Projection,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BlochReadout {
pub probabilities: [f64; 2],
pub probability_radius: f64,
pub phase: Option<f64>,
pub length: f64,
pub purity: f64,
pub coherence: f64,
}
impl BlochReadout {
pub const PHASE_DISPLAY_TOLERANCE: f64 = 1e-6;
pub fn from_vector([x, y, z]: [f64; 3]) -> Self {
let p1 = ((1. - z) * 0.5).clamp(0., 1.);
let transverse = x.hypot(y);
let length = transverse.hypot(z).min(1.);
Self {
probabilities: [1. - p1, p1],
probability_radius: p1.sqrt(),
phase: (transverse > Self::PHASE_DISPLAY_TOLERANCE).then(|| y.atan2(x)),
length,
purity: (1. + length * length) * 0.5,
coherence: transverse.min(1.),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum BlochPlane {
Xy,
#[default]
Xz,
Yz,
}
impl BlochPlane {
pub fn axes(self) -> [&'static str; 3] {
match self {
Self::Xy => ["x", "y", "z"],
Self::Xz => ["x", "z", "y"],
Self::Yz => ["y", "z", "x"],
}
}
pub fn project(self, v: [f64; 3]) -> [f64; 3] {
match self {
Self::Xy => v,
Self::Xz => [v[0], v[2], v[1]],
Self::Yz => [v[1], v[2], v[0]],
}
}
pub fn next(self) -> Self {
match self {
Self::Xy => Self::Xz,
Self::Xz => Self::Yz,
Self::Yz => Self::Xy,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(actual: f64, expected: f64) {
assert!((actual - expected).abs() < 1e-12, "{actual} != {expected}");
}
#[test]
fn basis_states_have_certain_measurements_and_no_phase() {
for (vector, expected) in [([0., 0., 1.], [1., 0.]), ([0., 0., -1.], [0., 1.])] {
let readout = BlochReadout::from_vector(vector);
assert_eq!(readout.probabilities, expected);
close(readout.probability_radius.powi(2), expected[1]);
assert_eq!(readout.phase, None);
close(readout.length, 1.);
close(readout.purity, 1.);
close(readout.coherence, 0.);
}
}
#[test]
fn equatorial_eigenstates_preserve_phase_sign() {
use std::f64::consts::{FRAC_PI_2, PI};
for (vector, phase) in [
([1., 0., 0.], 0.),
([-1., 0., 0.], PI),
([0., 1., 0.], FRAC_PI_2),
([0., -1., 0.], -FRAC_PI_2),
] {
let readout = BlochReadout::from_vector(vector);
assert_eq!(readout.probabilities, [0.5, 0.5]);
close(readout.probability_radius.powi(2), 0.5);
close(readout.phase.unwrap(), phase);
close(readout.purity, 1.);
close(readout.coherence, 1.);
}
}
#[test]
fn density_matrix_readouts_distinguish_mixtures_from_superpositions() {
let mixed = BlochReadout::from_vector([0., 0., 0.5]);
let pure = BlochReadout::from_vector([0.75_f64.sqrt(), 0., 0.5]);
assert_eq!(mixed.probabilities, pure.probabilities);
close(mixed.probability_radius, 0.5);
assert_eq!(mixed.phase, None);
close(mixed.purity, 0.625);
close(pure.purity, 1.);
let maximally_mixed = BlochReadout::from_vector([0.; 3]);
assert_eq!(maximally_mixed.probabilities, [0.5, 0.5]);
assert_eq!(maximally_mixed.phase, None);
close(maximally_mixed.length, 0.);
close(maximally_mixed.purity, 0.5);
}
#[test]
fn readouts_match_amplitudes_for_unequal_superpositions() {
for p in [0.01_f64, 0.25, 0.5, 0.8, 0.99] {
for phase in [-2.4_f64, -0.6, 0., 1.1, 2.9] {
let alpha = (1. - p).sqrt();
let beta_re = p.sqrt() * phase.cos();
let beta_im = p.sqrt() * phase.sin();
let vector = [
2. * alpha * beta_re,
2. * alpha * beta_im,
alpha * alpha - beta_re * beta_re - beta_im * beta_im,
];
let readout = BlochReadout::from_vector(vector);
close(readout.probabilities[0], alpha * alpha);
close(
readout.probabilities[1],
beta_re * beta_re + beta_im * beta_im,
);
close(readout.probability_radius.powi(2), p);
close(readout.phase.unwrap(), phase);
close(readout.purity, 1.);
}
}
}
#[test]
fn display_roundoff_does_not_create_false_phase_or_overflow_the_disk() {
let at_north_pole = BlochReadout::from_vector([1e-8, -1e-8, 1.00000001]);
assert_eq!(at_north_pole.phase, None);
assert_eq!(at_north_pole.probabilities, [1., 0.]);
close(at_north_pole.length, 1.);
let at_south_pole = BlochReadout::from_vector([0., 0., -1.00000001]);
close(at_south_pole.probability_radius, 1.);
}
#[test]
fn projections_preserve_the_hidden_component_and_squared_length() {
for plane in [BlochPlane::Xy, BlochPlane::Xz, BlochPlane::Yz] {
for v in [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.], [0.2, -0.4, 0.3]] {
let p = plane.project(v);
assert!(
(v.iter().map(|x| x * x).sum::<f64>() - p.iter().map(|x| x * x).sum::<f64>())
.abs()
< 1e-12
);
let axes = plane.axes();
for (i, name) in axes.iter().enumerate() {
assert_eq!(
p[i],
v[["x", "y", "z"].iter().position(|a| a == name).unwrap()]
);
}
}
}
}
}