use super::lanes::{self, GateWords, Lane};
use crate::quantum::{Circuit, MAX_QUBITS, ONE, QuantumError};
pub const PLAN_REVISION: u32 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlannedGate {
pub words: GateWords,
pub index: u32,
pub diag: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Step {
Gate { gate: u32 },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
pub n_qubits: u8,
pub start: usize,
pub gates: Vec<PlannedGate>,
pub steps: Vec<Step>,
}
pub fn compile(c: &Circuit, start: usize) -> Result<Program, QuantumError> {
c.validate()?;
if c.n_qubits > MAX_QUBITS {
return Err(QuantumError::TooManyQubits(c.n_qubits));
}
let gates: Vec<PlannedGate> = c
.ops
.iter()
.enumerate()
.map(|(k, g)| {
let words = lanes::gate_words(g, c.gateset);
PlannedGate { words, index: k as u32, diag: words.m[2..6] == [0; 4] }
})
.collect();
let steps = (0..gates.len() as u32).map(|gate| Step::Gate { gate }).collect();
Ok(Program { n_qubits: c.n_qubits, start: start % (1usize << c.n_qubits), gates, steps })
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LaneRun {
pub amps: Vec<Lane>,
pub out_of_range: Option<u32>,
}
pub fn execute(p: &Program) -> LaneRun {
let mut amps = vec![[0i32; 2]; 1usize << p.n_qubits];
amps[p.start] = [ONE as i32, 0];
let mut out_of_range: Option<u32> = None;
for step in &p.steps {
match *step {
Step::Gate { gate } => {
let g = &p.gates[gate as usize];
let fits = lanes::apply_words(&mut amps, &g.words).expect("a compiled program's words fit its state");
if !fits {
out_of_range = Some(out_of_range.map_or(g.index, |k| k.min(g.index)));
}
}
}
}
LaneRun { amps, out_of_range }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quantum::{BaseGate, Gate, Gateset};
use crate::quantum_fabric::{Corpus, FABRIC_DEFAULT};
fn widened(lanes: &[Lane]) -> Vec<[i64; 2]> {
lanes.iter().map(|a| [i64::from(a[0]), i64::from(a[1])]).collect()
}
fn native(c: &Circuit, start: usize) -> Vec<[i64; 2]> {
c.simulate_from(start).unwrap().amps.iter().map(|a| [a.re, a.im]).collect()
}
#[test]
fn execute_is_the_native_simulator() {
for gs in Gateset::ALL {
let random = Corpus::Random { seed: 7, cases: 300 }.circuits(gs);
let fabric = FABRIC_DEFAULT.circuits(gs);
for (name, circuits) in [("random-v1", random), ("fabric-v1", fabric)] {
for c in &circuits {
for start in [0, 5] {
let p = compile(c, start).unwrap();
assert_eq!(p.start, start % (1 << c.n_qubits));
assert_eq!((p.gates.len(), p.steps.len()), (c.ops.len(), c.ops.len()));
let run = execute(&p);
assert_eq!(run.out_of_range, None, "{name} {gs:?}");
assert_eq!(widened(&run.amps), native(c, start), "{name} {gs:?} start {start}");
}
}
}
}
let mut c = Circuit::with_gateset(3, Gateset::V1);
c.h(0).t(1).cx(0, 2).p(19, 2).y(1).z(0);
let p = compile(&c, 11).unwrap();
assert_eq!(p.start, 3);
assert_eq!(p.steps, (0..6).map(|gate| Step::Gate { gate }).collect::<Vec<_>>());
assert_eq!(p.gates.iter().map(|g| g.index).collect::<Vec<_>>(), [0, 1, 2, 3, 4, 5]);
assert_eq!(p.gates.iter().map(|g| g.diag).collect::<Vec<_>>(), [false, true, false, true, false, true]);
let empty = compile(&Circuit::with_gateset(0, Gateset::V2), 9).unwrap();
assert_eq!((empty.start, empty.gates.len()), (0, 0));
assert_eq!(execute(&empty), LaneRun { amps: vec![[ONE as i32, 0]], out_of_range: None });
for c in crate::quantum_fabric::tests::refused_circuits() {
assert_eq!(compile(&c, 0).unwrap_err(), c.simulate_from(0).unwrap_err(), "{c:?}");
}
let mut wide = Circuit::new(MAX_QUBITS + 1);
wide.ops.push(Gate { base: BaseGate::X, controls: vec![], target: 0, param: 0 });
assert_eq!(compile(&wide, 0).unwrap_err(), QuantumError::TooManyQubits(MAX_QUBITS + 1));
assert_eq!(compile(&Circuit::new(MAX_QUBITS + 1), 0).unwrap_err(), QuantumError::TooManyQubits(MAX_QUBITS + 1));
let mut both = Circuit::new(MAX_QUBITS + 1);
both.ops.push(Gate { base: BaseGate::P, controls: vec![], target: 0, param: 0 });
assert_eq!(compile(&both, 0).unwrap_err(), both.simulate_from(0).unwrap_err());
assert!(matches!(compile(&both, 0), Err(QuantumError::Invalid(_))));
}
}