1use crate::quantum::{circuit_hash, Amp, BaseGate, Circuit, Gate, StateVector, FRAC};
32
33fn mcz(c: &mut Circuit, n: u8) {
40 if n == 1 {
41 c.z(0);
42 return;
43 }
44 c.ops.push(Gate { base: BaseGate::Z, controls: (0..n - 1).collect(), target: n - 1, param: 0 });
45}
46
47pub fn bell() -> Circuit {
49 let mut c = Circuit::new(2);
50 c.h(0);
51 c.cx(0, 1);
52 c
53}
54
55pub fn ghz(n: u8) -> Circuit {
57 let mut c = Circuit::new(n.max(1));
58 c.h(0);
59 for i in 1..n {
60 c.cx(i - 1, i);
61 }
62 c
63}
64
65pub fn grover(n: u8, marked: u32) -> Circuit {
68 let n = n.clamp(1, 12);
69 let mut c = Circuit::new(n);
70 for q in 0..n {
71 c.h(q);
72 }
73 let dim = 1u64 << n;
74 let iters = ((std::f64::consts::PI / 4.0) * (dim as f64).sqrt()).floor() as u32;
75 for _ in 0..iters.max(1) {
76 for q in 0..n {
78 if (marked >> q) & 1 == 0 {
79 c.x(q);
80 }
81 }
82 mcz(&mut c, n);
83 for q in 0..n {
84 if (marked >> q) & 1 == 0 {
85 c.x(q);
86 }
87 }
88 for q in 0..n {
90 c.h(q);
91 }
92 for q in 0..n {
93 c.x(q);
94 }
95 mcz(&mut c, n);
96 for q in 0..n {
97 c.x(q);
98 }
99 for q in 0..n {
100 c.h(q);
101 }
102 }
103 c
104}
105
106pub fn bernstein_vazirani(secret: u32, nbits: u8) -> Circuit {
110 let nbits = nbits.clamp(1, 20);
111 let anc = nbits;
112 let mut c = Circuit::new(nbits + 1);
113 c.x(anc);
114 for q in 0..=nbits {
115 c.h(q);
116 }
117 for q in 0..nbits {
118 if (secret >> q) & 1 == 1 {
119 c.cx(q, anc);
120 }
121 }
122 for q in 0..nbits {
123 c.h(q);
124 }
125 c
126}
127
128pub fn deutsch_jozsa(balanced: bool, nbits: u8) -> Circuit {
132 let nbits = nbits.clamp(1, 20);
133 let anc = nbits;
134 let mut c = Circuit::new(nbits + 1);
135 c.x(anc);
136 for q in 0..=nbits {
137 c.h(q);
138 }
139 if balanced {
140 for q in 0..nbits {
141 c.cx(q, anc);
142 }
143 }
144 for q in 0..nbits {
145 c.h(q);
146 }
147 c
148}
149
150pub fn teleportation() -> Circuit {
154 let mut c = Circuit::new(3);
155 c.h(0);
157 c.t(0);
158 c.h(1);
160 c.cx(1, 2);
161 c.cx(0, 1);
163 c.h(0);
164 c.cx(1, 2);
166 c.cz(0, 2);
167 c
168}
169
170pub fn superdense(bits: u8) -> Circuit {
173 let mut c = Circuit::new(2);
174 c.h(0);
175 c.cx(0, 1);
176 if bits & 1 != 0 {
179 c.z(0);
180 }
181 if bits & 2 != 0 {
182 c.x(0);
183 }
184 c.cx(0, 1);
185 c.h(0);
186 c
187}
188
189fn splitmix(s: &mut u64) -> u64 {
190 *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
191 let mut z = *s;
192 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
193 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
194 z ^ (z >> 31)
195}
196
197pub fn trotter_ising(n: u8, steps: u8) -> Circuit {
202 let n = n.clamp(2, 12);
203 let k = 3u16; let mut c = Circuit::new(n);
205 for _ in 0..steps.clamp(1, 20) {
206 for q in 0..n - 1 {
207 c.cx(q, q + 1);
208 c.p(k, q + 1);
209 c.cx(q, q + 1);
210 }
211 for q in 0..n {
212 c.h(q);
213 c.p(k, q);
214 c.h(q);
215 }
216 }
217 c
218}
219
220pub fn random_clifford(n: u8, depth: u16, seed: u64) -> Circuit {
222 let n = n.clamp(1, 12);
223 let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(1);
224 let mut c = Circuit::new(n);
225 for _ in 0..depth {
226 match splitmix(&mut st) % 3 {
227 0 => {
228 c.h((splitmix(&mut st) % n as u64) as u8);
229 }
230 1 => {
231 c.s((splitmix(&mut st) % n as u64) as u8);
232 }
233 _ if n >= 2 => {
234 let a = (splitmix(&mut st) % n as u64) as u8;
235 let mut b = (splitmix(&mut st) % n as u64) as u8;
236 if b == a {
237 b = (b + 1) % n;
238 }
239 c.cx(a, b);
240 }
241 _ => {
242 c.h(0);
243 }
244 }
245 }
246 c
247}
248
249pub fn build_algorithm(id: &str, n: u8, param: u32) -> Option<Circuit> {
251 Some(match id {
252 "bell" => bell(),
253 "ghz" => ghz(n.clamp(2, 12)),
254 "qft" => Circuit::qft(n.clamp(1, 12)),
255 "grover" => grover(n.clamp(2, 10), param),
256 "bv" => bernstein_vazirani(param, n.clamp(1, 10)),
257 "dj" => deutsch_jozsa(param != 0, n.clamp(1, 10)),
258 "teleport" => teleportation(),
259 "superdense" => superdense((param & 3) as u8),
260 "trotter" => trotter_ising(n.clamp(2, 10), param.clamp(1, 12) as u8),
261 "rclifford" => random_clifford(n.clamp(2, 10), 40, param as u64),
262 _ => return None,
263 })
264}
265
266pub fn catalog() -> Vec<(&'static str, &'static str, &'static str)> {
268 vec![
269 ("bell", "Bell pair", "The canonical entangled pair (|00⟩+|11⟩)/√2 — entanglement in two gates."),
270 ("ghz", "GHZ state", "n-qubit maximal entanglement (|0…0⟩+|1…1⟩)/√2."),
271 ("qft", "Quantum Fourier Transform", "The quantum FFT — Hadamard + controlled-phase ladder."),
272 ("grover", "Grover search", "Amplitude amplification finds a marked item in √N steps."),
273 ("bv", "Bernstein–Vazirani", "Recover a hidden bitstring in one query — interference at work."),
274 ("dj", "Deutsch–Jozsa", "One query decides constant vs balanced."),
275 ("teleport", "Teleportation", "Move a state across a Bell pair via corrections."),
276 ("superdense", "Superdense coding", "Two classical bits down one qubit."),
277 ("trotter", "Trotter Ising", "Transverse-field Ising dynamics — the tensor-network / sparse-Pauli home turf."),
278 ("rclifford", "Random Clifford", "A random Clifford circuit — the stabilizer backend's home turf."),
279 ]
280}
281
282const SCALE: f64 = (1u64 << FRAC) as f64;
287
288#[inline]
289fn re(a: Amp) -> f64 {
290 a.re as f64 / SCALE
291}
292#[inline]
293fn im(a: Amp) -> f64 {
294 a.im as f64 / SCALE
295}
296
297fn total_weight(sv: &StateVector) -> f64 {
298 sv.prob_weights().iter().map(|&w| w as f64).sum::<f64>()
299}
300
301pub fn probabilities(sv: &StateVector) -> Vec<f64> {
303 let t = total_weight(sv);
304 if t <= 0.0 {
305 return vec![0.0; sv.amps.len()];
306 }
307 sv.prob_weights().iter().map(|&w| w as f64 / t).collect()
308}
309
310pub fn expect_z(sv: &StateVector, q: u8) -> f64 {
312 let bit = 1usize << q;
313 let t = total_weight(sv);
314 if t <= 0.0 {
315 return 0.0;
316 }
317 let mut acc = 0.0;
318 for (i, w) in sv.prob_weights().iter().enumerate() {
319 let s = if i & bit == 0 { 1.0 } else { -1.0 };
320 acc += s * (*w as f64);
321 }
322 acc / t
323}
324
325pub fn expect_xy(sv: &StateVector, q: u8) -> (f64, f64) {
327 let bit = 1usize << q;
328 let t = total_weight(sv);
329 if t <= 0.0 {
330 return (0.0, 0.0);
331 }
332 let mut ex = 0.0;
333 let mut ey = 0.0;
334 for i in 0..sv.amps.len() {
335 if i & bit == 0 {
336 let a = sv.amps[i];
337 let b = sv.amps[i | bit];
338 ex += 2.0 * (re(a) * re(b) + im(a) * im(b));
340 ey += 2.0 * (re(a) * im(b) - im(a) * re(b));
341 }
342 }
343 (ex * SCALE * SCALE / t, ey * SCALE * SCALE / t)
344}
345
346pub fn bloch(sv: &StateVector, q: u8) -> [f64; 3] {
348 let (x, y) = expect_xy(sv, q);
349 [x, y, expect_z(sv, q)]
350}
351
352pub fn entanglement_entropy(sv: &StateVector, q: u8) -> f64 {
356 let b = bloch(sv, q);
357 let r = (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]).sqrt().min(1.0);
358 let lam = (1.0 + r) / 2.0;
359 binary_entropy(lam)
360}
361
362pub fn purity(sv: &StateVector, q: u8) -> f64 {
364 let b = bloch(sv, q);
365 let r2 = (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]).min(1.0);
366 (1.0 + r2) / 2.0
367}
368
369fn binary_entropy(p: f64) -> f64 {
370 if p <= 0.0 || p >= 1.0 {
371 return 0.0;
372 }
373 -p * p.log2() - (1.0 - p) * (1.0 - p).log2()
374}
375
376pub fn top_outcomes(sv: &StateVector, top: usize) -> Vec<(usize, f64)> {
378 let mut v: Vec<(usize, f64)> = probabilities(sv).into_iter().enumerate().collect();
379 v.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
380 v.truncate(top);
381 v
382}
383
384pub fn noisy_expect_z(
394 circuit: &Circuit,
395 q: u8,
396 depol_permille: u32,
397 trajectories: u32,
398 seed: u64,
399) -> f64 {
400 let mut st = seed.wrapping_mul(0x9E37_79B9).wrapping_add(1);
401 let mut acc = 0.0;
402 let traj = trajectories.max(1);
403 for _ in 0..traj {
404 let mut noisy = Circuit::new(circuit.n_qubits);
405 for g in &circuit.ops {
406 noisy.ops.push(g.clone());
407 for &qb in g.controls.iter().chain(std::iter::once(&g.target)) {
408 if (splitmix(&mut st) % 1000) < depol_permille as u64 {
409 let base = match splitmix(&mut st) % 3 {
410 0 => BaseGate::X,
411 1 => BaseGate::Y,
412 _ => BaseGate::Z,
413 };
414 noisy.ops.push(Gate { base, controls: vec![], target: qb, param: 0 });
415 }
416 }
417 }
418 if let Ok(sv) = noisy.simulate() {
419 acc += expect_z(&sv, q);
420 }
421 }
422 acc / traj as f64
423}
424
425#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum Backend {
431 Stabilizer,
433 Statevector,
435 TensorNetwork,
438}
439
440pub fn is_clifford(c: &Circuit) -> bool {
443 c.ops.iter().all(|g| match g.controls.len() {
444 0 => matches!(
445 g.base,
446 BaseGate::I | BaseGate::X | BaseGate::Y | BaseGate::Z | BaseGate::H | BaseGate::S | BaseGate::Sdg
447 ) || (g.base == BaseGate::P && (g.param == 1 || g.param == 2)),
448 1 => matches!(g.base, BaseGate::X | BaseGate::Y | BaseGate::Z)
449 || (g.base == BaseGate::P && g.param == 1),
450 _ => false,
451 })
452}
453
454pub fn recommend_backend(c: &Circuit) -> (Backend, &'static str) {
457 if is_clifford(c) {
458 (Backend::Stabilizer, "all-Clifford → stabilizer tableau, O(n²); scales far past statevector")
459 } else if c.n_qubits <= 22 {
460 (Backend::Statevector, "non-Clifford, ≤22 qubits → exact dense statevector")
461 } else {
462 (Backend::TensorNetwork, "non-Clifford, >22 qubits → tensor-network contraction (the referee)")
463 }
464}
465
466pub fn statevector_work(c: &Circuit) -> u64 {
469 (c.ops.len() as u64).saturating_mul(1u64 << c.n_qubits.min(63))
470}
471
472pub struct Routed {
478 pub circuit: Circuit,
479 pub final_map: Vec<u8>,
481 pub swaps: u32,
482 pub source_2q: u32,
483 pub source_nonlocal: u32,
484}
485
486pub fn route_line(src: &Circuit) -> Routed {
490 let n = src.n_qubits;
491 let mut phys: Vec<u8> = (0..n).collect();
492 let mut logq: Vec<u8> = (0..n).collect();
493 let mut out = Circuit::new(n);
494 let mut swaps = 0u32;
495 let (mut s2q, mut nonlocal) = (0u32, 0u32);
496 let do_swap = |out: &mut Circuit, phys: &mut [u8], logq: &mut [u8], a: u8, b: u8| {
497 out.swap(a, b);
498 let (la, lb) = (logq[a as usize], logq[b as usize]);
499 logq.swap(a as usize, b as usize);
500 phys[la as usize] = b;
501 phys[lb as usize] = a;
502 };
503 for g in &src.ops {
504 match g.controls.len() {
505 0 => {
506 out.ops.push(Gate {
507 base: g.base,
508 controls: vec![],
509 target: phys[g.target as usize],
510 param: g.param,
511 });
512 }
513 1 => {
514 s2q += 1;
515 let (la, lb) = (g.controls[0], g.target);
516 let mut pa = phys[la as usize];
517 let pb = phys[lb as usize];
518 if (pa as i32 - pb as i32).abs() > 1 {
519 nonlocal += 1;
520 while (pa as i32 - pb as i32).abs() > 1 {
522 let step = if pa < pb { pa + 1 } else { pa - 1 };
523 do_swap(&mut out, &mut phys, &mut logq, pa, step);
524 swaps += 1;
525 pa = step;
526 }
527 }
528 out.ops.push(Gate {
529 base: g.base,
530 controls: vec![phys[la as usize]],
531 target: phys[lb as usize],
532 param: g.param,
533 });
534 }
535 _ => {
536 s2q += 1;
537 out.ops.push(Gate {
538 base: g.base,
539 controls: g.controls.iter().map(|&c| phys[c as usize]).collect(),
540 target: phys[g.target as usize],
541 param: g.param,
542 });
543 }
544 }
545 }
546 Routed { circuit: out, final_map: phys, swaps, source_2q: s2q, source_nonlocal: nonlocal }
547}
548
549fn restore_swaps(final_map: &[u8], n: u8) -> Vec<(u8, u8)> {
551 let mut logq = vec![0u8; n as usize];
552 for (l, &p) in final_map.iter().enumerate() {
553 logq[p as usize] = l as u8;
554 }
555 let mut phys: Vec<u8> = final_map.to_vec();
556 let mut out = Vec::new();
557 for p in 0..n as usize {
558 while logq[p] != p as u8 {
559 let cur = phys[p] as usize; out.push((p as u8, cur as u8));
561 let (lp, lc) = (logq[p], logq[cur]);
562 logq[p] = lc;
563 logq[cur] = lp;
564 phys[lp as usize] = cur as u8;
565 phys[lc as usize] = p as u8;
566 }
567 }
568 out
569}
570
571pub fn routing_equivalent(src: &Circuit, routed: &Routed) -> bool {
574 let mut full = routed.circuit.clone();
575 for (a, b) in restore_swaps(&routed.final_map, src.n_qubits) {
576 full.swap(a, b);
577 }
578 match (full.simulate(), src.simulate()) {
579 (Ok(a), Ok(b)) => a.statevector_hash() == b.statevector_hash(),
580 _ => false,
581 }
582}
583
584pub fn to_qasm(c: &Circuit) -> String {
591 let mut s = String::from("OPENQASM 3.0;\ninclude \"stdgates.inc\";\n");
592 s.push_str(&format!("qubit[{}] q;\n", c.n_qubits));
593 for g in &c.ops {
594 let t = g.target;
595 let line = match (g.base, g.controls.as_slice()) {
596 (BaseGate::I, []) => continue,
597 (BaseGate::X, []) => format!("x q[{t}];"),
598 (BaseGate::Y, []) => format!("y q[{t}];"),
599 (BaseGate::Z, []) => format!("z q[{t}];"),
600 (BaseGate::H, []) => format!("h q[{t}];"),
601 (BaseGate::S, []) => format!("s q[{t}];"),
602 (BaseGate::Sdg, []) => format!("sdg q[{t}];"),
603 (BaseGate::T, []) => format!("t q[{t}];"),
604 (BaseGate::Tdg, []) => format!("tdg q[{t}];"),
605 (BaseGate::P, []) => format!("p(2*pi/{}) q[{t}];", 1u64 << g.param),
606 (BaseGate::X, [c0]) => format!("cx q[{c0}],q[{t}];"),
607 (BaseGate::Y, [c0]) => format!("cy q[{c0}],q[{t}];"),
608 (BaseGate::Z, [c0]) => format!("cz q[{c0}],q[{t}];"),
609 (BaseGate::P, [c0]) => format!("cp(2*pi/{}) q[{c0}],q[{t}];", 1u64 << g.param),
610 (BaseGate::X, [a, b]) => format!("ccx q[{a}],q[{b}],q[{t}];"),
611 (BaseGate::Z, [a, b]) => format!("ccz q[{a}],q[{b}],q[{t}];"),
612 (base, ctrls) => format!("// unmapped: {base:?} controls={ctrls:?} target={t}"),
613 };
614 s.push_str(&line);
615 s.push('\n');
616 }
617 s
618}
619
620pub fn from_qasm(src: &str) -> Result<Circuit, String> {
643 let mut n: Option<u8> = None;
644 for raw in src.lines() {
646 let line = strip(raw);
647 if let Some(rest) = line.strip_prefix("qubit[")
648 && let Some(end) = rest.find(']')
649 {
650 n = rest[..end].trim().parse().ok();
651 } else if let Some(rest) = line.strip_prefix("qreg ")
652 && let (Some(a), Some(b)) = (rest.find('['), rest.find(']'))
653 {
654 n = rest[a + 1..b].trim().parse().ok();
655 }
656 }
657 let n = n.ok_or("no qubit register declaration (qubit[n] q; or qreg q[n];)")?;
658 let mut c = Circuit::new(n);
659 for raw in src.lines() {
660 let line = strip(raw);
661 if line.is_empty() {
662 continue;
663 }
664 if line.contains("measure") {
670 continue;
671 }
672 let head: String = line.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect();
673 let head = head.as_str();
674 match head {
675 "OPENQASM" | "include" | "qubit" | "qreg" | "creg" | "bit" | "measure" | "barrier"
676 | "reset" | "gate" => continue,
677 _ => {}
678 }
679 let qs = parse_qubits(&line);
680 let push = |c: &mut Circuit, base: BaseGate, controls: Vec<u8>, target: u8, param: u16| {
681 c.ops.push(Gate { base, controls, target, param });
682 };
683 match head {
684 "id" | "i" => {}
685 "x" => require1(&qs).map(|t| push(&mut c, BaseGate::X, vec![], t, 0))?,
686 "y" => require1(&qs).map(|t| push(&mut c, BaseGate::Y, vec![], t, 0))?,
687 "z" => require1(&qs).map(|t| push(&mut c, BaseGate::Z, vec![], t, 0))?,
688 "h" => require1(&qs).map(|t| push(&mut c, BaseGate::H, vec![], t, 0))?,
689 "s" => require1(&qs).map(|t| push(&mut c, BaseGate::S, vec![], t, 0))?,
690 "sdg" => require1(&qs).map(|t| push(&mut c, BaseGate::Sdg, vec![], t, 0))?,
691 "t" => require1(&qs).map(|t| push(&mut c, BaseGate::T, vec![], t, 0))?,
692 "tdg" => require1(&qs).map(|t| push(&mut c, BaseGate::Tdg, vec![], t, 0))?,
693 "cx" | "cnot" => require2(&qs).map(|(a, t)| push(&mut c, BaseGate::X, vec![a], t, 0))?,
694 "cy" => require2(&qs).map(|(a, t)| push(&mut c, BaseGate::Y, vec![a], t, 0))?,
695 "cz" => require2(&qs).map(|(a, t)| push(&mut c, BaseGate::Z, vec![a], t, 0))?,
696 "swap" => require2(&qs).map(|(a, b)| {
697 c.swap(a, b);
698 })?,
699 "ccx" | "toffoli" => require3(&qs).map(|(a, b, t)| push(&mut c, BaseGate::X, vec![a, b], t, 0))?,
700 "ccz" => require3(&qs).map(|(a, b, t)| push(&mut c, BaseGate::Z, vec![a, b], t, 0))?,
701 "p" | "u1" | "phase" => {
702 let t = parse_angle(&arg_n(&line, 0)?)?;
703 let q = require1(&qs)?;
704 push_turn(&mut c, &[], q, t);
705 }
706 "cp" | "cu1" | "cphase" => {
707 let t = parse_angle(&arg_n(&line, 0)?)?;
708 let (a, q) = require2(&qs)?;
709 push_turn(&mut c, &[a], q, t);
710 }
711 "rz" => {
716 let t = parse_angle(&arg_n(&line, 0)?)?;
717 let q = require1(&qs)?;
718 push_turn(&mut c, &[], q, t);
719 }
720 "rx" => {
721 let t = parse_angle(&arg_n(&line, 0)?)?;
722 let q = require1(&qs)?;
723 push_rx(&mut c, q, t);
724 }
725 "ry" => {
726 let t = parse_angle(&arg_n(&line, 0)?)?;
727 let q = require1(&qs)?;
728 push_ry(&mut c, q, t);
729 }
730 "sx" | "sxdg" => {
732 let q = require1(&qs)?;
733 let t = if head == "sx" {
734 Turn { m: 1, k: 2 }
735 } else {
736 Turn { m: 3, k: 2 }
737 };
738 push_rx(&mut c, q, t);
739 }
740 "u" | "u3" | "u2" => {
742 let (theta, phi, lam) = if head == "u2" {
743 (Turn { m: 1, k: 2 }, parse_angle(&arg_n(&line, 0)?)?, parse_angle(&arg_n(&line, 1)?)?)
745 } else {
746 (
747 parse_angle(&arg_n(&line, 0)?)?,
748 parse_angle(&arg_n(&line, 1)?)?,
749 parse_angle(&arg_n(&line, 2)?)?,
750 )
751 };
752 let q = require1(&qs)?;
753 push_turn(&mut c, &[], q, lam);
754 push_ry(&mut c, q, theta);
755 push_turn(&mut c, &[], q, phi);
756 }
757 "crz" => {
760 let (p, den) = angle_turns(&arg_n(&line, 0)?)?;
763 if den * 2 > (1i128 << crate::quantum::DYADIC_MAX) {
764 return Err("crz angle is finer than the dyadic floor".into());
765 }
766 let (a, q) = require2(&qs)?;
767 push_turn(&mut c, &[], a, to_turn(-p, den * 2));
768 push_turn(&mut c, &[a], q, to_turn(p, den));
769 }
770 other => {
771 return Err(format!(
772 "unsupported gate: '{other}' (supported: id x y z h s sdg t tdg sx sxdg \
773 rx ry rz p/u1/phase u/u2/u3 cx cy cz cp/cu1 crz swap ccx ccz)"
774 ));
775 }
776 }
777 }
778 c.validate().map_err(|e| e.to_string())?;
779 Ok(c)
780}
781
782fn strip(line: &str) -> String {
783 let no_comment = line.split("//").next().unwrap_or("");
784 no_comment.trim().trim_end_matches(';').trim().to_string()
785}
786
787fn parse_qubits(line: &str) -> Vec<u8> {
788 let mut out = Vec::new();
789 let bytes = line.as_bytes();
790 let mut i = 0;
791 while i < bytes.len() {
792 if bytes[i] == b'[' {
793 let mut j = i + 1;
794 while j < bytes.len() && bytes[j] != b']' {
795 j += 1;
796 }
797 if let Ok(v) = line[i + 1..j].trim().parse::<u8>() {
798 out.push(v);
799 }
800 i = j + 1;
801 } else {
802 i += 1;
803 }
804 }
805 out
806}
807
808fn require1(qs: &[u8]) -> Result<u8, String> {
809 qs.first().copied().ok_or_else(|| "expected 1 qubit".into())
810}
811fn require2(qs: &[u8]) -> Result<(u8, u8), String> {
812 if qs.len() >= 2 {
813 Ok((qs[0], qs[1]))
814 } else {
815 Err("expected 2 qubits".into())
816 }
817}
818fn require3(qs: &[u8]) -> Result<(u8, u8, u8), String> {
819 if qs.len() >= 3 {
820 Ok((qs[0], qs[1], qs[2]))
821 } else {
822 Err("expected 3 qubits".into())
823 }
824}
825
826#[derive(Clone, Copy, Debug, PartialEq, Eq)]
837struct Turn {
838 m: u64,
839 k: u32,
840}
841
842fn decimal_ratio(tok: &str) -> Option<(i128, i128)> {
845 if tok.is_empty() {
846 return None;
847 }
848 match tok.split_once('.') {
849 None => tok.parse::<i128>().ok().map(|v| (v, 1)),
850 Some((int, frac)) => {
851 if frac.is_empty() || !frac.bytes().all(|b| b.is_ascii_digit()) {
852 return None;
853 }
854 let scale = 10i128.checked_pow(frac.len() as u32)?;
855 let whole: i128 = if int.is_empty() { 0 } else { int.parse().ok()? };
856 let f: i128 = frac.parse().ok()?;
857 Some((whole * scale + f, scale))
858 }
859 }
860}
861
862fn to_turn(p: i128, q: i128) -> Turn {
877 let k = q.trailing_zeros();
878 let m = ((p % q) + q) % q;
879 Turn { m: m as u64, k }
880}
881
882fn angle_turns(arg: &str) -> Result<(i128, i128), String> {
890 let s = arg.replace(' ', "").to_lowercase();
891 if s.is_empty() {
892 return Err("empty angle".into());
893 }
894 let (sign, body) = match s.strip_prefix('-') {
895 Some(r) => (-1i128, r),
896 None => (1i128, s.strip_prefix('+').unwrap_or(&s)),
897 };
898
899 let mut parts: Vec<(char, String)> = Vec::new();
901 let (mut cur, mut op) = (String::new(), '*');
902 for ch in body.chars() {
903 if ch == '*' || ch == '/' {
904 parts.push((op, std::mem::take(&mut cur)));
905 op = ch;
906 } else {
907 cur.push(ch);
908 }
909 }
910 parts.push((op, cur));
911
912 let (mut num, mut den, mut pi_pow) = (sign, 1i128, 0i32);
913 for (op, tok) in &parts {
914 if tok == "pi" {
915 pi_pow += if *op == '*' { 1 } else { -1 };
916 continue;
917 }
918 let (a, b) = decimal_ratio(tok)
919 .ok_or_else(|| format!("cannot read '{tok}' in angle '{arg}'"))?;
920 if a == 0 && *op == '/' {
921 return Err("division by zero in angle".into());
922 }
923 if *op == '*' {
924 num *= a;
925 den *= b;
926 } else {
927 num *= b;
928 den *= a;
929 }
930 }
931
932 if num == 0 {
933 return Ok((0, 2));
934 }
935 if pi_pow == 0 {
936 let v = (num as f64 / den as f64) / std::f64::consts::TAU;
938 for k in 1..=crate::quantum::DYADIC_MAX as u32 {
939 let scaled = v * (1u64 << k) as f64;
940 let r = scaled.round();
941 if (scaled - r).abs() < 1e-9 {
942 return Ok((r as i128, 1i128 << k));
943 }
944 }
945 return Err(format!(
946 "angle '{arg}' is not a dyadic multiple of 2π; this simulator is exact, \
947 so it refuses angles it cannot represent rather than rounding them"
948 ));
949 }
950 if pi_pow != 1 {
951 return Err(format!("angle '{arg}' is not linear in pi"));
952 }
953
954 let mut p = num;
956 let mut q = den * 2;
957 if q < 0 {
958 p = -p;
959 q = -q;
960 }
961 let g = {
962 let (mut a, mut b) = (p.unsigned_abs(), q.unsigned_abs());
963 while b != 0 {
964 let t = a % b;
965 a = b;
966 b = t;
967 }
968 a.max(1) as i128
969 };
970 p /= g;
971 q /= g;
972 if q <= 0 || (q as u128).count_ones() != 1 {
973 return Err(format!(
974 "angle '{arg}' is not a dyadic multiple of 2π (reduced denominator {q}); \
975 this simulator is exact, so it refuses angles it cannot represent"
976 ));
977 }
978 let k = q.trailing_zeros();
979 if k > crate::quantum::DYADIC_MAX as u32 {
980 return Err(format!("angle '{arg}' is finer than the dyadic floor (k={k})"));
981 }
982 Ok((p, q))
983}
984
985fn parse_angle(arg: &str) -> Result<Turn, String> {
993 let (p, q) = angle_turns(arg)?;
994 Ok(to_turn(p, q))
995}
996
997fn arg_n(line: &str, i: usize) -> Result<String, String> {
999 let a = line.find('(').ok_or("gate needs an argument")?;
1000 let b = line[a..].find(')').ok_or("unterminated argument")? + a;
1001 line[a + 1..b]
1002 .split(',')
1003 .nth(i)
1004 .map(|s| s.trim().to_string())
1005 .ok_or_else(|| format!("expected at least {} argument(s)", i + 1))
1006}
1007
1008fn push_turn(c: &mut Circuit, controls: &[u8], target: u8, t: Turn) {
1010 for i in 0..=t.k {
1011 if (t.m >> i) & 1 == 1 {
1012 let k = t.k - i;
1013 if k >= 1 {
1015 c.ops.push(Gate {
1016 base: BaseGate::P,
1017 controls: controls.to_vec(),
1018 target,
1019 param: k as u16,
1020 });
1021 }
1022 }
1023 }
1024}
1025
1026fn push_rx(c: &mut Circuit, target: u8, t: Turn) {
1028 c.h(target);
1029 push_turn(c, &[], target, t);
1030 c.h(target);
1031}
1032
1033fn push_ry(c: &mut Circuit, target: u8, t: Turn) {
1035 let one = |b: BaseGate| Gate { base: b, controls: vec![], target, param: 0 };
1036 c.ops.push(one(BaseGate::Sdg));
1037 push_rx(c, target, t);
1038 c.ops.push(one(BaseGate::S));
1039}
1040
1041pub fn circuit_id(c: &Circuit) -> [u8; 32] {
1043 circuit_hash(c)
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048 #[test]
1049 #[ignore]
1050 fn probe_sv_perf() {
1051 use std::time::Instant;
1052 use crate::quantum::Circuit;
1053 println!("\n statevector: time per gate vs width");
1054 for n in [8u8, 12, 16, 18, 20, 22] {
1055 let mut c = Circuit::new(n);
1056 for _ in 0..3 { for q in 0..n { c.h(q); } for q in 0..n - 1 { c.cx(q, q + 1); } }
1057 let gates = c.ops.len();
1058 let t0 = Instant::now();
1059 let sv = c.simulate().unwrap();
1060 let el = t0.elapsed();
1061 println!(" n={n:<3} dim={:<9} {gates:>4} gates {:>9.2?} total {:>8.1} ns/gate {:>7.2} ns/amp-gate",
1062 1usize << n, el, el.as_nanos() as f64 / gates as f64,
1063 el.as_nanos() as f64 / (gates as f64 * (1usize << n) as f64));
1064 std::hint::black_box(sv.statevector_hash());
1065 }
1066 println!("\n gate-kind cost at n=20 (200 gates each)");
1067 for kind in ["h", "t", "z", "cx", "cz"] {
1068 let n = 20u8;
1069 let mut c = Circuit::new(n);
1070 for i in 0..200 {
1071 let q = (i % n as usize) as u8;
1072 match kind {
1073 "h" => { c.h(q); }
1074 "t" => { c.t(q); }
1075 "z" => { c.z(q); }
1076 "cx" => { c.cx(q, (q + 1) % n); }
1077 _ => { c.cz(q, (q + 1) % n); }
1078 }
1079 }
1080 let t0 = Instant::now();
1081 let sv = c.simulate().unwrap();
1082 let el = t0.elapsed();
1083 println!(" {kind:<3} {:>8.1} ns/gate", el.as_nanos() as f64 / 200.0);
1084 std::hint::black_box(sv.statevector_hash());
1085 }
1086 }
1087
1088 use super::*;
1089
1090 fn approx(a: f64, b: f64, tol: f64) -> bool {
1091 (a - b).abs() < tol
1092 }
1093
1094 #[test]
1095 fn bell_is_maximally_entangled() {
1096 let sv = bell().simulate().unwrap();
1097 let p = probabilities(&sv);
1098 assert!(approx(p[0b00], 0.5, 1e-4) && approx(p[0b11], 0.5, 1e-4));
1099 assert!(approx(p[0b01], 0.0, 1e-4) && approx(p[0b10], 0.0, 1e-4));
1100 assert!(approx(entanglement_entropy(&sv, 0), 1.0, 1e-3), "S={}", entanglement_entropy(&sv, 0));
1102 assert!(approx(purity(&sv, 0), 0.5, 1e-3));
1103 }
1104
1105 #[test]
1106 fn ghz_all_or_nothing() {
1107 let sv = ghz(4).simulate().unwrap();
1108 let p = probabilities(&sv);
1109 assert!(approx(p[0], 0.5, 1e-4) && approx(p[15], 0.5, 1e-4));
1110 assert!(approx(entanglement_entropy(&sv, 2), 1.0, 1e-3));
1111 }
1112
1113 #[test]
1114 fn plus_state_bloch() {
1115 let mut c = Circuit::new(1);
1117 c.h(0);
1118 let sv = c.simulate().unwrap();
1119 let b = bloch(&sv, 0);
1120 assert!(approx(b[0], 1.0, 1e-3) && approx(b[1], 0.0, 1e-3) && approx(b[2], 0.0, 1e-3), "bloch={b:?}");
1121 assert!(approx(entanglement_entropy(&sv, 0), 0.0, 1e-3));
1122 }
1123
1124 #[test]
1125 fn grover_amplifies_the_marked_state() {
1126 for &(n, marked) in &[(3u8, 5u32), (4, 11), (5, 20)] {
1127 let sv = grover(n, marked).simulate().unwrap();
1128 let top = top_outcomes(&sv, 1)[0];
1129 assert_eq!(top.0, marked as usize, "grover n={n} should peak at {marked}");
1130 assert!(top.1 > 0.6, "marked prob {} too low", top.1);
1131 }
1132 }
1133
1134 #[test]
1135 fn bernstein_vazirani_recovers_secret() {
1136 let secret = 0b10110u32;
1137 let sv = bernstein_vazirani(secret, 5).simulate().unwrap();
1138 let top = top_outcomes(&sv, 1)[0];
1140 assert_eq!(top.0 & 0b11111, secret as usize, "BV should reveal the secret");
1141 }
1142
1143 #[test]
1144 fn deutsch_jozsa_distinguishes() {
1145 let sv_c = deutsch_jozsa(false, 4).simulate().unwrap();
1146 let sv_b = deutsch_jozsa(true, 4).simulate().unwrap();
1147 let inputs = |i: usize| i & 0b1111;
1149 assert_eq!(inputs(top_outcomes(&sv_c, 1)[0].0), 0, "constant → all-zero inputs");
1150 assert_ne!(inputs(top_outcomes(&sv_b, 1)[0].0), 0, "balanced → nonzero inputs");
1151 }
1152
1153 #[test]
1154 fn superdense_roundtrips_all_four() {
1155 for bits in 0u8..4 {
1156 let sv = superdense(bits).simulate().unwrap();
1157 let top = top_outcomes(&sv, 1)[0];
1158 assert_eq!(top.0, bits as usize, "superdense {bits} decoded wrong");
1159 assert!(top.1 > 0.99);
1160 }
1161 }
1162
1163 #[test]
1164 fn clifford_detection() {
1165 assert!(is_clifford(&bell()));
1166 assert!(is_clifford(&ghz(5)));
1167 assert!(!is_clifford(&teleportation())); assert!(!is_clifford(&Circuit::qft(4))); assert_eq!(recommend_backend(&bell()).0, Backend::Stabilizer);
1170 assert_eq!(recommend_backend(&Circuit::qft(6)).0, Backend::Statevector);
1171 }
1172
1173 #[test]
1174 fn qasm_round_trip() {
1175 for c in [bell(), ghz(3), Circuit::qft(4), grover(3, 5)] {
1176 let qasm = to_qasm(&c);
1177 let back = from_qasm(&qasm).expect("reparse");
1178 assert_eq!(
1180 c.simulate().unwrap().statevector_hash(),
1181 back.simulate().unwrap().statevector_hash(),
1182 "QASM round-trip changed the state"
1183 );
1184 }
1185 }
1186
1187 #[test]
1188 fn qasm_imports_foreign_circuit() {
1189 let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\n";
1190 let c = from_qasm(src).unwrap();
1191 assert_eq!(c.simulate().unwrap().statevector_hash(), bell().simulate().unwrap().statevector_hash());
1192 }
1193
1194 fn same_state(a: &str, b: &Circuit) {
1198 let got = from_qasm(a).unwrap_or_else(|e| panic!("parse {a:?}: {e}"));
1199 let f = got.simulate().unwrap().fidelity_fx(&b.simulate().unwrap());
1200 assert!(
1201 (f - crate::quantum::ONE).abs() < 1 << 12,
1202 "{a:?} gave fidelity {f} against the reference, want 1"
1203 );
1204 }
1205
1206 #[test]
1211 fn qasm3_assignment_measure_parses() {
1212 let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\n\
1213 h q[0];\ncx q[0], q[1];\nc[0] = measure q[0];\nc[1] = measure q[1];\n";
1214 same_state(src, &bell());
1215 }
1216
1217 #[test]
1220 fn rotations_decompose_to_the_dyadic_basis() {
1221 let mut x = Circuit::new(1);
1222 x.x(0);
1223 let mut y = Circuit::new(1);
1224 y.y(0);
1225 let mut z = Circuit::new(1);
1226 z.z(0);
1227 let mut t = Circuit::new(1);
1228 t.t(0);
1229
1230 same_state("qreg q[1];\nrx(pi) q[0];", &x);
1231 same_state("qreg q[1];\nry(pi) q[0];", &y);
1232 same_state("qreg q[1];\nrz(pi) q[0];", &z);
1233 same_state("qreg q[1];\np(pi/4) q[0];", &t);
1234 same_state("qreg q[1];\nu3(pi,0,pi) q[0];", &x);
1236 same_state("qreg q[1];\nu(pi,0,pi) q[0];", &x);
1237 same_state("qreg q[1];\nsx q[0];\nsx q[0];", &x);
1239 }
1240
1241 #[test]
1245 fn a_general_dyadic_angle_splits_across_gates() {
1246 let mut want = Circuit::new(1);
1247 want.h(0).p(3, 0).p(2, 0);
1248 let got = from_qasm("qreg q[1];\nh q[0];\np(3*pi/4) q[0];").unwrap();
1249 assert_eq!(
1250 got.simulate().unwrap().statevector_hash(),
1251 want.simulate().unwrap().statevector_hash()
1252 );
1253 assert_eq!(got.ops.len(), 3, "3π/4 should split into two P gates after the H");
1254 }
1255
1256 #[test]
1259 fn negative_and_zero_angles_are_exact() {
1260 let plain = Circuit::new(1);
1261 same_state("qreg q[1];\nrz(0) q[0];", &plain);
1262 same_state("qreg q[1];\np(0) q[0];", &plain);
1263 same_state("qreg q[1];\nrz(pi/2) q[0];\nrz(-pi/2) q[0];", &plain);
1264 same_state("qreg q[1];\nsx q[0];\nsxdg q[0];", &plain);
1265 let mut s = Circuit::new(1);
1267 s.s(0);
1268 same_state("qreg q[1];\np(pi*0.5) q[0];", &s);
1269 same_state("qreg q[1];\np(0.5*pi) q[0];", &s);
1270 }
1271
1272 #[test]
1275 fn controlled_rz_is_exact() {
1276 let mut hh = Circuit::new(2);
1280 hh.h(0).h(1);
1281 same_state(
1282 "qreg q[2];\nh q[0];\nh q[1];\ncrz(pi/2) q[0],q[1];\ncrz(-pi/2) q[0],q[1];",
1283 &hh,
1284 );
1285 let crz = from_qasm("qreg q[2];\nh q[0];\nh q[1];\ncrz(pi/2) q[0],q[1];").unwrap();
1286 let mut cp = Circuit::new(2);
1287 cp.h(0).h(1).cp(2, 0, 1);
1288 assert_ne!(
1289 crz.simulate().unwrap().statevector_hash(),
1290 cp.simulate().unwrap().statevector_hash(),
1291 "crz must not silently be cp — they differ by a phase on the control"
1292 );
1293 }
1294
1295 #[test]
1299 fn non_dyadic_angles_are_refused_not_rounded() {
1300 for src in ["qreg q[1];\nrz(pi/3) q[0];", "qreg q[1];\np(pi/5) q[0];"] {
1301 let e = from_qasm(src).expect_err("a non-dyadic angle must not be accepted");
1302 assert!(e.contains("dyadic"), "error should say why: {e}");
1303 }
1304 }
1305
1306 #[test]
1307 fn catalog_all_build_and_simulate() {
1308 for (id, _, _) in catalog() {
1309 let c = build_algorithm(id, 4, 5).expect(id);
1310 assert!(c.simulate().is_ok(), "{id} failed to simulate");
1311 }
1312 }
1313
1314 #[test]
1315 fn routing_preserves_the_state() {
1316 for c in [bell(), ghz(5), Circuit::qft(5), trotter_ising(6, 3)] {
1318 let r = route_line(&c);
1319 assert!(routing_equivalent(&c, &r), "routed circuit must reproduce the state");
1320 for g in &r.circuit.ops {
1322 if g.controls.len() == 1 {
1323 let d = (g.controls[0] as i32 - g.target as i32).abs();
1324 assert_eq!(d, 1, "routed 2q gate must be local: {} {}", g.controls[0], g.target);
1325 }
1326 }
1327 }
1328 }
1329
1330 #[test]
1331 fn noise_decoheres_the_signal() {
1332 let mut c = Circuit::new(2);
1335 c.x(0);
1336 for _ in 0..6 {
1337 c.cx(0, 1);
1338 c.cx(0, 1); }
1340 let e0 = noisy_expect_z(&c, 0, 0, 300, 1); let e1 = noisy_expect_z(&c, 0, 40, 500, 1); let e2 = noisy_expect_z(&c, 0, 150, 500, 1); assert!(approx(e0, -1.0, 1e-9), "no noise ⇒ exact: {e0}");
1344 assert!(e1 > e0 && e1 < 0.0, "light noise decoheres: {e1}");
1346 assert!(e2 > e1, "more noise ⇒ more decoherence: {e2} > {e1}");
1347 }
1348
1349 #[test]
1350 fn showcase_circuits_target_the_right_backends() {
1351 let ti = trotter_ising(6, 4);
1354 assert!(!is_clifford(&ti), "Trotter Ising should be non-Clifford");
1355 assert!(ti.simulate().is_ok());
1356 let rc = random_clifford(6, 60, 7);
1358 assert!(is_clifford(&rc), "random Clifford must be Clifford");
1359 assert_eq!(random_clifford(6, 60, 7), random_clifford(6, 60, 7), "deterministic in seed");
1360 }
1361}