use super::*;
use crate::qis::{DensityMatrix, Statevector};
use crate::visualization::test_utils::assert_svg_visual_match;
use num_complex::Complex64;
fn zero_state() -> Statevector {
Statevector::from_state(1, vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]).unwrap()
}
fn plus_state() -> Statevector {
Statevector::from_state(
1,
vec![
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
],
)
.unwrap()
}
fn bell_state() -> Statevector {
Statevector::from_state(
2,
vec![
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
],
)
.unwrap()
}
fn assert_state_visual_match(svg: &str, filename: &str) {
assert_svg_visual_match(&["state", "figure"], filename, |output_path| {
render_state_plot_to_file(svg, &output_path.to_string_lossy())
});
}
fn attr_f64(tag: &str, name: &str) -> f64 {
let needle = format!("{name}=\"");
let start = tag.find(&needle).unwrap() + needle.len();
let end = tag[start..].find('"').unwrap() + start;
tag[start..end].parse().unwrap()
}
fn rect_tags_between(svg: &str, start_marker: &str, end_marker: &str) -> Vec<String> {
let start = svg.find(start_marker).unwrap();
let end = svg[start..].find(end_marker).unwrap() + start;
let segment = &svg[start..end];
let mut tags = Vec::new();
let mut offset = 0;
while let Some(relative_start) = segment[offset..].find("<rect ") {
let tag_start = offset + relative_start;
let tag_end = tag_start + segment[tag_start..].find("/>").unwrap() + 2;
tags.push(segment[tag_start..tag_end].to_string());
offset = tag_end;
}
tags
}
fn assert_in_display_label_order(svg: &str, expected: &[&str]) {
let mut last = 0;
for label in expected {
let needle = format!(">{label}</text>");
let found = svg[last..].find(&needle).unwrap() + last;
assert!(found >= last);
last = found;
}
}
#[test]
fn state_city_matches_reference_image() {
let svg = plot_state_city(&zero_state(), &StatePlotOptions::default()).unwrap();
assert_state_visual_match(&svg, "state_city_zero.png");
}
#[test]
fn state_city_reverse_bits_remaps_matrix_values_and_labels() {
let density = DensityMatrix::from_density_matrix_state(
2,
vec![
Complex64::new(0.05, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.15, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.75, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.05, 0.0),
],
)
.unwrap();
let svg = plot_state_city(
&density,
&StatePlotOptions {
reverse_bits: true,
..StatePlotOptions::default()
},
)
.unwrap();
assert_in_display_label_order(&svg, &["00", "10", "01", "11"]);
let real_rects = rect_tags_between(&svg, "Re[rho]", "Im[rho]");
let cell_widths: Vec<f64> = real_rects
.iter()
.skip(1)
.map(|tag| attr_f64(tag, "width"))
.collect();
assert_eq!(cell_widths.len(), 16);
let diag_00 = cell_widths[0];
let diag_10 = cell_widths[5];
let diag_01 = cell_widths[10];
let diag_11 = cell_widths[15];
let off_diagonal = cell_widths[1];
assert!(diag_10 > diag_01);
assert!(diag_01 > diag_00);
assert!((diag_00 - diag_11).abs() < 1e-9);
assert!(off_diagonal < diag_00);
assert_state_visual_match(&svg, "state_city_reverse_bits.png");
}
#[test]
fn bloch_vector_matches_reference_image() {
let svg = plot_bloch_vector([0.0, 0.0, 1.0], &StatePlotOptions::default()).unwrap();
assert_state_visual_match(&svg, "bloch_vector_z.png");
}
#[test]
fn bloch_multivector_matches_reference_image() {
let svg = plot_bloch_multivector(&plus_state(), &StatePlotOptions::default()).unwrap();
assert_state_visual_match(&svg, "bloch_multivector_plus.png");
}
#[test]
fn bloch_multivector_bell_state_matches_reference_image() {
let svg = plot_bloch_multivector(&bell_state(), &StatePlotOptions::default()).unwrap();
assert!(svg.contains("q0"));
assert!(svg.contains("q1"));
assert_state_visual_match(&svg, "bloch_multivector_bell.png");
}
#[test]
fn paulivec_matches_reference_image() {
let svg = plot_state_paulivec(&plus_state(), &StatePlotOptions::default()).unwrap();
assert_state_visual_match(&svg, "paulivec_plus.png");
}
#[test]
fn paulivec_svg_labels_match_asymmetric_state() {
let state = Statevector::from_state(
2,
vec![
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.unwrap();
let density = DensityMatrix::from_state(2, state.data().to_vec()).unwrap();
for reverse_bits in [false, true] {
let options = StatePlotOptions {
reverse_bits,
..StatePlotOptions::default()
};
for svg in [
plot_state_paulivec(&state, &options).unwrap(),
plot_state_paulivec(&density, &options).unwrap(),
] {
let bar = |label: &str| {
let label_start = svg.find(&format!(">{label}</text>")).unwrap();
let bar_start = svg[..label_start].rfind("<rect ").unwrap();
let tag = &svg[bar_start..label_start];
(attr_f64(tag, "y"), attr_f64(tag, "height"))
};
let (one_y, one_height) = bar("II");
assert!(one_height > 1.0);
for (label, expected) in [("IX", 1.0), ("XI", 0.0), ("IZ", 0.0), ("ZI", 1.0)] {
let displayed = if reverse_bits {
label.chars().rev().collect::<String>()
} else {
label.to_string()
};
let (y, height) = bar(&displayed);
if expected == 1.0 {
assert_eq!((y, height), (one_y, one_height), "{displayed}");
} else {
assert_eq!(height, 1.0, "{displayed}");
assert!((y - one_y - one_height).abs() < 1e-3, "{displayed}");
}
}
}
}
}
#[test]
fn density_matrix_uses_same_state_plot_api() {
let statevector = plus_state();
let density_matrix =
DensityMatrix::from_state(statevector.num_qubits, statevector.data().to_vec()).unwrap();
let sv_svg = plot_state_paulivec(&statevector, &StatePlotOptions::default()).unwrap();
let dm_svg = plot_state_paulivec(&density_matrix, &StatePlotOptions::default()).unwrap();
assert_eq!(sv_svg, dm_svg);
let sv_city = plot_state_city(&statevector, &StatePlotOptions::default()).unwrap();
let dm_city = plot_state_city(&density_matrix, &StatePlotOptions::default()).unwrap();
assert_eq!(sv_city, dm_city);
}