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