use crate::repro::ln;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InstructionSet {
GateBased,
Measurement,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PhysicalQubit {
pub name: String,
pub instruction_set: InstructionSet,
pub gate_ns: f64,
pub meas_ns: f64,
pub clifford_error: f64,
pub t_error: f64,
}
impl PhysicalQubit {
fn preset(name: &str, set: InstructionSet, gate_ns: f64, meas_ns: f64, p: f64, pt: f64) -> PhysicalQubit {
PhysicalQubit { name: name.into(), instruction_set: set, gate_ns, meas_ns, clifford_error: p, t_error: pt }
}
pub fn us_e3() -> PhysicalQubit {
Self::preset("(us, 1e-3)", InstructionSet::GateBased, 100_000.0, 100_000.0, 1e-3, 1e-6)
}
pub fn us_e4() -> PhysicalQubit {
Self::preset("(us, 1e-4)", InstructionSet::GateBased, 100_000.0, 100_000.0, 1e-4, 1e-6)
}
pub fn ns_e3() -> PhysicalQubit {
Self::preset("(ns, 1e-3)", InstructionSet::GateBased, 50.0, 100.0, 1e-3, 1e-3)
}
pub fn ns_e4() -> PhysicalQubit {
Self::preset("(ns, 1e-4)", InstructionSet::GateBased, 50.0, 100.0, 1e-4, 1e-4)
}
pub fn meas_ns_e4() -> PhysicalQubit {
Self::preset("(ns, 1e-4)*", InstructionSet::Measurement, 100.0, 100.0, 1e-4, 0.05)
}
pub fn meas_ns_e6() -> PhysicalQubit {
Self::preset("(ns, 1e-6)*", InstructionSet::Measurement, 100.0, 100.0, 1e-6, 0.01)
}
pub fn presets() -> Vec<PhysicalQubit> {
vec![Self::us_e3(), Self::us_e4(), Self::ns_e3(), Self::ns_e4(), Self::meas_ns_e4(), Self::meas_ns_e6()]
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct QecModel {
pub name: String,
pub instruction_set: InstructionSet,
pub prefactor: f64,
pub threshold: f64,
pub qubits: (u64, u64, i64),
pub cycle: (f64, f64),
}
impl QecModel {
pub fn surface_gate() -> QecModel {
QecModel {
name: "surface code".into(),
instruction_set: InstructionSet::GateBased,
prefactor: 0.03,
threshold: 0.01,
qubits: (2, 0, 0),
cycle: (4.0, 2.0),
}
}
pub fn surface_measurement() -> QecModel {
QecModel {
name: "surface code".into(),
instruction_set: InstructionSet::Measurement,
prefactor: 0.08,
threshold: 0.0015,
qubits: (2, 0, 0),
cycle: (0.0, 20.0),
}
}
pub fn floquet() -> QecModel {
QecModel {
name: "floquet code".into(),
instruction_set: InstructionSet::Measurement,
prefactor: 0.07,
threshold: 0.01,
qubits: (4, 8, -8),
cycle: (0.0, 3.0),
}
}
pub fn for_qubit(q: &PhysicalQubit) -> Vec<QecModel> {
match q.instruction_set {
InstructionSet::GateBased => vec![Self::surface_gate()],
InstructionSet::Measurement => vec![Self::surface_measurement(), Self::floquet()],
}
}
pub fn tile_qubits(&self, d: u32) -> u64 {
let d = d as i64;
let (a, b, c) = self.qubits;
(a as i64 * d * d + b as i64 * d + c) as u64
}
pub fn step_ns(&self, q: &PhysicalQubit, d: u32) -> f64 {
(self.cycle.0 * q.gate_ns + self.cycle.1 * q.meas_ns) * d as f64
}
pub fn logical_error(&self, p: f64, d: u32) -> f64 {
self.prefactor * powu(p / self.threshold, d.div_ceil(2))
}
}
fn powu(x: f64, mut k: u32) -> f64 {
let (mut base, mut acc) = (x, 1.0);
while k > 0 {
if k & 1 == 1 {
acc *= base;
}
base *= base;
k >>= 1;
}
acc
}
fn log2(x: f64) -> f64 {
ln(x) / core::f64::consts::LN_2
}
fn ceil_sqrt(x: u64) -> u64 {
if x == 0 {
return 0;
}
let mut r = (x as f64).sqrt() as u64;
while r.saturating_mul(r) < x {
r += 1;
}
while r > 0 && (r - 1).saturating_mul(r - 1) >= x {
r -= 1;
}
r
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct LogicalCounts {
pub qubits: u64,
pub measurements: u64,
pub rotations: u64,
pub rotation_depth: u64,
pub t_gates: u64,
pub ccz: u64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct IsaCounts {
pub tiles: u64,
pub min_steps: u64,
pub t_states: u64,
pub t_per_rotation: u64,
pub synthesizes: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Budget {
pub logical: f64,
pub distillation: f64,
pub synthesis: f64,
}
impl Budget {
pub fn split(total: f64, uses_t_states: bool, synthesizes: bool) -> Budget {
let parts = 1.0 + uses_t_states as u8 as f64 + synthesizes as u8 as f64;
let share = total / parts;
Budget {
logical: share,
distillation: if uses_t_states { share } else { 0.0 },
synthesis: if synthesizes { share } else { 0.0 },
}
}
}
pub fn layout(c: &LogicalCounts, total_budget: f64) -> IsaCounts {
let tiles = 2 * c.qubits + ceil_sqrt(8 * c.qubits) + 1;
let synthesizes = c.rotations > 0;
let uses_t = synthesizes || c.t_gates > 0 || c.ccz > 0;
let b = Budget::split(total_budget, uses_t, synthesizes);
let t_per_rotation = if synthesizes {
let x = 0.53 * log2(c.rotations as f64 / b.synthesis) + 5.3;
ceil_pos(x)
} else {
0
};
let min_steps = c.measurements + c.rotations + c.t_gates + t_per_rotation * c.rotation_depth + 3 * c.ccz;
let t_states = t_per_rotation * c.rotations + 4 * c.ccz + c.t_gates;
IsaCounts { tiles, min_steps, t_states, t_per_rotation, synthesizes }
}
fn ceil_pos(x: f64) -> u64 {
let t = x as u64;
if (t as f64) < x { t + 1 } else { t }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnitKind {
SpaceEfficient,
ReedMuller,
}
impl UnitKind {
const ALL: [UnitKind; 2] = [UnitKind::SpaceEfficient, UnitKind::ReedMuller];
fn physical(self) -> (u64, f64) {
match self {
UnitKind::SpaceEfficient => (12, 45.0),
UnitKind::ReedMuller => (31, 24.0),
}
}
fn logical(self) -> (u64, u64) {
match self {
UnitKind::SpaceEfficient => (20, 13),
UnitKind::ReedMuller => (31, 11),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Round {
pub kind: UnitKind,
pub distance: u32,
pub copies: u64,
pub unit_qubits: u64,
pub duration_ns: f64,
pub failure: f64,
pub output_error: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Factory {
pub rounds: Vec<Round>,
pub qubits: u64,
pub duration_ns: f64,
pub output_error: f64,
pub outputs: u64,
}
fn binomial_quantile(n: u64, s: f64, alpha: f64) -> u64 {
if s >= 1.0 {
return n;
}
if s <= 0.0 {
return 0;
}
let q = 1.0 - s;
let mode = (((n + 1) as f64) * s) as u64;
let mode = mode.min(n);
let mut down = Vec::new();
let mut w = 1.0;
let mut i = mode;
while i > 0 {
w *= (i as f64) / ((n - i + 1) as f64) * (q / s);
if w < 1e-40 {
break;
}
i -= 1;
down.push(w);
}
let lo = mode - down.len() as u64;
let mut up = Vec::new();
let mut w = 1.0;
let mut i = mode;
while i < n {
w *= ((n - i) as f64) / ((i + 1) as f64) * (s / q);
if w < 1e-40 {
break;
}
i += 1;
up.push(w);
}
let weights: Vec<f64> = down.iter().rev().copied().chain(core::iter::once(1.0)).chain(up.iter().copied()).collect();
let total: f64 = weights.iter().sum();
let mut cum = 0.0;
for (j, w) in weights.iter().enumerate() {
cum += w;
if cum >= alpha * total {
return lo + j as u64;
}
}
lo + weights.len() as u64 - 1
}
fn copies_for(needed: u64, s: f64, alpha: f64) -> Option<u64> {
let ok = |n: u64| binomial_quantile(n, s, alpha) >= needed;
let mut n = needed.max(1);
while !ok(n) {
n *= 2;
if n >= 1_000_000_000 {
return None;
}
}
let (mut lo, mut hi) = (n / 2, n);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if ok(mid) { hi = mid } else { lo = mid + 1 }
}
Some(hi)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SearchLimits {
pub max_distance: u32,
pub max_rounds: usize,
}
impl Default for SearchLimits {
fn default() -> Self {
SearchLimits { max_distance: 51, max_rounds: 3 }
}
}
fn build_factory(
seq: &[(UnitKind, u32)],
qubit: &PhysicalQubit,
qec: &QecModel,
target: f64,
bound: f64,
) -> Option<Factory> {
let alpha = 0.01 / seq.len() as f64;
let mut input = qubit.t_error;
let mut rounds = Vec::with_capacity(seq.len());
for &(kind, d) in seq {
let (clifford, unit_qubits, duration_ns) = if d == 1 {
let (nq, steps) = kind.physical();
(qubit.clifford_error, nq, steps * qubit.meas_ns)
} else {
let (tiles, steps) = kind.logical();
(
qec.logical_error(qubit.clifford_error, d),
tiles * qec.tile_qubits(d),
steps as f64 * qec.step_ns(qubit, d),
)
};
let failure = 15.0 * input + 356.0 * clifford;
if failure <= 0.0 || failure >= 1.0 {
return None;
}
let output_error = 35.0 * input * input * input + 7.1 * clifford;
if output_error > input {
return None;
}
rounds.push(Round { kind, distance: d, copies: 1, unit_qubits, duration_ns, failure, output_error });
input = output_error;
}
if input > target {
return None;
}
let floor_qubits = rounds.iter().map(|r| r.unit_qubits).max().unwrap_or(0);
let duration_ns: f64 = rounds.iter().map(|r| r.duration_ns).sum();
if floor_qubits as f64 * duration_ns > bound {
return None;
}
let mut needed = 1;
for r in rounds.iter_mut().rev() {
r.copies = copies_for(needed, 1.0 - r.failure, alpha)?;
needed = 15 * r.copies;
}
let qubits = rounds.iter().map(|r| r.copies * r.unit_qubits).max().unwrap_or(0);
Some(Factory { rounds, qubits, duration_ns, output_error: input, outputs: 1 })
}
pub fn find_factory(qubit: &PhysicalQubit, qec: &QecModel, target: f64, limits: SearchLimits) -> Option<Factory> {
let mut distances: Vec<u32> = vec![1];
distances.extend((3..=limits.max_distance).step_by(2));
let mut best: Option<(f64, Factory)> = None;
let mut seq: Vec<(UnitKind, u32)> = Vec::with_capacity(limits.max_rounds);
for rounds in 1..=limits.max_rounds {
search(&mut seq, rounds, &distances, qubit, qec, target, &mut best);
}
best.map(|(_, f)| f)
}
fn search(
seq: &mut Vec<(UnitKind, u32)>,
rounds: usize,
distances: &[u32],
qubit: &PhysicalQubit,
qec: &QecModel,
target: f64,
best: &mut Option<(f64, Factory)>,
) {
if seq.len() == rounds {
let bound = best.as_ref().map_or(f64::INFINITY, |(v, _)| *v);
let Some(f) = build_factory(seq, qubit, qec, target, bound) else { return };
let volume = f.qubits as f64 * f.duration_ns;
let better = match best {
None => true,
Some((v, b)) => volume < *v || (volume == *v && f.qubits < b.qubits),
};
if better {
*best = Some((volume, f));
}
return;
}
let floor = seq.last().map_or(1, |&(_, d)| d.max(3));
for &d in distances {
if (d == 1 && !seq.is_empty()) || (d != 1 && d < floor) {
continue;
}
for kind in UnitKind::ALL {
seq.push((kind, d));
search(seq, rounds, distances, qubit, qec, target, best);
seq.pop();
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct EstimateConfig {
pub error_budget: f64,
pub slowdown: u64,
pub limits: SearchLimits,
}
impl EstimateConfig {
pub fn new(error_budget: f64) -> EstimateConfig {
EstimateConfig { error_budget, slowdown: 1, limits: SearchLimits::default() }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Estimate {
pub qubit: String,
pub code: String,
pub isa: IsaCounts,
pub budget: Budget,
pub distance: u32,
pub tile_qubits: u64,
pub step_ns: f64,
pub steps: u64,
pub runtime_ns: f64,
pub logical_error: f64,
pub t_target: f64,
pub factory: Option<Factory>,
pub factories: u64,
pub algorithm_qubits: u64,
pub factory_qubits: u64,
pub physical_qubits: u64,
}
impl Estimate {
pub fn factory_fraction(&self) -> f64 {
self.factory_qubits as f64 / self.physical_qubits as f64
}
pub fn volume(&self) -> f64 {
self.physical_qubits as f64 * self.runtime_ns
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EstimateError {
AboveThreshold,
DistanceExceeded,
NoFactory,
Invalid,
Mismatch,
NotRun,
}
pub fn estimate(c: &LogicalCounts, qubit: &PhysicalQubit, qec: &QecModel, cfg: &EstimateConfig) -> Result<Estimate, EstimateError> {
if c.qubits == 0 || cfg.error_budget.is_nan() || cfg.error_budget <= 0.0 {
return Err(EstimateError::Invalid);
}
estimate_isa(&layout(c, cfg.error_budget), qubit, qec, cfg)
}
pub fn estimate_isa(isa: &IsaCounts, qubit: &PhysicalQubit, qec: &QecModel, cfg: &EstimateConfig) -> Result<Estimate, EstimateError> {
if isa.tiles == 0 || isa.min_steps == 0 || cfg.slowdown == 0 || !(cfg.error_budget > 0.0 && cfg.error_budget < 1.0) {
return Err(EstimateError::Invalid);
}
if qec.instruction_set != qubit.instruction_set {
return Err(EstimateError::Mismatch);
}
if qubit.clifford_error >= qec.threshold {
return Err(EstimateError::AboveThreshold);
}
let budget = Budget::split(cfg.error_budget, isa.t_states > 0, isa.synthesizes);
let t_target = if isa.t_states > 0 { budget.distillation / isa.t_states as f64 } else { 0.0 };
let factory = if isa.t_states == 0 {
None
} else if t_target >= qubit.t_error {
None
} else {
Some(find_factory(qubit, qec, t_target, cfg.limits).ok_or(EstimateError::NoFactory)?)
};
let mut steps = isa.min_steps.saturating_mul(cfg.slowdown);
loop {
let distance = distance_for(qec, qubit, isa.tiles, steps, budget.logical, cfg.limits.max_distance)?;
let step_ns = qec.step_ns(qubit, distance);
let runtime_ns = step_ns * steps as f64;
if let Some(f) = &factory
&& runtime_ns < f.duration_ns
{
let need = ceil_pos(f.duration_ns / step_ns);
if need > steps {
steps = need;
continue;
}
}
let (factories, factory_qubits) = match &factory {
Some(f) => {
let n = ceil_pos(isa.t_states as f64 * f.duration_ns / (f.outputs as f64 * runtime_ns));
(n, n * f.qubits)
}
None => (0, 0),
};
let tile_qubits = qec.tile_qubits(distance);
let algorithm_qubits = isa.tiles * tile_qubits;
return Ok(Estimate {
qubit: qubit.name.clone(),
code: qec.name.clone(),
isa: *isa,
budget,
distance,
tile_qubits,
step_ns,
steps,
runtime_ns,
logical_error: qec.logical_error(qubit.clifford_error, distance),
t_target,
factory,
factories,
algorithm_qubits,
factory_qubits,
physical_qubits: algorithm_qubits + factory_qubits,
});
}
}
fn distance_for(qec: &QecModel, qubit: &PhysicalQubit, tiles: u64, steps: u64, budget: f64, max: u32) -> Result<u32, EstimateError> {
let volume = tiles as f64 * steps as f64;
let mut d = 1;
while d <= max {
if volume * qec.logical_error(qubit.clifford_error, d) <= budget {
return Ok(d);
}
d += 2;
}
Err(EstimateError::DistanceExceeded)
}
pub fn estimate_best(c: &LogicalCounts, qubit: &PhysicalQubit, cfg: &EstimateConfig) -> Result<Estimate, EstimateError> {
let isa = layout(c, cfg.error_budget);
estimate_best_isa(&isa, qubit, cfg)
}
pub fn estimate_best_isa(isa: &IsaCounts, qubit: &PhysicalQubit, cfg: &EstimateConfig) -> Result<Estimate, EstimateError> {
let mut best: Option<Estimate> = None;
let mut last_err = EstimateError::Mismatch;
for qec in QecModel::for_qubit(qubit) {
match estimate_isa(isa, qubit, &qec, cfg) {
Ok(e) => {
if best.as_ref().is_none_or(|b| e.volume() < b.volume()) {
best = Some(e);
}
}
Err(e) => last_err = e,
}
}
best.ok_or(last_err)
}
pub fn frontier(isa: &IsaCounts, qubit: &PhysicalQubit, qec: &QecModel, cfg: &EstimateConfig, max_slowdown: u64) -> Vec<Estimate> {
let mut out: Vec<Estimate> = Vec::new();
let mut s = 1;
while s <= max_slowdown {
let c = EstimateConfig { slowdown: s, ..*cfg };
if let Ok(e) = estimate_isa(isa, qubit, qec, &c)
&& out.last().is_none_or(|p| e.physical_qubits < p.physical_qubits)
{
out.push(e);
}
s *= 2;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
const US: f64 = 1e3;
const MS: f64 = 1e6;
const S: f64 = 1e9;
const HOUR: f64 = 3600.0 * S;
const DAY: f64 = 24.0 * HOUR;
fn chemistry() -> LogicalCounts {
LogicalCounts {
qubits: 1318,
measurements: 1_370_000_000,
rotations: 206_000_000,
rotation_depth: 205_000_000,
t_gates: 55_300_000,
ccz: 135_000_000_000,
}
}
fn factoring() -> LogicalCounts {
LogicalCounts {
qubits: 12_581,
measurements: 1_080_000_000,
rotations: 12,
rotation_depth: 12,
t_gates: 12,
ccz: 3_730_000_000,
}
}
fn printed(x: f64, shown: f64, figures: i32) -> bool {
let unit = 10f64.powi(shown.abs().log10().floor() as i32 - figures + 1);
(x - shown).abs() < unit
}
#[test]
fn integer_helpers_are_exact() {
for x in [1u64, 2, 3, 4, 8, 9, 10, 100_648, 10_544, u32::MAX as u64] {
let r = ceil_sqrt(x);
assert!(r * r >= x && (r - 1) * (r - 1) < x, "{x}");
}
assert_eq!(powu(0.1, 0), 1.0);
assert_eq!(powu(2.0, 10), 1024.0);
assert!((powu(0.01, 5) - 1e-10).abs() < 1e-24);
assert_eq!(binomial_quantile(16, 1.0 - 0.002568, 0.005), 15);
assert_eq!(binomial_quantile(15, 1.0 - 0.002568, 0.005), 14);
assert_eq!(copies_for(15, 1.0 - 0.002568, 0.005), Some(16));
}
#[test]
fn the_layout_reproduces_the_isa_counts() {
let c = layout(&chemistry(), 0.01);
assert_eq!(c.tiles, 2740);
assert_eq!(c.t_per_rotation, 25);
assert!(printed(c.min_steps as f64, 4.12e11, 3)); assert!((c.t_states as f64 / 5.44e11 - 1.0).abs() < 0.005); let f = layout(&factoring(), 1.0 / 3.0);
assert_eq!(f.tiles, 25_481);
assert_eq!(f.t_per_rotation, 9);
assert!(printed(f.min_steps as f64, 1.23e10, 3));
assert!(printed(f.t_states as f64, 1.49e10, 3));
}
#[test]
fn the_worked_factories_are_reproduced_exactly() {
let q = PhysicalQubit::ns_e4();
let s = QecModel::surface_gate();
let f = build_factory(&[(UnitKind::SpaceEfficient, 9)], &q, &s, 1.0, f64::INFINITY).unwrap();
assert_eq!((f.qubits, f.rounds[0].copies), (3240, 1));
assert!((f.duration_ns - 46.8 * US).abs() < 1e-6);
assert!(printed(f.output_error, 5.6e-11, 2));
let f = build_factory(&[(UnitKind::SpaceEfficient, 3), (UnitKind::SpaceEfficient, 11)], &q, &s, 1.0, f64::INFINITY).unwrap();
assert_eq!(f.rounds.iter().map(|r| r.copies).collect::<Vec<_>>(), vec![16, 1]);
assert_eq!(f.qubits, 5760);
assert!((f.duration_ns - 72.8 * US).abs() < 1e-6);
assert!(printed(f.output_error, 5.51e-13, 3));
let f = build_factory(&[(UnitKind::SpaceEfficient, 5), (UnitKind::ReedMuller, 13)], &q, &s, 1.0, f64::INFINITY).unwrap();
assert_eq!(f.qubits, 16_000);
assert_eq!(f.rounds[1].unit_qubits, 10_478);
assert!((f.duration_ns - 83.2 * US).abs() < 1e-6);
assert!(printed(f.output_error, 2.13e-15, 3));
}
#[test]
fn the_per_qubit_factoring_table_is_reproduced() {
let isa = layout(&factoring(), 1.0 / 3.0);
let cfg = EstimateConfig::new(1.0 / 3.0);
let rows: [(&str, u32, u64, f64, u64, f64); 6] = [
("surface code", 27, 1458, 16.0 * MS, 17_640, 163.0 * MS),
("surface code", 13, 338, 7.0 * MS, 4840, 85.0 * MS),
("surface code", 27, 1458, 10.0 * US, 33_320, 128.0 * US),
("surface code", 13, 338, 5.0 * US, 5760, 72.0 * US),
("floquet code", 15, 1012, 4.0 * US, 21_840, 52.0 * US),
("floquet code", 7, 244, 2.0 * US, 16_416, 23.0 * US),
];
let unit = |x: f64| if x >= MS { MS } else { US };
for (i, (q, row)) in PhysicalQubit::presets().iter().zip(rows).enumerate() {
let e = estimate_best_isa(&isa, q, &cfg).unwrap();
let f = e.factory.as_ref().unwrap();
if i == 5 {
assert_eq!((e.code.as_str(), e.distance, e.tile_qubits), (row.0, row.1, row.2));
assert!(f.output_error <= e.t_target);
assert!(f.qubits as f64 * f.duration_ns < row.4 as f64 * row.5 / 5.0);
continue;
}
let got = (
e.code.as_str(),
e.distance,
e.tile_qubits,
(e.step_ns / unit(row.3)).floor() * unit(row.3),
f.qubits,
(f.duration_ns / unit(row.5)).floor() * unit(row.5),
);
assert_eq!(got, row, "{}: {:?}", q.name, f.rounds);
}
}
#[test]
fn the_summary_rows_are_reproduced() {
const YEAR: f64 = 365.25 * DAY;
const MONTH: f64 = YEAR / 12.0;
let factoring_rows: [(u32, u64, f64, f64, f64); 6] = [
(27, 13, 37.0, 6.2, YEAR),
(13, 14, 8.6, 3.0, YEAR),
(27, 15, 37.0, 1.5, DAY),
(13, 18, 8.7, 18.0, HOUR),
(15, 15, 26.0, 15.0, HOUR),
(7, 13, 6.2, 7.1, HOUR),
];
let chemistry_rows: [(u32, u64, f64, f64, f64); 6] = [
(33, 15, 6.4, 260.0, YEAR),
(17, 14, 1.6, 130.0, YEAR),
(33, 17, 6.9, 2.0, MONTH),
(17, 17, 1.9, 1.0, MONTH),
(17, 19, 4.5, 24.0, DAY), (9, 19, 1.3, 12.0, DAY),
];
for (prog, eps, rows) in [(factoring(), 1.0 / 3.0, factoring_rows), (chemistry(), 0.01, chemistry_rows)] {
let cfg = EstimateConfig::new(eps);
for (q, (d, nf, mq, t, unit)) in PhysicalQubit::presets().iter().zip(rows) {
let e = estimate_best(&prog, q, &cfg).unwrap();
assert_eq!((e.distance, e.factories), (d, nf), "{} {:?}", q.name, e.factory);
assert!(printed(e.physical_qubits as f64 / 1e6, mq, 2), "{}: {}", q.name, e.physical_qubits);
assert!(printed(e.runtime_ns / unit, t, 2), "{}: {} vs {t}", q.name, e.runtime_ns / unit);
}
}
}
#[test]
fn the_factoring_example_matches_to_the_minute() {
let e = estimate(&factoring(), &PhysicalQubit::ns_e4(), &QecModel::surface_gate(), &EstimateConfig::new(1.0 / 3.0)).unwrap();
assert_eq!(e.distance, 13);
assert_eq!(e.factories, 18);
assert_eq!(e.physical_qubits, 18 * 5760 + 25_481 * 338);
let minutes = (e.runtime_ns / (60.0 * S)) as u64;
assert_eq!((minutes / 60, minutes % 60), (17, 43));
let f = e.factory.unwrap();
assert_eq!(f.rounds.iter().map(|r| (r.kind, r.distance, r.copies)).collect::<Vec<_>>(),
vec![(UnitKind::SpaceEfficient, 3, 16), (UnitKind::SpaceEfficient, 11, 1)]);
}
#[test]
fn the_dynamics_rows_follow_from_the_isa_counts() {
let isa = IsaCounts { tiles: 230, min_steps: 157_000, t_states: 2_400_000, t_per_rotation: 20, synthesizes: true };
let fast: [(u32, u64, f64); 6] = [(19, 199, 3.0), (9, 199, 0.68), (19, 242, 8.2), (9, 199, 0.68), (9, 260, 5.8), (5, 224, 0.62)];
let slow: [(u32, u64, f64); 6] = [(21, 18, 0.46), (11, 17, 0.11), (21, 22, 0.94), (11, 17, 0.11), (11, 22, 0.61), (5, 23, 0.09)];
for (slowdown, rows) in [(1, fast), (10, slow)] {
let cfg = EstimateConfig { slowdown, ..EstimateConfig::new(0.001) };
for (q, (d, nf, mq)) in PhysicalQubit::presets().iter().zip(rows) {
let e = estimate_best_isa(&isa, q, &cfg).unwrap();
assert_eq!((e.distance, e.factories), (d, nf), "{} ×{slowdown}", q.name);
let figures = if mq < 0.1 { 1 } else { 2 };
assert!(printed(e.physical_qubits as f64 / 1e6, mq, figures), "{} ×{slowdown}: {}", q.name, e.physical_qubits);
}
}
}
#[test]
fn the_frontier_trades_qubits_for_time() {
let isa = layout(&chemistry(), 0.01);
let q = PhysicalQubit::ns_e4();
let fr = frontier(&isa, &q, &QecModel::surface_gate(), &EstimateConfig::new(0.01), 64);
assert!(fr.len() >= 2);
for w in fr.windows(2) {
assert!(w[1].physical_qubits < w[0].physical_qubits);
assert!(w[1].runtime_ns > w[0].runtime_ns);
}
}
#[test]
fn bad_inputs_are_refused() {
let isa = layout(&factoring(), 1.0 / 3.0);
let mut bad = PhysicalQubit::ns_e3();
bad.clifford_error = 0.02;
let cfg = EstimateConfig::new(1.0 / 3.0);
assert_eq!(estimate_isa(&isa, &bad, &QecModel::surface_gate(), &cfg), Err(EstimateError::AboveThreshold));
assert_eq!(estimate_isa(&isa, &PhysicalQubit::ns_e3(), &QecModel::floquet(), &cfg), Err(EstimateError::Mismatch));
assert_eq!(estimate_isa(&isa, &PhysicalQubit::ns_e3(), &QecModel::surface_gate(), &EstimateConfig { slowdown: 0, ..cfg }), Err(EstimateError::Invalid));
assert_eq!(estimate(&LogicalCounts::default(), &PhysicalQubit::ns_e3(), &QecModel::surface_gate(), &cfg), Err(EstimateError::Invalid));
}
#[test]
fn a_clifford_only_program_needs_no_factory() {
let c = LogicalCounts { qubits: 100, measurements: 10_000, ..Default::default() };
let e = estimate(&c, &PhysicalQubit::ns_e4(), &QecModel::surface_gate(), &EstimateConfig::new(0.01)).unwrap();
assert!(e.factory.is_none());
assert_eq!((e.factories, e.factory_qubits), (0, 0));
assert_eq!(e.budget.logical, 0.01);
}
}