use crate::{
Circuit, ComplexTensor, DenseSimulator, DensityMatrix, Error, Matrix, Register, ResourceLimits,
Result, Unitary,
};
use burn::tensor::{DType, Device};
use num_complex::Complex64;
use serde::Serialize;
#[derive(Clone, Copy, Debug, Serialize)]
pub struct MagicState {
phase_error: f64,
availability: f64,
}
impl MagicState {
pub fn new(phase_error: f64, availability: f64) -> Result<Self> {
probability(phase_error)?;
probability(availability)?;
Ok(Self {
phase_error,
availability,
})
}
pub fn phase_error(&self) -> f64 {
self.phase_error
}
pub fn availability(&self) -> f64 {
self.availability
}
pub fn density(
&self,
device: &Device,
dtype: DType,
limits: ResourceLimits,
) -> Result<DensityMatrix> {
limits.check_state(1, 4)?;
limits.check_matrix(1, 2)?;
let c = (1. - 2. * self.phase_error) / (2. * 2f64.sqrt());
let entries = Matrix::new(
2,
vec![
Complex64::new(0.5, 0.),
Complex64::new(c, -c),
Complex64::new(c, c),
Complex64::new(0.5, 0.),
],
)?
.to_tensor(device, dtype)?;
DensityMatrix::from_entries(Register::qubits(1)?, entries, limits)
}
pub fn inject(
&self,
input: &DensityMatrix,
target: usize,
limits: ResourceLimits,
) -> Result<MagicInjection> {
if input.register().dimensions().iter().any(|&d| d != 2)
|| target >= input.register().wires()
{
return Err(Error::InvalidDimensions(
"T injection requires a valid qubit target".into(),
));
}
let n = input.register().size();
let b = input.batch_size();
let n2 = n
.checked_mul(n)
.and_then(|v| v.checked_mul(4))
.ok_or(Error::Overflow)?;
limits.check_state(b, n2)?;
let anc = self.density(&input.entries().device(), input.entries().dtype(), limits)?;
let a = input.entries().real().reshape([b, n, 1, n, 1]);
let z = input.entries().imag().reshape([b, n, 1, n, 1]);
let u = anc.entries().real().reshape([1, 1, 2, 1, 2]);
let v = anc.entries().imag().reshape([1, 1, 2, 1, 2]);
let appended = DensityMatrix::from_entries(
Register::qubits(input.register().wires() + 1)?,
ComplexTensor::new(
(a.clone() * u.clone() - z.clone() * v.clone()).reshape([b, 2 * n, 2 * n]),
(a * v + z * u).reshape([b, 2 * n, 2 * n]),
)?,
limits,
)?;
let engine = DenseSimulator::new(limits);
let ancilla = input.register().wires();
let joint = appended.apply(&engine, &[target, ancilla], &Unitary::cnot())?;
let keep: Vec<_> = (0..ancilla).collect();
let mut branches = vec![MagicInjectionBranch {
record: MagicRecord::Missing,
state: scaled(input, 1. - self.availability, limits)?,
}];
for bit in 0..2 {
let projector = Matrix::real(
2,
if bit == 0 {
vec![1., 0., 0., 0.]
} else {
vec![0., 0., 0., 1.]
},
)?;
let mut state = joint
.left_right(&engine, &[ancilla], &projector)?
.reduced(&keep, limits)?;
if bit == 1 {
state = state.apply(&engine, &[target], &Unitary::s())?;
}
branches.push(MagicInjectionBranch {
record: if bit == 0 {
MagicRecord::Zero
} else {
MagicRecord::One
},
state: scaled(&state, self.availability, limits)?,
});
}
Ok(MagicInjection {
branches,
raw_source_attempts: 1,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum MagicRecord {
Missing,
Zero,
One,
}
#[derive(Clone, Debug)]
pub struct MagicInjectionBranch {
pub record: MagicRecord,
pub state: DensityMatrix,
}
#[derive(Clone, Debug)]
pub struct MagicInjection {
pub branches: Vec<MagicInjectionBranch>,
pub raw_source_attempts: usize,
}
impl MagicInjection {
pub fn completed(&self, limits: ResourceLimits) -> Result<DensityMatrix> {
if self.branches.len() != 3
|| self.branches[0].record != MagicRecord::Missing
|| self.branches[1].record != MagicRecord::Zero
|| self.branches[2].record != MagicRecord::One
|| self.branches[1].state.register() != self.branches[2].state.register()
|| self.branches[1].state.entries().dims() != self.branches[2].state.entries().dims()
{
return Err(Error::InvalidValue(
"incomplete magic injection record".into(),
));
}
let a = &self.branches[1].state;
let b = &self.branches[2].state;
crate::complex::compatible(&a.entries().real(), &b.entries().real())?;
DensityMatrix::from_entries(
a.register().clone(),
ComplexTensor::new(
a.entries().real() + b.entries().real(),
a.entries().imag() + b.entries().imag(),
)?,
limits,
)
}
}
fn scaled(state: &DensityMatrix, p: f64, limits: ResourceLimits) -> Result<DensityMatrix> {
DensityMatrix::from_entries(
state.register().clone(),
ComplexTensor::new(state.entries().real() * p, state.entries().imag() * p)?,
limits,
)
}
fn probability(p: f64) -> Result<()> {
if p.is_finite() && (0. ..=1.).contains(&p) {
Ok(())
} else {
Err(Error::InvalidValue(
"magic-state probability outside [0,1]".into(),
))
}
}
#[derive(Clone, Debug, Serialize)]
pub struct MagicDistillationConfig {
pub phase_errors: [f64; 15],
pub source_availability: [f64; 15],
pub syndrome_readout_errors: [f64; 4],
pub max_fault_patterns: usize,
}
impl Default for MagicDistillationConfig {
fn default() -> Self {
Self {
phase_errors: [0.; 15],
source_availability: [1.; 15],
syndrome_readout_errors: [0.; 4],
max_fault_patterns: 1 << 15,
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct MagicDistillationCost {
pub raw_source_attempts: usize,
pub expected_raw_states_emitted: f64,
pub expected_code_carriers_prepared: f64,
pub expected_clifford_ancillas_prepared: f64,
pub expected_measurements: f64,
pub expected_cnot_gates: f64,
pub expected_hadamards: f64,
pub expected_phase_cliffords: f64,
pub raw_attempts_per_output: Option<f64>,
}
#[derive(Clone, Debug, Serialize)]
pub struct MagicDistillation {
pub sources_ready: f64,
pub acceptance: f64,
pub false_herald_probability: f64,
pub output_phase_error: Option<f64>,
pub true_syndrome_probabilities: [f64; 16],
pub observed_syndrome_probabilities: [f64; 16],
pub evaluated_fault_patterns: usize,
pub cost: MagicDistillationCost,
}
impl MagicDistillation {
pub fn resource(&self) -> Result<MagicState> {
MagicState::new(self.output_phase_error.unwrap_or(0.), self.acceptance)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ReedMuller15;
impl ReedMuller15 {
pub fn x_checks() -> [u16; 4] {
std::array::from_fn(|bit| {
(0..15).fold(0, |mask, q| mask | ((((q + 1) >> bit) & 1) as u16) << q)
})
}
pub fn logical_x_mask() -> u16 {
0x7fff
}
pub fn phase_fault(mask: u16) -> Result<(u8, bool)> {
if mask >= 1 << 15 {
return Err(Error::InvalidValue("15-qubit phase mask".into()));
}
let syndrome = Self::x_checks().iter().enumerate().fold(0, |s, (i, r)| {
s | (((mask & r).count_ones() & 1) as u8) << i
});
Ok((syndrome, mask.count_ones() % 2 == 1))
}
pub fn encoding_cnots() -> Vec<(usize, usize)> {
let mut columns = vec![Self::logical_x_mask()];
columns.extend(Self::x_checks());
for q in 0..15 {
let unit = 1u16 << q;
let mut candidate = columns.clone();
candidate.push(unit);
if rank(&candidate) > columns.len() {
columns.push(unit);
}
}
let mut rows: Vec<u16> = (0..15)
.map(|q| {
columns
.iter()
.enumerate()
.fold(0, |r, (j, c)| r | ((c >> q) & 1) << j)
})
.collect();
let mut operations = vec![];
for pivot in 0..15 {
let found = (pivot..15).find(|&r| rows[r] & (1 << pivot) != 0).unwrap();
if found != pivot {
for (c, t) in [(pivot, found), (found, pivot), (pivot, found)] {
rows[t] ^= rows[c];
operations.push((c, t));
}
}
for row in 0..15 {
if row != pivot && rows[row] & (1 << pivot) != 0 {
rows[row] ^= rows[pivot];
operations.push((pivot, row));
}
}
}
operations.reverse();
operations
}
pub fn fault_circuit(mask: u16, decode: bool) -> Result<Circuit> {
Self::phase_fault(mask)?;
let mut circuit = Circuit::new(Register::qubits(15)?, 0, 0);
for q in 0..5 {
circuit.unitary(vec![q], Unitary::h())?;
}
let cnots = Self::encoding_cnots();
for &(c, t) in &cnots {
circuit.unitary(vec![c, t], Unitary::cnot())?;
}
for q in 0..15 {
circuit.unitary(vec![q], Unitary::t().adjoint())?;
if mask & (1 << q) != 0 {
circuit.unitary(vec![q], Unitary::z())?;
}
}
if decode {
for &(c, t) in cnots.iter().rev() {
circuit.unitary(vec![c, t], Unitary::cnot())?;
}
for q in 1..5 {
circuit.unitary(vec![q], Unitary::h())?;
}
}
Ok(circuit)
}
pub fn distill(config: &MagicDistillationConfig) -> Result<MagicDistillation> {
for &p in config
.phase_errors
.iter()
.chain(&config.source_availability)
.chain(&config.syndrome_readout_errors)
{
probability(p)?;
}
if config.max_fault_patterns < 1 << 15 {
return Err(Error::ResourceLimit {
resource: "magic phase fault patterns",
required: 1 << 15,
limit: config.max_fault_patterns,
});
}
let mut joint = [[0.; 2]; 16];
for mask in 0u16..1 << 15 {
let p = config
.phase_errors
.iter()
.enumerate()
.map(|(q, &p)| if mask & (1 << q) == 0 { 1. - p } else { p })
.product::<f64>();
let (syndrome, logical) = Self::phase_fault(mask)?;
joint[syndrome as usize][usize::from(logical)] += p;
}
let available = config.source_availability.iter().product::<f64>();
let mut observed = [[0.; 2]; 16];
let mut false_herald = 0.;
for (syndrome, weight) in joint.iter().enumerate() {
for (record, out) in observed.iter_mut().enumerate() {
let readout = config
.syndrome_readout_errors
.iter()
.enumerate()
.map(|(bit, &p)| {
if (syndrome ^ record) & (1 << bit) == 0 {
1. - p
} else {
p
}
})
.product::<f64>();
for logical in 0..2 {
out[logical] += available * weight[logical] * readout;
}
if record == 0 && syndrome != 0 {
false_herald += available * (weight[0] + weight[1]) * readout;
}
}
}
let accepted = observed[0][0] + observed[0][1];
let encoder = Self::encoding_cnots().len() as f64;
Ok(MagicDistillation {
sources_ready: available,
acceptance: accepted,
false_herald_probability: false_herald,
output_phase_error: (accepted > 0.).then(|| observed[0][1] / accepted),
true_syndrome_probabilities: joint.map(|v| v[0] + v[1]),
observed_syndrome_probabilities: observed.map(|v| v[0] + v[1]),
evaluated_fault_patterns: 1 << 15,
cost: MagicDistillationCost {
raw_source_attempts: 15,
expected_raw_states_emitted: config.source_availability.iter().sum(),
expected_code_carriers_prepared: available * 15.,
expected_clifford_ancillas_prepared: available * 18.,
expected_measurements: available * 19.,
expected_cnot_gates: available * (encoder + 47.) + accepted * encoder,
expected_hadamards: available * 13. + accepted * 4.,
expected_phase_cliffords: available * 22.5,
raw_attempts_per_output: (accepted > 0.).then(|| 15. / accepted),
},
})
}
}
fn rank(rows: &[u16]) -> usize {
let mut basis = [0u16; 15];
let mut rank = 0;
for &row in rows {
let mut value = row;
for bit in (0..15).rev() {
if value & (1 << bit) != 0 {
if basis[bit] != 0 {
value ^= basis[bit];
} else {
basis[bit] = value;
rank += 1;
break;
}
}
}
}
rank
}