1use std::sync::OnceLock;
4
5use crate::backend::{PauliString, Simulator};
6
7use crate::{ExecError, OutputLogical};
8
9pub fn logical_bloch(
16 sim: &Simulator,
17 output: &OutputLogical,
18 frame: (bool, bool),
19) -> Result<(f64, f64, f64), ExecError> {
20 let signature = logical_signature(sim, &[(output, frame)])?;
21 Ok((signature[0], signature[1], signature[2]))
22}
23
24pub fn logical_signature(
37 sim: &Simulator,
38 outputs: &[(&OutputLogical, (bool, bool))],
39) -> Result<Vec<f64>, ExecError> {
40 let cardinality = u32::try_from(outputs.len())
41 .ok()
42 .and_then(|n| 4usize.checked_pow(n))
43 .ok_or(ExecError::LogicalSignatureTooLarge {
44 outputs: outputs.len(),
45 })?;
46 let width = outputs
47 .iter()
48 .map(|(out, _)| out.logical_x.nqubits.max(out.logical_z.nqubits))
49 .max()
50 .unwrap_or(0);
51 let logicals: Vec<_> = outputs
52 .iter()
53 .map(|(out, frame)| {
54 (
55 crate::circuit::widen_pauli(&out.logical_x, width),
56 crate::circuit::widen_pauli(&out.logical_z, width),
57 *frame,
58 )
59 })
60 .collect();
61 (1..cardinality)
62 .map(|mut mask| {
63 let mut product = PauliString::new(width);
64 for (logical_x, logical_z, (x, z)) in &logicals {
65 let digit = mask & 3;
66 if digit == 1 || digit == 2 {
67 product = &product * logical_x;
68 product.set_phase(product.phase_exponent() + 2 * i32::from(*z));
69 }
70 if digit == 2 || digit == 3 {
71 product = &product * logical_z;
72 product.set_phase(product.phase_exponent() + 2 * i32::from(*x));
73 }
74 if digit == 2 {
75 product.set_phase(product.phase_exponent() + 1);
76 }
77 mask >>= 2;
78 }
79 sim.peek_observable_expectation(&product)
80 .map_err(Into::into)
81 })
82 .collect()
83}
84
85#[must_use]
91pub fn ccz_state_signature() -> &'static [f64] {
92 static SIGNATURE: OnceLock<Vec<f64>> = OnceLock::new();
93 SIGNATURE.get_or_init(|| {
94 use crate::backend::Pauli;
95 let mut sim = Simulator::with_seed(3, 0);
96 for q in 0..3 {
97 sim.h(q);
98 }
99 sim.ccz(0, 1, 2).expect("CCZ on three fresh qubits");
100 let outputs: Vec<_> = (0..3)
101 .map(|q| OutputLogical {
102 port: glam::IVec3::ZERO,
103 logical_x: PauliString::single(3, q, Pauli::X),
104 logical_z: PauliString::single(3, q, Pauli::Z),
105 consumed: false,
106 })
107 .collect();
108 logical_signature(
109 &sim,
110 &outputs
111 .iter()
112 .map(|out| (out, (false, false)))
113 .collect::<Vec<_>>(),
114 )
115 .expect("valid three-qubit logicals")
116 })
117}
118
119#[must_use]
121pub fn ccz_orbit_signatures() -> &'static [Vec<f64>] {
122 static SIGNATURES: OnceLock<Vec<Vec<f64>>> = OnceLock::new();
123 SIGNATURES.get_or_init(|| {
124 (0..64usize)
125 .map(|frame| {
126 ccz_state_signature()
127 .iter()
128 .enumerate()
129 .map(|(index, expectation)| {
130 let mut mask = index + 1;
131 let mut frame = frame;
132 let mut flip = false;
133 for _ in 0..3 {
134 let pauli = mask & 3;
135 let byproduct = frame & 3;
136 flip ^= pauli != 0 && byproduct != 0 && pauli != byproduct;
137 mask >>= 2;
138 frame >>= 2;
139 }
140 expectation * sign(flip)
141 })
142 .collect()
143 })
144 .collect()
145 })
146}
147
148#[must_use]
150pub fn signatures_match(a: &[f64], b: &[f64]) -> bool {
151 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-9)
152}
153
154fn sign(bit: bool) -> f64 {
155 if bit { -1.0 } else { 1.0 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::backend::Pauli;
162
163 #[test]
164 fn signed_logical_readouts_preserve_y_and_frame_without_mutation() {
165 let mut sim = Simulator::with_seed(2, 0);
166 sim.h(0);
167 sim.t(0).unwrap();
168 sim.cx(0, 1).unwrap();
169 let mut output = OutputLogical {
170 port: glam::IVec3::ZERO,
171 logical_x: &PauliString::single(2, 0, Pauli::X) * &PauliString::single(2, 1, Pauli::X),
172 logical_z: PauliString::single(1, 0, Pauli::Z),
173 consumed: false,
174 };
175 let r = std::f64::consts::FRAC_1_SQRT_2;
176 for x_sign in [false, true] {
177 for z_sign in [false, true] {
178 output.logical_x.set_phase(2 * i32::from(x_sign));
179 output.logical_z.set_phase(2 * i32::from(z_sign));
180 let (x, y, z) = logical_bloch(&sim, &output, (false, false)).unwrap();
181 assert!((x - r * sign(x_sign)).abs() < 1e-9);
182 assert!((y - r * sign(x_sign ^ z_sign)).abs() < 1e-9);
183 assert!(z.abs() < 1e-9);
184 let signature = logical_signature(&sim, &[(&output, (z_sign, x_sign))]).unwrap();
185 assert!(signatures_match(&signature, &[r, r, 0.0]));
186 }
187 }
188 assert_eq!(sim.num_qubits(), 2);
189 assert_eq!(sim.peek_z(0).unwrap(), 0.0);
190 sim.reset(0).unwrap();
191 sim.reset(1).unwrap();
192 sim.reset(2).unwrap();
195 assert_eq!(
196 logical_bloch(&sim, &output, (false, false)).unwrap().2,
197 -1.0
198 );
199 assert!(signatures_match(
200 &logical_signature(&sim, &[(&output, (true, false))]).unwrap(),
201 &[0.0, 0.0, 1.0],
202 ));
203 }
204
205 #[test]
206 fn joint_signature_extends_mixed_widths_and_preserves_signed_correlations() {
207 let mut sim = Simulator::with_seed(2, 0);
208 sim.h(0);
209 sim.cx(0, 1).unwrap();
210 let first = OutputLogical {
211 port: glam::IVec3::ZERO,
212 logical_x: PauliString::single(1, 0, Pauli::X),
213 logical_z: PauliString::single(1, 0, Pauli::Z),
214 consumed: false,
215 };
216 let mut second = OutputLogical {
217 port: glam::IVec3::X,
218 logical_x: PauliString::single(2, 1, Pauli::X),
219 logical_z: PauliString::single(2, 1, Pauli::Z),
220 consumed: false,
221 };
222 second.logical_z.set_phase(2);
223 let signature =
224 logical_signature(&sim, &[(&first, (false, false)), (&second, (true, false))]).unwrap();
225 let mut bell = vec![0.0; 15];
226 bell[4] = 1.0; bell[9] = -1.0; bell[14] = 1.0; assert!(signatures_match(&signature, &bell));
230
231 let count = usize::BITS as usize / 2;
233 assert!(matches!(
234 logical_signature(&sim, &vec![(&first, (false, false)); count]),
235 Err(ExecError::LogicalSignatureTooLarge { outputs }) if outputs == count,
236 ));
237 }
238
239 #[test]
240 fn ccz_orbit_accepts_paulis_and_rejects_non_clifford_twist() {
241 let mut sim = Simulator::with_seed(3, 0);
242 for q in 0..3 {
243 sim.h(q);
244 }
245 sim.ccz(0, 1, 2).unwrap();
246 sim.x(0);
247 let outputs: Vec<_> = (0..3)
248 .map(|q| OutputLogical {
249 port: glam::IVec3::ZERO,
250 logical_x: PauliString::single(3, q, Pauli::X),
251 logical_z: PauliString::single(3, q, Pauli::Z),
252 consumed: false,
253 })
254 .collect();
255 let outputs: Vec<_> = outputs.iter().map(|out| (out, (false, false))).collect();
256 let orbit = ccz_orbit_signatures();
257 assert_eq!(orbit.len(), 64);
258 for (i, member) in orbit.iter().enumerate() {
259 assert!(
260 !orbit[..i]
261 .iter()
262 .any(|other| signatures_match(member, other))
263 );
264 }
265 let signature = logical_signature(&sim, &outputs).unwrap();
266 assert!(
267 orbit
268 .iter()
269 .any(|member| signatures_match(member, &signature))
270 );
271 sim.t(0).unwrap();
272 let signature = logical_signature(&sim, &outputs).unwrap();
273 assert!(
274 !orbit
275 .iter()
276 .any(|member| signatures_match(member, &signature))
277 );
278 }
279}