1use crate::quantum::{circuit_hash, Amp, BaseGate, Circuit, Gate, QuantumError, StateVector, FRAC};
35
36fn mcz(c: &mut Circuit, n: u8) {
43 if n == 1 {
44 c.z(0);
45 return;
46 }
47 c.ops.push(Gate { base: BaseGate::Z, controls: (0..n - 1).collect(), target: n - 1, param: 0 });
48}
49
50pub fn bell() -> Circuit {
52 let mut c = Circuit::new(2);
53 c.h(0);
54 c.cx(0, 1);
55 c
56}
57
58pub fn ghz(n: u8) -> Circuit {
60 let mut c = Circuit::new(n.max(1));
61 c.h(0);
62 for i in 1..n {
63 c.cx(i - 1, i);
64 }
65 c
66}
67
68pub fn grover(n: u8, marked: u32) -> Circuit {
71 let n = n.clamp(1, 12);
72 let mut c = Circuit::new(n);
73 for q in 0..n {
74 c.h(q);
75 }
76 let dim = 1u64 << n;
77 let iters = ((std::f64::consts::PI / 4.0) * (dim as f64).sqrt()).floor() as u32;
78 for _ in 0..iters.max(1) {
79 for q in 0..n {
81 if (marked >> q) & 1 == 0 {
82 c.x(q);
83 }
84 }
85 mcz(&mut c, n);
86 for q in 0..n {
87 if (marked >> q) & 1 == 0 {
88 c.x(q);
89 }
90 }
91 for q in 0..n {
93 c.h(q);
94 }
95 for q in 0..n {
96 c.x(q);
97 }
98 mcz(&mut c, n);
99 for q in 0..n {
100 c.x(q);
101 }
102 for q in 0..n {
103 c.h(q);
104 }
105 }
106 c
107}
108
109pub fn bernstein_vazirani(secret: u32, nbits: u8) -> Circuit {
113 let nbits = nbits.clamp(1, 20);
114 let anc = nbits;
115 let mut c = Circuit::new(nbits + 1);
116 c.x(anc);
117 for q in 0..=nbits {
118 c.h(q);
119 }
120 for q in 0..nbits {
121 if (secret >> q) & 1 == 1 {
122 c.cx(q, anc);
123 }
124 }
125 for q in 0..nbits {
126 c.h(q);
127 }
128 c
129}
130
131pub fn deutsch_jozsa(balanced: bool, nbits: u8) -> Circuit {
135 let nbits = nbits.clamp(1, 20);
136 let anc = nbits;
137 let mut c = Circuit::new(nbits + 1);
138 c.x(anc);
139 for q in 0..=nbits {
140 c.h(q);
141 }
142 if balanced {
143 for q in 0..nbits {
144 c.cx(q, anc);
145 }
146 }
147 for q in 0..nbits {
148 c.h(q);
149 }
150 c
151}
152
153pub fn teleportation() -> Circuit {
157 let mut c = Circuit::new(3);
158 c.h(0);
160 c.t(0);
161 c.h(1);
163 c.cx(1, 2);
164 c.cx(0, 1);
166 c.h(0);
167 c.cx(1, 2);
169 c.cz(0, 2);
170 c
171}
172
173pub fn superdense(bits: u8) -> Circuit {
176 let mut c = Circuit::new(2);
177 c.h(0);
178 c.cx(0, 1);
179 if bits & 1 != 0 {
182 c.z(0);
183 }
184 if bits & 2 != 0 {
185 c.x(0);
186 }
187 c.cx(0, 1);
188 c.h(0);
189 c
190}
191
192fn splitmix(s: &mut u64) -> u64 {
193 *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
194 let mut z = *s;
195 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
196 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
197 z ^ (z >> 31)
198}
199
200pub fn trotter_ising(n: u8, steps: u8) -> Circuit {
205 let n = n.clamp(2, 12);
206 let k = 3u16; let mut c = Circuit::new(n);
208 for _ in 0..steps.clamp(1, 20) {
209 for q in 0..n - 1 {
210 c.cx(q, q + 1);
211 c.p(k, q + 1);
212 c.cx(q, q + 1);
213 }
214 for q in 0..n {
215 c.h(q);
216 c.p(k, q);
217 c.h(q);
218 }
219 }
220 c
221}
222
223pub fn random_clifford(n: u8, depth: u16, seed: u64) -> Circuit {
225 let n = n.clamp(1, 12);
226 let mut st = seed.wrapping_mul(0x2545_F491).wrapping_add(1);
227 let mut c = Circuit::new(n);
228 for _ in 0..depth {
229 match splitmix(&mut st) % 3 {
230 0 => {
231 c.h((splitmix(&mut st) % n as u64) as u8);
232 }
233 1 => {
234 c.s((splitmix(&mut st) % n as u64) as u8);
235 }
236 _ if n >= 2 => {
237 let a = (splitmix(&mut st) % n as u64) as u8;
238 let mut b = (splitmix(&mut st) % n as u64) as u8;
239 if b == a {
240 b = (b + 1) % n;
241 }
242 c.cx(a, b);
243 }
244 _ => {
245 c.h(0);
246 }
247 }
248 }
249 c
250}
251
252pub fn build_algorithm(id: &str, n: u8, param: u32) -> Option<Circuit> {
254 Some(match id {
255 "bell" => bell(),
256 "ghz" => ghz(n.clamp(2, 12)),
257 "qft" => Circuit::qft(n.clamp(1, 12)),
258 "grover" => grover(n.clamp(2, 10), param),
259 "bv" => bernstein_vazirani(param, n.clamp(1, 10)),
260 "dj" => deutsch_jozsa(param != 0, n.clamp(1, 10)),
261 "teleport" => teleportation(),
262 "superdense" => superdense((param & 3) as u8),
263 "trotter" => trotter_ising(n.clamp(2, 10), param.clamp(1, 12) as u8),
264 "rclifford" => random_clifford(n.clamp(2, 10), 40, param as u64),
265 _ => return None,
266 })
267}
268
269pub fn catalog() -> Vec<(&'static str, &'static str, &'static str)> {
271 vec![
272 ("bell", "Bell pair", "The canonical entangled pair (|00⟩+|11⟩)/√2 — entanglement in two gates."),
273 ("ghz", "GHZ state", "n-qubit maximal entanglement (|0…0⟩+|1…1⟩)/√2."),
274 ("qft", "Quantum Fourier Transform", "The quantum FFT — Hadamard + controlled-phase ladder."),
275 ("grover", "Grover search", "Amplitude amplification finds a marked item in √N steps."),
276 ("bv", "Bernstein–Vazirani", "Recover a hidden bitstring in one query — interference at work."),
277 ("dj", "Deutsch–Jozsa", "One query decides constant vs balanced."),
278 ("teleport", "Teleportation", "Move a state across a Bell pair via corrections."),
279 ("superdense", "Superdense coding", "Two classical bits down one qubit."),
280 ("trotter", "Trotter Ising", "Transverse-field Ising dynamics — the tensor-network / sparse-Pauli home turf."),
281 ("rclifford", "Random Clifford", "A random Clifford circuit — the stabilizer backend's home turf."),
282 ]
283}
284
285const SCALE: f64 = (1u64 << FRAC) as f64;
290
291#[inline]
292fn re(a: Amp) -> f64 {
293 a.re as f64 / SCALE
294}
295#[inline]
296fn im(a: Amp) -> f64 {
297 a.im as f64 / SCALE
298}
299
300fn total_weight(sv: &StateVector) -> f64 {
301 sv.prob_weights().iter().map(|&w| w as f64).sum::<f64>()
302}
303
304pub fn probabilities(sv: &StateVector) -> Vec<f64> {
306 let t = total_weight(sv);
307 if t <= 0.0 {
308 return vec![0.0; sv.amps.len()];
309 }
310 sv.prob_weights().iter().map(|&w| w as f64 / t).collect()
311}
312
313pub fn expect_z(sv: &StateVector, q: u8) -> f64 {
315 let bit = 1usize << q;
316 let t = total_weight(sv);
317 if t <= 0.0 {
318 return 0.0;
319 }
320 let mut acc = 0.0;
321 for (i, w) in sv.prob_weights().iter().enumerate() {
322 let s = if i & bit == 0 { 1.0 } else { -1.0 };
323 acc += s * (*w as f64);
324 }
325 acc / t
326}
327
328pub fn expect_xy(sv: &StateVector, q: u8) -> (f64, f64) {
330 let bit = 1usize << q;
331 let t = total_weight(sv);
332 if t <= 0.0 {
333 return (0.0, 0.0);
334 }
335 let mut ex = 0.0;
336 let mut ey = 0.0;
337 for i in 0..sv.amps.len() {
338 if i & bit == 0 {
339 let a = sv.amps[i];
340 let b = sv.amps[i | bit];
341 ex += 2.0 * (re(a) * re(b) + im(a) * im(b));
343 ey += 2.0 * (re(a) * im(b) - im(a) * re(b));
344 }
345 }
346 (ex * SCALE * SCALE / t, ey * SCALE * SCALE / t)
347}
348
349pub fn bloch(sv: &StateVector, q: u8) -> [f64; 3] {
351 let (x, y) = expect_xy(sv, q);
352 [x, y, expect_z(sv, q)]
353}
354
355pub fn entanglement_entropy(sv: &StateVector, q: u8) -> f64 {
359 let b = bloch(sv, q);
360 let r = (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]).sqrt().min(1.0);
361 let lam = (1.0 + r) / 2.0;
362 binary_entropy(lam)
363}
364
365pub fn purity(sv: &StateVector, q: u8) -> f64 {
367 let b = bloch(sv, q);
368 let r2 = (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]).min(1.0);
369 (1.0 + r2) / 2.0
370}
371
372fn binary_entropy(p: f64) -> f64 {
373 if p <= 0.0 || p >= 1.0 {
374 return 0.0;
375 }
376 -p * p.log2() - (1.0 - p) * (1.0 - p).log2()
377}
378
379pub fn top_outcomes(sv: &StateVector, top: usize) -> Vec<(usize, f64)> {
381 let mut v: Vec<(usize, f64)> = probabilities(sv).into_iter().enumerate().collect();
382 v.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
383 v.truncate(top);
384 v
385}
386
387pub fn noisy_expect_z(
398 circuit: &Circuit,
399 q: u8,
400 depol_permille: u32,
401 trajectories: u32,
402 seed: u64,
403) -> f64 {
404 let mut st = seed.wrapping_mul(0x9E37_79B9).wrapping_add(1);
405 let mut acc = 0.0;
406 let traj = trajectories.max(1);
407 for _ in 0..traj {
408 let mut noisy = circuit.empty_like();
409 for g in &circuit.ops {
410 noisy.ops.push(g.clone());
411 for &qb in g.controls.iter().chain(std::iter::once(&g.target)) {
412 if (splitmix(&mut st) % 1000) < depol_permille as u64 {
413 let base = match splitmix(&mut st) % 3 {
414 0 => BaseGate::X,
415 1 => BaseGate::Y,
416 _ => BaseGate::Z,
417 };
418 noisy.ops.push(Gate { base, controls: vec![], target: qb, param: 0 });
419 }
420 }
421 }
422 if let Ok(sv) = noisy.simulate() {
423 acc += expect_z(&sv, q);
424 }
425 }
426 acc / traj as f64
427}
428
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
434pub enum Backend {
435 Stabilizer,
437 Statevector,
439 TensorNetwork,
442}
443
444pub fn is_clifford(c: &Circuit) -> bool {
447 c.ops.iter().all(|g| match g.controls.len() {
448 0 => matches!(
449 g.base,
450 BaseGate::I | BaseGate::X | BaseGate::Y | BaseGate::Z | BaseGate::H | BaseGate::S | BaseGate::Sdg
451 ) || (g.base == BaseGate::P && (g.param == 1 || g.param == 2)),
452 1 => matches!(g.base, BaseGate::X | BaseGate::Y | BaseGate::Z)
453 || (g.base == BaseGate::P && g.param == 1),
454 _ => false,
455 })
456}
457
458pub fn recommend_backend(c: &Circuit) -> (Backend, &'static str) {
461 if is_clifford(c) {
462 (Backend::Stabilizer, "all-Clifford → stabilizer tableau, O(n²); scales far past statevector")
463 } else if c.n_qubits <= 22 {
464 (Backend::Statevector, "non-Clifford, ≤22 qubits → exact dense statevector")
465 } else {
466 (Backend::TensorNetwork, "non-Clifford, >22 qubits → tensor-network contraction (the referee)")
467 }
468}
469
470pub fn statevector_work(c: &Circuit) -> u64 {
473 (c.ops.len() as u64).saturating_mul(1u64 << c.n_qubits.min(63))
474}
475
476pub struct Routed {
482 pub circuit: Circuit,
483 pub final_map: Vec<u8>,
485 pub swaps: u32,
486 pub source_2q: u32,
487 pub source_nonlocal: u32,
488}
489
490pub fn route_line(src: &Circuit) -> Routed {
495 let n = src.n_qubits;
496 let mut phys: Vec<u8> = (0..n).collect();
497 let mut logq: Vec<u8> = (0..n).collect();
498 let mut out = src.empty_like();
499 let mut swaps = 0u32;
500 let (mut s2q, mut nonlocal) = (0u32, 0u32);
501 let do_swap = |out: &mut Circuit, phys: &mut [u8], logq: &mut [u8], a: u8, b: u8| {
502 out.swap(a, b);
503 let (la, lb) = (logq[a as usize], logq[b as usize]);
504 logq.swap(a as usize, b as usize);
505 phys[la as usize] = b;
506 phys[lb as usize] = a;
507 };
508 for g in &src.ops {
509 match g.controls.len() {
510 0 => {
511 out.ops.push(Gate {
512 base: g.base,
513 controls: vec![],
514 target: phys[g.target as usize],
515 param: g.param,
516 });
517 }
518 1 => {
519 s2q += 1;
520 let (la, lb) = (g.controls[0], g.target);
521 let mut pa = phys[la as usize];
522 let pb = phys[lb as usize];
523 if (pa as i32 - pb as i32).abs() > 1 {
524 nonlocal += 1;
525 while (pa as i32 - pb as i32).abs() > 1 {
527 let step = if pa < pb { pa + 1 } else { pa - 1 };
528 do_swap(&mut out, &mut phys, &mut logq, pa, step);
529 swaps += 1;
530 pa = step;
531 }
532 }
533 out.ops.push(Gate {
534 base: g.base,
535 controls: vec![phys[la as usize]],
536 target: phys[lb as usize],
537 param: g.param,
538 });
539 }
540 _ => {
541 s2q += 1;
542 out.ops.push(Gate {
543 base: g.base,
544 controls: g.controls.iter().map(|&c| phys[c as usize]).collect(),
545 target: phys[g.target as usize],
546 param: g.param,
547 });
548 }
549 }
550 }
551 Routed { circuit: out, final_map: phys, swaps, source_2q: s2q, source_nonlocal: nonlocal }
552}
553
554fn restore_swaps(final_map: &[u8], n: u8) -> Vec<(u8, u8)> {
556 let mut logq = vec![0u8; n as usize];
557 for (l, &p) in final_map.iter().enumerate() {
558 logq[p as usize] = l as u8;
559 }
560 let mut phys: Vec<u8> = final_map.to_vec();
561 let mut out = Vec::new();
562 for p in 0..n as usize {
563 while logq[p] != p as u8 {
564 let cur = phys[p] as usize; out.push((p as u8, cur as u8));
566 let (lp, lc) = (logq[p], logq[cur]);
567 logq[p] = lc;
568 logq[cur] = lp;
569 phys[lp as usize] = cur as u8;
570 phys[lc as usize] = p as u8;
571 }
572 }
573 out
574}
575
576pub fn routing_equivalent(src: &Circuit, routed: &Routed) -> bool {
579 let mut full = routed.circuit.clone();
580 for (a, b) in restore_swaps(&routed.final_map, src.n_qubits) {
581 full.swap(a, b);
582 }
583 match (full.simulate(), src.simulate()) {
584 (Ok(a), Ok(b)) => a.statevector_hash() == b.statevector_hash(),
585 _ => false,
586 }
587}
588
589pub fn to_qasm(c: &Circuit) -> String {
601 let mut s = String::from("OPENQASM 3.0;\ninclude \"stdgates.inc\";\n");
602 s.push_str(&format!("qubit[{}] q;\n", c.n_qubits));
603 for g in &c.ops {
604 let t = g.target;
605 let line = match (g.base, g.controls.as_slice()) {
606 (BaseGate::I, []) => continue,
607 (BaseGate::X, []) => format!("x q[{t}];"),
608 (BaseGate::Y, []) => format!("y q[{t}];"),
609 (BaseGate::Z, []) => format!("z q[{t}];"),
610 (BaseGate::H, []) => format!("h q[{t}];"),
611 (BaseGate::S, []) => format!("s q[{t}];"),
612 (BaseGate::Sdg, []) => format!("sdg q[{t}];"),
613 (BaseGate::T, []) => format!("t q[{t}];"),
614 (BaseGate::Tdg, []) => format!("tdg q[{t}];"),
615 (BaseGate::P, []) => format!("p(2*pi/{}) q[{t}];", 1u64 << g.param),
616 (BaseGate::X, [c0]) => format!("cx q[{c0}],q[{t}];"),
617 (BaseGate::Y, [c0]) => format!("cy q[{c0}],q[{t}];"),
618 (BaseGate::Z, [c0]) => format!("cz q[{c0}],q[{t}];"),
619 (BaseGate::P, [c0]) => format!("cp(2*pi/{}) q[{c0}],q[{t}];", 1u64 << g.param),
620 (BaseGate::X, [a, b]) => format!("ccx q[{a}],q[{b}],q[{t}];"),
621 (BaseGate::Z, [a, b]) => format!("ccz q[{a}],q[{b}],q[{t}];"),
622 (base, ctrls) => format!("// unmapped: {base:?} controls={ctrls:?} target={t}"),
623 };
624 s.push_str(&line);
625 s.push('\n');
626 }
627 s
628}
629
630pub fn from_qasm(src: &str) -> Result<Circuit, String> {
660 let mut n: Option<u8> = None;
661 for raw in src.lines() {
663 let line = strip(raw);
664 if let Some(rest) = line.strip_prefix("qubit[")
665 && let Some(end) = rest.find(']')
666 {
667 n = rest[..end].trim().parse().ok();
668 } else if let Some(rest) = line.strip_prefix("qreg ")
669 && let (Some(a), Some(b)) = (rest.find('['), rest.find(']'))
670 {
671 n = rest[a + 1..b].trim().parse().ok();
672 }
673 }
674 let n = n.ok_or("no qubit register declaration (qubit[n] q; or qreg q[n];)")?;
675 let mut c = Circuit::new(n);
676 for raw in src.lines() {
677 let line = strip(raw);
678 if line.is_empty() {
679 continue;
680 }
681 if line.contains("measure") {
687 continue;
688 }
689 let head: String = line.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect();
690 let head = head.as_str();
691 match head {
692 "OPENQASM" | "include" | "qubit" | "qreg" | "creg" | "bit" | "measure" | "barrier"
693 | "reset" | "gate" => continue,
694 _ => {}
695 }
696 let qs = parse_qubits(&line);
697 let push = |c: &mut Circuit, base: BaseGate, controls: Vec<u8>, target: u8, param: u16| -> Result<(), String> {
698 distinct_qubits(&controls, target)?;
699 c.ops.push(Gate { base, controls, target, param });
700 Ok(())
701 };
702 match head {
703 "id" | "i" => {}
704 "x" => require1(&qs).and_then(|t| push(&mut c, BaseGate::X, vec![], t, 0))?,
705 "y" => require1(&qs).and_then(|t| push(&mut c, BaseGate::Y, vec![], t, 0))?,
706 "z" => require1(&qs).and_then(|t| push(&mut c, BaseGate::Z, vec![], t, 0))?,
707 "h" => require1(&qs).and_then(|t| push(&mut c, BaseGate::H, vec![], t, 0))?,
708 "s" => require1(&qs).and_then(|t| push(&mut c, BaseGate::S, vec![], t, 0))?,
709 "sdg" => require1(&qs).and_then(|t| push(&mut c, BaseGate::Sdg, vec![], t, 0))?,
710 "t" => require1(&qs).and_then(|t| push(&mut c, BaseGate::T, vec![], t, 0))?,
711 "tdg" => require1(&qs).and_then(|t| push(&mut c, BaseGate::Tdg, vec![], t, 0))?,
712 "cx" | "cnot" => require2(&qs).and_then(|(a, t)| push(&mut c, BaseGate::X, vec![a], t, 0))?,
713 "cy" => require2(&qs).and_then(|(a, t)| push(&mut c, BaseGate::Y, vec![a], t, 0))?,
714 "cz" => require2(&qs).and_then(|(a, t)| push(&mut c, BaseGate::Z, vec![a], t, 0))?,
715 "swap" => require2(&qs).and_then(|(a, b)| {
716 distinct_qubits(&[a], b)?;
717 c.swap(a, b);
718 Ok(())
719 })?,
720 "ccx" | "toffoli" => require3(&qs).and_then(|(a, b, t)| push(&mut c, BaseGate::X, vec![a, b], t, 0))?,
721 "ccz" => require3(&qs).and_then(|(a, b, t)| push(&mut c, BaseGate::Z, vec![a, b], t, 0))?,
722 "p" | "u1" | "phase" => {
723 let t = parse_angle(&arg_n(&line, 0)?)?;
724 let q = require1(&qs)?;
725 push_turn(&mut c, &[], q, t)?;
726 }
727 "cp" | "cu1" | "cphase" => {
728 let t = parse_angle(&arg_n(&line, 0)?)?;
729 let (a, q) = require2(&qs)?;
730 push_turn(&mut c, &[a], q, t)?;
731 }
732 "rz" => {
737 let t = parse_angle(&arg_n(&line, 0)?)?;
738 let q = require1(&qs)?;
739 push_turn(&mut c, &[], q, t)?;
740 }
741 "rx" => {
742 let t = parse_angle(&arg_n(&line, 0)?)?;
743 let q = require1(&qs)?;
744 push_rx(&mut c, q, t)?;
745 }
746 "ry" => {
747 let t = parse_angle(&arg_n(&line, 0)?)?;
748 let q = require1(&qs)?;
749 push_ry(&mut c, q, t)?;
750 }
751 "sx" | "sxdg" => {
753 let q = require1(&qs)?;
754 let t = if head == "sx" {
755 Turn { m: 1, k: 2 }
756 } else {
757 Turn { m: 3, k: 2 }
758 };
759 push_rx(&mut c, q, t)?;
760 }
761 "u" | "u3" | "u2" => {
763 let (theta, phi, lam) = if head == "u2" {
764 (Turn { m: 1, k: 2 }, parse_angle(&arg_n(&line, 0)?)?, parse_angle(&arg_n(&line, 1)?)?)
766 } else {
767 (
768 parse_angle(&arg_n(&line, 0)?)?,
769 parse_angle(&arg_n(&line, 1)?)?,
770 parse_angle(&arg_n(&line, 2)?)?,
771 )
772 };
773 let q = require1(&qs)?;
774 push_turn(&mut c, &[], q, lam)?;
775 push_ry(&mut c, q, theta)?;
776 push_turn(&mut c, &[], q, phi)?;
777 }
778 "crz" => {
781 let (p, den) = angle_turns(&arg_n(&line, 0)?)?;
784 if den * 2 > (1i128 << crate::quantum::DYADIC_MAX) {
785 return Err("crz angle is finer than the dyadic floor".into());
786 }
787 let (a, q) = require2(&qs)?;
788 distinct_qubits(&[a], q)?;
789 push_turn(&mut c, &[], a, to_turn(-p, den * 2))?;
790 push_turn(&mut c, &[a], q, to_turn(p, den))?;
791 }
792 other => {
793 return Err(format!(
794 "unsupported gate: '{other}' (supported: id x y z h s sdg t tdg sx sxdg \
795 rx ry rz p/u1/phase u/u2/u3 cx cy cz cp/cu1 crz swap ccx ccz)"
796 ));
797 }
798 }
799 }
800 c.validate().map_err(|e| e.to_string())?;
801 Ok(c)
802}
803
804fn distinct_qubits(controls: &[u8], target: u8) -> Result<(), String> {
807 for (i, &q) in controls.iter().enumerate() {
808 if q == target {
809 return Err(QuantumError::Invalid(format!("control {q} equals target")).to_string());
810 }
811 if controls[..i].contains(&q) {
812 return Err(QuantumError::Invalid(format!("control {q} repeated")).to_string());
813 }
814 }
815 Ok(())
816}
817
818fn strip(line: &str) -> String {
819 let no_comment = line.split("//").next().unwrap_or("");
820 no_comment.trim().trim_end_matches(';').trim().to_string()
821}
822
823fn parse_qubits(line: &str) -> Vec<u8> {
824 let mut out = Vec::new();
825 let bytes = line.as_bytes();
826 let mut i = 0;
827 while i < bytes.len() {
828 if bytes[i] == b'[' {
829 let mut j = i + 1;
830 while j < bytes.len() && bytes[j] != b']' {
831 j += 1;
832 }
833 if let Ok(v) = line[i + 1..j].trim().parse::<u8>() {
834 out.push(v);
835 }
836 i = j + 1;
837 } else {
838 i += 1;
839 }
840 }
841 out
842}
843
844fn require1(qs: &[u8]) -> Result<u8, String> {
845 qs.first().copied().ok_or_else(|| "expected 1 qubit".into())
846}
847fn require2(qs: &[u8]) -> Result<(u8, u8), String> {
848 if qs.len() >= 2 {
849 Ok((qs[0], qs[1]))
850 } else {
851 Err("expected 2 qubits".into())
852 }
853}
854fn require3(qs: &[u8]) -> Result<(u8, u8, u8), String> {
855 if qs.len() >= 3 {
856 Ok((qs[0], qs[1], qs[2]))
857 } else {
858 Err("expected 3 qubits".into())
859 }
860}
861
862#[derive(Clone, Copy, Debug, PartialEq, Eq)]
873struct Turn {
874 m: u64,
875 k: u32,
876}
877
878fn decimal_ratio(tok: &str) -> Option<(i128, i128)> {
881 if tok.is_empty() {
882 return None;
883 }
884 match tok.split_once('.') {
885 None => tok.parse::<i128>().ok().map(|v| (v, 1)),
886 Some((int, frac)) => {
887 if frac.is_empty() || !frac.bytes().all(|b| b.is_ascii_digit()) {
888 return None;
889 }
890 let scale = 10i128.checked_pow(frac.len() as u32)?;
891 let whole: i128 = if int.is_empty() { 0 } else { int.parse().ok()? };
892 let f: i128 = frac.parse().ok()?;
893 Some((whole * scale + f, scale))
894 }
895 }
896}
897
898fn to_turn(p: i128, q: i128) -> Turn {
913 let k = q.trailing_zeros();
914 let m = ((p % q) + q) % q;
915 Turn { m: m as u64, k }
916}
917
918fn angle_turns(arg: &str) -> Result<(i128, i128), String> {
926 let s = arg.replace(' ', "").to_lowercase();
927 if s.is_empty() {
928 return Err("empty angle".into());
929 }
930 let (sign, body) = match s.strip_prefix('-') {
931 Some(r) => (-1i128, r),
932 None => (1i128, s.strip_prefix('+').unwrap_or(&s)),
933 };
934
935 let mut parts: Vec<(char, String)> = Vec::new();
937 let (mut cur, mut op) = (String::new(), '*');
938 for ch in body.chars() {
939 if ch == '*' || ch == '/' {
940 parts.push((op, std::mem::take(&mut cur)));
941 op = ch;
942 } else {
943 cur.push(ch);
944 }
945 }
946 parts.push((op, cur));
947
948 let (mut num, mut den, mut pi_pow) = (sign, 1i128, 0i32);
949 for (op, tok) in &parts {
950 if tok == "pi" {
951 pi_pow += if *op == '*' { 1 } else { -1 };
952 continue;
953 }
954 let (a, b) = decimal_ratio(tok)
955 .ok_or_else(|| format!("cannot read '{tok}' in angle '{arg}'"))?;
956 if a == 0 && *op == '/' {
957 return Err("division by zero in angle".into());
958 }
959 if *op == '*' {
960 num *= a;
961 den *= b;
962 } else {
963 num *= b;
964 den *= a;
965 }
966 }
967
968 if num == 0 {
969 return Ok((0, 2));
970 }
971 if pi_pow == 0 {
972 let v = (num as f64 / den as f64) / std::f64::consts::TAU;
974 for k in 1..=crate::quantum::DYADIC_MAX as u32 {
975 let scaled = v * (1u64 << k) as f64;
976 let r = scaled.round();
977 if (scaled - r).abs() < 1e-9 {
978 return Ok((r as i128, 1i128 << k));
979 }
980 }
981 return Err(format!(
982 "angle '{arg}' is not a dyadic multiple of 2π; this simulator is exact, \
983 so it refuses angles it cannot represent rather than rounding them"
984 ));
985 }
986 if pi_pow != 1 {
987 return Err(format!("angle '{arg}' is not linear in pi"));
988 }
989
990 let mut p = num;
992 let mut q = den * 2;
993 if q < 0 {
994 p = -p;
995 q = -q;
996 }
997 let g = {
998 let (mut a, mut b) = (p.unsigned_abs(), q.unsigned_abs());
999 while b != 0 {
1000 let t = a % b;
1001 a = b;
1002 b = t;
1003 }
1004 a.max(1) as i128
1005 };
1006 p /= g;
1007 q /= g;
1008 if q <= 0 || (q as u128).count_ones() != 1 {
1009 return Err(format!(
1010 "angle '{arg}' is not a dyadic multiple of 2π (reduced denominator {q}); \
1011 this simulator is exact, so it refuses angles it cannot represent"
1012 ));
1013 }
1014 let k = q.trailing_zeros();
1015 if k > crate::quantum::DYADIC_MAX as u32 {
1016 return Err(format!("angle '{arg}' is finer than the dyadic floor (k={k})"));
1017 }
1018 Ok((p, q))
1019}
1020
1021fn parse_angle(arg: &str) -> Result<Turn, String> {
1029 let (p, q) = angle_turns(arg)?;
1030 Ok(to_turn(p, q))
1031}
1032
1033fn arg_n(line: &str, i: usize) -> Result<String, String> {
1035 let a = line.find('(').ok_or("gate needs an argument")?;
1036 let b = line[a..].find(')').ok_or("unterminated argument")? + a;
1037 line[a + 1..b]
1038 .split(',')
1039 .nth(i)
1040 .map(|s| s.trim().to_string())
1041 .ok_or_else(|| format!("expected at least {} argument(s)", i + 1))
1042}
1043
1044fn push_turn(c: &mut Circuit, controls: &[u8], target: u8, t: Turn) -> Result<(), String> {
1048 distinct_qubits(controls, target)?;
1049 for i in 0..=t.k {
1050 if (t.m >> i) & 1 == 1 {
1051 let k = t.k - i;
1052 if k >= 1 {
1054 c.ops.push(Gate {
1055 base: BaseGate::P,
1056 controls: controls.to_vec(),
1057 target,
1058 param: k as u16,
1059 });
1060 }
1061 }
1062 }
1063 Ok(())
1064}
1065
1066fn push_rx(c: &mut Circuit, target: u8, t: Turn) -> Result<(), String> {
1068 c.h(target);
1069 push_turn(c, &[], target, t)?;
1070 c.h(target);
1071 Ok(())
1072}
1073
1074fn push_ry(c: &mut Circuit, target: u8, t: Turn) -> Result<(), String> {
1076 let one = |b: BaseGate| Gate { base: b, controls: vec![], target, param: 0 };
1077 c.ops.push(one(BaseGate::Sdg));
1078 push_rx(c, target, t)?;
1079 c.ops.push(one(BaseGate::S));
1080 Ok(())
1081}
1082
1083pub fn circuit_id(c: &Circuit) -> Result<[u8; 32], QuantumError> {
1087 circuit_hash(c)
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092 #[test]
1093 #[ignore]
1094 fn probe_sv_perf() {
1095 use std::time::Instant;
1096 use crate::quantum::Circuit;
1097 println!("\n statevector: time per gate vs width");
1098 for n in [8u8, 12, 16, 18, 20, 22] {
1099 let mut c = Circuit::new(n);
1100 for _ in 0..3 { for q in 0..n { c.h(q); } for q in 0..n - 1 { c.cx(q, q + 1); } }
1101 let gates = c.ops.len();
1102 let t0 = Instant::now();
1103 let sv = c.simulate().unwrap();
1104 let el = t0.elapsed();
1105 println!(" n={n:<3} dim={:<9} {gates:>4} gates {:>9.2?} total {:>8.1} ns/gate {:>7.2} ns/amp-gate",
1106 1usize << n, el, el.as_nanos() as f64 / gates as f64,
1107 el.as_nanos() as f64 / (gates as f64 * (1usize << n) as f64));
1108 std::hint::black_box(sv.statevector_hash());
1109 }
1110 println!("\n gate-kind cost at n=20 (200 gates each)");
1111 for kind in ["h", "t", "z", "cx", "cz"] {
1112 let n = 20u8;
1113 let mut c = Circuit::new(n);
1114 for i in 0..200 {
1115 let q = (i % n as usize) as u8;
1116 match kind {
1117 "h" => { c.h(q); }
1118 "t" => { c.t(q); }
1119 "z" => { c.z(q); }
1120 "cx" => { c.cx(q, (q + 1) % n); }
1121 _ => { c.cz(q, (q + 1) % n); }
1122 }
1123 }
1124 let t0 = Instant::now();
1125 let sv = c.simulate().unwrap();
1126 let el = t0.elapsed();
1127 println!(" {kind:<3} {:>8.1} ns/gate", el.as_nanos() as f64 / 200.0);
1128 std::hint::black_box(sv.statevector_hash());
1129 }
1130 }
1131
1132 use super::*;
1133
1134 fn approx(a: f64, b: f64, tol: f64) -> bool {
1135 (a - b).abs() < tol
1136 }
1137
1138 #[test]
1139 fn bell_is_maximally_entangled() {
1140 let sv = bell().simulate().unwrap();
1141 let p = probabilities(&sv);
1142 assert!(approx(p[0b00], 0.5, 1e-4) && approx(p[0b11], 0.5, 1e-4));
1143 assert!(approx(p[0b01], 0.0, 1e-4) && approx(p[0b10], 0.0, 1e-4));
1144 assert!(approx(entanglement_entropy(&sv, 0), 1.0, 1e-3), "S={}", entanglement_entropy(&sv, 0));
1146 assert!(approx(purity(&sv, 0), 0.5, 1e-3));
1147 }
1148
1149 #[test]
1150 fn ghz_all_or_nothing() {
1151 let sv = ghz(4).simulate().unwrap();
1152 let p = probabilities(&sv);
1153 assert!(approx(p[0], 0.5, 1e-4) && approx(p[15], 0.5, 1e-4));
1154 assert!(approx(entanglement_entropy(&sv, 2), 1.0, 1e-3));
1155 }
1156
1157 #[test]
1158 fn plus_state_bloch() {
1159 let mut c = Circuit::new(1);
1161 c.h(0);
1162 let sv = c.simulate().unwrap();
1163 let b = bloch(&sv, 0);
1164 assert!(approx(b[0], 1.0, 1e-3) && approx(b[1], 0.0, 1e-3) && approx(b[2], 0.0, 1e-3), "bloch={b:?}");
1165 assert!(approx(entanglement_entropy(&sv, 0), 0.0, 1e-3));
1166 }
1167
1168 #[test]
1169 fn grover_amplifies_the_marked_state() {
1170 for &(n, marked) in &[(3u8, 5u32), (4, 11), (5, 20)] {
1171 let sv = grover(n, marked).simulate().unwrap();
1172 let top = top_outcomes(&sv, 1)[0];
1173 assert_eq!(top.0, marked as usize, "grover n={n} should peak at {marked}");
1174 assert!(top.1 > 0.6, "marked prob {} too low", top.1);
1175 }
1176 }
1177
1178 #[test]
1179 fn bernstein_vazirani_recovers_secret() {
1180 let secret = 0b10110u32;
1181 let sv = bernstein_vazirani(secret, 5).simulate().unwrap();
1182 let top = top_outcomes(&sv, 1)[0];
1184 assert_eq!(top.0 & 0b11111, secret as usize, "BV should reveal the secret");
1185 }
1186
1187 #[test]
1188 fn deutsch_jozsa_distinguishes() {
1189 let sv_c = deutsch_jozsa(false, 4).simulate().unwrap();
1190 let sv_b = deutsch_jozsa(true, 4).simulate().unwrap();
1191 let inputs = |i: usize| i & 0b1111;
1193 assert_eq!(inputs(top_outcomes(&sv_c, 1)[0].0), 0, "constant → all-zero inputs");
1194 assert_ne!(inputs(top_outcomes(&sv_b, 1)[0].0), 0, "balanced → nonzero inputs");
1195 }
1196
1197 #[test]
1198 fn superdense_roundtrips_all_four() {
1199 for bits in 0u8..4 {
1200 let sv = superdense(bits).simulate().unwrap();
1201 let top = top_outcomes(&sv, 1)[0];
1202 assert_eq!(top.0, bits as usize, "superdense {bits} decoded wrong");
1203 assert!(top.1 > 0.99);
1204 }
1205 }
1206
1207 #[test]
1208 fn clifford_detection() {
1209 assert!(is_clifford(&bell()));
1210 assert!(is_clifford(&ghz(5)));
1211 assert!(!is_clifford(&teleportation())); assert!(!is_clifford(&Circuit::qft(4))); assert_eq!(recommend_backend(&bell()).0, Backend::Stabilizer);
1214 assert_eq!(recommend_backend(&Circuit::qft(6)).0, Backend::Statevector);
1215 }
1216
1217 #[test]
1218 fn qasm_round_trip() {
1219 for c in [bell(), ghz(3), Circuit::qft(4), grover(3, 5)] {
1220 let qasm = to_qasm(&c);
1221 let back = from_qasm(&qasm).expect("reparse");
1222 assert_eq!(
1224 c.simulate().unwrap().statevector_hash(),
1225 back.simulate().unwrap().statevector_hash(),
1226 "QASM round-trip changed the state"
1227 );
1228 }
1229 }
1230
1231 #[test]
1232 fn qasm_imports_foreign_circuit() {
1233 let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\n";
1234 let c = from_qasm(src).unwrap();
1235 assert_eq!(c.simulate().unwrap().statevector_hash(), bell().simulate().unwrap().statevector_hash());
1236 }
1237
1238 fn same_state(a: &str, b: &Circuit) {
1242 let got = from_qasm(a).unwrap_or_else(|e| panic!("parse {a:?}: {e}"));
1243 let f = got.simulate().unwrap().fidelity_fx(&b.simulate().unwrap());
1244 assert!(
1245 (f - crate::quantum::ONE).abs() < 1 << 12,
1246 "{a:?} gave fidelity {f} against the reference, want 1"
1247 );
1248 }
1249
1250 #[test]
1255 fn qasm3_assignment_measure_parses() {
1256 let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\n\
1257 h q[0];\ncx q[0], q[1];\nc[0] = measure q[0];\nc[1] = measure q[1];\n";
1258 same_state(src, &bell());
1259 }
1260
1261 #[test]
1264 fn rotations_decompose_to_the_dyadic_basis() {
1265 let mut x = Circuit::new(1);
1266 x.x(0);
1267 let mut y = Circuit::new(1);
1268 y.y(0);
1269 let mut z = Circuit::new(1);
1270 z.z(0);
1271 let mut t = Circuit::new(1);
1272 t.t(0);
1273
1274 same_state("qreg q[1];\nrx(pi) q[0];", &x);
1275 same_state("qreg q[1];\nry(pi) q[0];", &y);
1276 same_state("qreg q[1];\nrz(pi) q[0];", &z);
1277 same_state("qreg q[1];\np(pi/4) q[0];", &t);
1278 same_state("qreg q[1];\nu3(pi,0,pi) q[0];", &x);
1280 same_state("qreg q[1];\nu(pi,0,pi) q[0];", &x);
1281 same_state("qreg q[1];\nsx q[0];\nsx q[0];", &x);
1283 }
1284
1285 #[test]
1289 fn a_general_dyadic_angle_splits_across_gates() {
1290 let mut want = Circuit::new(1);
1291 want.h(0).p(3, 0).p(2, 0);
1292 let got = from_qasm("qreg q[1];\nh q[0];\np(3*pi/4) q[0];").unwrap();
1293 assert_eq!(
1294 got.simulate().unwrap().statevector_hash(),
1295 want.simulate().unwrap().statevector_hash()
1296 );
1297 assert_eq!(got.ops.len(), 3, "3π/4 should split into two P gates after the H");
1298 }
1299
1300 #[test]
1303 fn negative_and_zero_angles_are_exact() {
1304 let plain = Circuit::new(1);
1305 same_state("qreg q[1];\nrz(0) q[0];", &plain);
1306 same_state("qreg q[1];\np(0) q[0];", &plain);
1307 same_state("qreg q[1];\nrz(pi/2) q[0];\nrz(-pi/2) q[0];", &plain);
1308 same_state("qreg q[1];\nsx q[0];\nsxdg q[0];", &plain);
1309 let mut s = Circuit::new(1);
1311 s.s(0);
1312 same_state("qreg q[1];\np(pi*0.5) q[0];", &s);
1313 same_state("qreg q[1];\np(0.5*pi) q[0];", &s);
1314 }
1315
1316 #[test]
1319 fn controlled_rz_is_exact() {
1320 let mut hh = Circuit::new(2);
1324 hh.h(0).h(1);
1325 same_state(
1326 "qreg q[2];\nh q[0];\nh q[1];\ncrz(pi/2) q[0],q[1];\ncrz(-pi/2) q[0],q[1];",
1327 &hh,
1328 );
1329 let crz = from_qasm("qreg q[2];\nh q[0];\nh q[1];\ncrz(pi/2) q[0],q[1];").unwrap();
1330 let mut cp = Circuit::new(2);
1331 cp.h(0).h(1).cp(2, 0, 1);
1332 assert_ne!(
1333 crz.simulate().unwrap().statevector_hash(),
1334 cp.simulate().unwrap().statevector_hash(),
1335 "crz must not silently be cp — they differ by a phase on the control"
1336 );
1337 }
1338
1339 #[test]
1343 fn non_dyadic_angles_are_refused_not_rounded() {
1344 for src in ["qreg q[1];\nrz(pi/3) q[0];", "qreg q[1];\np(pi/5) q[0];"] {
1345 let e = from_qasm(src).expect_err("a non-dyadic angle must not be accepted");
1346 assert!(e.contains("dyadic"), "error should say why: {e}");
1347 }
1348 }
1349
1350 #[test]
1351 fn catalog_all_build_and_simulate() {
1352 for (id, _, _) in catalog() {
1353 let c = build_algorithm(id, 4, 5).expect(id);
1354 assert!(c.simulate().is_ok(), "{id} failed to simulate");
1355 }
1356 }
1357
1358 #[test]
1359 fn routing_preserves_the_state() {
1360 for c in [bell(), ghz(5), Circuit::qft(5), trotter_ising(6, 3)] {
1362 let r = route_line(&c);
1363 assert!(routing_equivalent(&c, &r), "routed circuit must reproduce the state");
1364 for g in &r.circuit.ops {
1366 if g.controls.len() == 1 {
1367 let d = (g.controls[0] as i32 - g.target as i32).abs();
1368 assert_eq!(d, 1, "routed 2q gate must be local: {} {}", g.controls[0], g.target);
1369 }
1370 }
1371 }
1372 }
1373
1374 #[test]
1375 fn noise_decoheres_the_signal() {
1376 let mut c = Circuit::new(2);
1379 c.x(0);
1380 for _ in 0..6 {
1381 c.cx(0, 1);
1382 c.cx(0, 1); }
1384 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}");
1388 assert!(e1 > e0 && e1 < 0.0, "light noise decoheres: {e1}");
1390 assert!(e2 > e1, "more noise ⇒ more decoherence: {e2} > {e1}");
1391 }
1392
1393 #[test]
1394 fn showcase_circuits_target_the_right_backends() {
1395 let ti = trotter_ising(6, 4);
1398 assert!(!is_clifford(&ti), "Trotter Ising should be non-Clifford");
1399 assert!(ti.simulate().is_ok());
1400 let rc = random_clifford(6, 60, 7);
1402 assert!(is_clifford(&rc), "random Clifford must be Clifford");
1403 assert_eq!(random_clifford(6, 60, 7), random_clifford(6, 60, 7), "deterministic in seed");
1404 }
1405
1406 #[test]
1409 fn derived_circuits_keep_their_gateset() {
1410 use crate::quantum::Gateset;
1411 let htz = |gs| {
1414 let mut c = Circuit::with_gateset(1, gs);
1415 c.h(0).t(0).h(0);
1416 c
1417 };
1418 let exact = |gs| expect_z(&htz(gs).simulate().unwrap(), 0);
1419 assert_ne!(exact(Gateset::V1).to_bits(), exact(Gateset::V2).to_bits());
1420 for gs in Gateset::ALL {
1421 assert_eq!(noisy_expect_z(&htz(gs), 0, 0, 3, 1).to_bits(), exact(gs).to_bits(), "{gs:?}");
1423 let mut src = Circuit::qft(5);
1424 src.gateset = gs;
1425 let r = route_line(&src);
1426 assert_eq!(r.circuit.gateset, gs);
1427 assert!(routing_equivalent(&src, &r));
1428 }
1429 }
1430
1431 #[test]
1434 fn qasm_gates_that_repeat_a_qubit_are_invalid() {
1435 for (gate, want) in [
1436 ("ccx q[0],q[0],q[1];", "control 0 repeated"),
1437 ("ccz q[2],q[2],q[0];", "control 2 repeated"),
1438 ("cx q[1],q[1];", "control 1 equals target"),
1439 ("cz q[0],q[0];", "control 0 equals target"),
1440 ("ccx q[0],q[1],q[1];", "control 1 equals target"),
1441 ("swap q[1],q[1];", "control 1 equals target"),
1442 ("cp(pi/2) q[0],q[0];", "control 0 equals target"),
1443 ("cp(0) q[0],q[0];", "control 0 equals target"),
1445 ("crz(pi/2) q[2],q[2];", "control 2 equals target"),
1446 ] {
1447 let e = from_qasm(&format!("qreg q[3];\n{gate}")).expect_err(gate);
1448 assert_eq!(e, format!("invalid circuit: {want}"), "{gate}");
1449 }
1450 let ok = from_qasm("qreg q[3];\nccx q[0],q[1],q[2];\ncp(pi/2) q[0],q[2];").unwrap();
1451 assert_eq!(ok.gateset, crate::quantum::Gateset::V2);
1452 assert_eq!(ok.ops.len(), 2);
1453 }
1454}