use std::hint::black_box;
use std::time::Duration;
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use ticit::{Gate1Q, Instruction};
use ticit::{Pauli, PauliBasis, PauliString, TableauSimulator};
#[path = "support/workload.rs"]
mod workload;
use workload::{Rng, SEED};
const N: usize = 128;
const OPS: usize = 5000;
type TwoQubitGate = (PauliBasis, PauliBasis, usize, usize);
fn two_qubit_stream(count: usize) -> Vec<TwoQubitGate> {
let mut rng = Rng::new(SEED);
(0..count)
.map(|i| {
let control_qubit = rng.below(N);
let target_qubit = rng.below_except(N, control_qubit);
let target = match i % 3 {
0 => PauliBasis::X, 1 => PauliBasis::Z, _ => PauliBasis::Y, };
(PauliBasis::Z, target, control_qubit, target_qubit)
})
.collect()
}
fn basis_pauli(basis: PauliBasis, qubit: usize) -> PauliString {
PauliString::single(N, qubit, basis.into())
}
fn bench_two_qubit(c: &mut Criterion) {
let gates = two_qubit_stream(OPS);
let instructions: Vec<Instruction> = gates
.iter()
.map(
|&(control, target, control_qubit, target_qubit)| Instruction::Gate2 {
control,
target,
control_qubit,
target_qubit,
},
)
.collect();
let mut group = c.benchmark_group("batch-vs-loop");
group.throughput(Throughput::Elements(OPS as u64));
group.bench_function("2q-layer/batch", |b| {
let mut sim = TableauSimulator::with_seed(N, SEED);
b.iter(|| {
black_box(
sim.apply_batch(black_box(&instructions))
.expect("distinct operands, no measurements"),
);
});
});
group.bench_function("2q-layer/controlled-pauli", |b| {
let mut sim = TableauSimulator::with_seed(N, SEED);
b.iter(|| {
for &(control, target, control_qubit, target_qubit) in black_box(&gates) {
sim.controlled_pauli(
&basis_pauli(control, control_qubit),
&basis_pauli(target, target_qubit),
)
.expect("single-qubit axes on distinct qubits commute");
}
});
});
group.finish();
}
fn bench_single_qubit(c: &mut Criterion) {
let cycle = [Gate1Q::H, Gate1Q::S, Gate1Q::Cxyz, Gate1Q::Hyz];
let mut rng = Rng::new(SEED);
let gates: Vec<(Gate1Q, usize)> = (0..OPS)
.map(|i| (cycle[i % cycle.len()], rng.below(N)))
.collect();
let instructions: Vec<Instruction> = gates
.iter()
.map(|&(gate, qubit)| Instruction::Gate1 { gate, qubit })
.collect();
let mut group = c.benchmark_group("batch-vs-loop");
group.throughput(Throughput::Elements(OPS as u64));
group.bench_function("1q-clifford/batch", |b| {
let mut sim = TableauSimulator::with_seed(N, SEED);
b.iter(|| {
black_box(
sim.apply_batch(black_box(&instructions))
.expect("single-qubit Cliffords cannot fail"),
);
});
});
group.bench_function("1q-clifford/procedural", |b| {
let mut sim = TableauSimulator::with_seed(N, SEED);
b.iter(|| {
for &(gate, qubit) in black_box(&gates) {
sim.gate1(gate, qubit);
}
});
});
group.finish();
}
const ROUND: usize = 100;
fn verify_stream(rounds: usize) -> Vec<Instruction> {
let mut rng = Rng::new(SEED ^ 0x00B1_0C00);
let mut stream = Vec::with_capacity(rounds * ROUND);
let mut magic_cursor = 0usize;
for _ in 0..rounds {
for i in 0..58 {
let control_qubit = rng.below(N);
let target_qubit = rng.below_except(N, control_qubit);
let target = match i % 3 {
0 => PauliBasis::X,
1 => PauliBasis::Z,
_ => PauliBasis::Y,
};
stream.push(Instruction::Gate2 {
control: PauliBasis::Z,
target,
control_qubit,
target_qubit,
});
}
for _ in 0..20 {
stream.push(Instruction::Measure(PauliString::single(
N,
rng.below(N),
Pauli::Z,
)));
}
for _ in 0..18 {
stream.push(Instruction::Reset {
basis: PauliBasis::Z,
qubit: rng.below(N),
});
}
for _ in 0..2 {
let qubit = magic_cursor % N;
magic_cursor += 1;
stream.push(Instruction::T {
basis: PauliBasis::Z,
qubit,
adjoint: false,
});
stream.push(Instruction::Reset {
basis: PauliBasis::Z,
qubit,
});
}
}
stream
}
fn bench_verify_replay(c: &mut Criterion) {
const ROUNDS: usize = 20;
const SHOTS: u64 = 4;
let stream = verify_stream(ROUNDS);
let mut group = c.benchmark_group("batch-verify-replay");
group.sample_size(20);
group.measurement_time(Duration::from_secs(10));
group.throughput(Throughput::Elements(SHOTS * (ROUNDS * ROUND) as u64));
group.bench_function(format!("n{N}"), |b| {
b.iter(|| {
for shot in 0..SHOTS {
let mut sim = TableauSimulator::with_seed(N, SEED ^ shot);
black_box(
sim.apply_batch(black_box(&stream))
.expect("the stream retires its own magic"),
);
}
});
});
group.finish();
}
criterion_group!(
benches,
bench_two_qubit,
bench_single_qubit,
bench_verify_replay,
);
criterion_main!(benches);