use smallvec::smallvec;
use crate::circuit::{Circuit, Instruction, SmallVec};
use crate::error::{PrismError, Result};
use crate::gates::Gate;
use crate::sim::noise::{NoiseChannel, NoiseEvent, NoiseModel, ReadoutError};
#[derive(Debug, Clone, Default)]
pub struct GateFilter {
arity: Option<usize>,
name: Option<String>,
qubits: Option<Vec<usize>>,
targets: Option<Vec<usize>>,
}
impl GateFilter {
pub fn all() -> Self {
Self::default()
}
pub fn arity(mut self, arity: usize) -> Self {
self.arity = Some(arity);
self
}
pub fn named(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn on_qubits(mut self, qubits: impl IntoIterator<Item = usize>) -> Self {
let mut listed: Vec<usize> = qubits.into_iter().collect();
listed.sort_unstable();
listed.dedup();
self.qubits = Some(listed);
self
}
pub fn on_targets(mut self, targets: impl IntoIterator<Item = usize>) -> Self {
self.targets = Some(targets.into_iter().collect());
self
}
fn matches(&self, gate: &Gate, targets: &[usize]) -> bool {
if self.arity.is_some_and(|arity| arity != targets.len()) {
return false;
}
if self.name.as_deref().is_some_and(|name| name != gate.name()) {
return false;
}
if self
.targets
.as_deref()
.is_some_and(|listed| listed != targets)
{
return false;
}
true
}
fn allows(&self, qubit: usize) -> bool {
self.qubits
.as_ref()
.is_none_or(|listed| listed.binary_search(&qubit).is_ok())
}
}
enum Rule {
PerTarget {
filter: GateFilter,
channel: NoiseChannel,
},
Joint {
filter: GateFilter,
channel: NoiseChannel,
},
Crosstalk {
filter: GateFilter,
coupling: Vec<(usize, usize)>,
channel: NoiseChannel,
},
OverRotation {
filter: GateFilter,
relative: f64,
},
Idle {
channel: NoiseChannel,
},
AfterReset {
channel: NoiseChannel,
},
BeforeMeasure {
channel: NoiseChannel,
},
}
#[derive(Default)]
pub struct NoiseBuilder {
rules: Vec<Rule>,
readout: Vec<(usize, ReadoutError)>,
uniform_readout: Option<ReadoutError>,
}
impl NoiseBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn after_gates(mut self, filter: GateFilter, channel: NoiseChannel) -> Self {
self.rules.push(Rule::PerTarget { filter, channel });
self
}
pub fn after_gates_joint(mut self, filter: GateFilter, channel: NoiseChannel) -> Self {
self.rules.push(Rule::Joint { filter, channel });
self
}
pub fn crosstalk(
mut self,
filter: GateFilter,
coupling: impl IntoIterator<Item = (usize, usize)>,
channel: NoiseChannel,
) -> Self {
self.rules.push(Rule::Crosstalk {
filter,
coupling: coupling.into_iter().collect(),
channel,
});
self
}
pub fn over_rotation(mut self, filter: GateFilter, relative: f64) -> Self {
self.rules.push(Rule::OverRotation { filter, relative });
self
}
pub fn on_idle_qubits(mut self, channel: NoiseChannel) -> Self {
self.rules.push(Rule::Idle { channel });
self
}
pub fn after_resets(mut self, channel: NoiseChannel) -> Self {
self.rules.push(Rule::AfterReset { channel });
self
}
pub fn before_measurements(mut self, channel: NoiseChannel) -> Self {
self.rules.push(Rule::BeforeMeasure { channel });
self
}
pub fn readout_error(mut self, bit: usize, p01: f64, p10: f64) -> Self {
self.readout.push((bit, ReadoutError { p01, p10 }));
self
}
pub fn uniform_readout_error(mut self, p01: f64, p10: f64) -> Self {
self.uniform_readout = Some(ReadoutError { p01, p10 });
self
}
pub fn build(&self, circuit: &Circuit) -> Result<NoiseModel> {
let mut after_gate: Vec<Vec<NoiseEvent>> = vec![Vec::new(); circuit.instructions.len()];
let idle = self
.rules
.iter()
.any(|rule| matches!(rule, Rule::Idle { .. }))
.then(|| idle_qubits_by_layer(circuit));
let pre_measure = self
.rules
.iter()
.any(|rule| matches!(rule, Rule::BeforeMeasure { .. }))
.then(|| pre_measure_qubits(circuit))
.transpose()?;
for (idx, instr) in circuit.instructions.iter().enumerate() {
for rule in &self.rules {
emit(rule, idx, instr, &idle, &pre_measure, &mut after_gate);
}
}
let mut readout = vec![self.uniform_readout.clone(); circuit.num_classical_bits];
for (bit, error) in &self.readout {
if *bit >= readout.len() {
return Err(PrismError::InvalidParameter {
message: format!(
"readout error on classical bit {bit} is outside the {}-bit register",
readout.len()
),
});
}
readout[*bit] = Some(error.clone());
}
let model = NoiseModel {
after_gate,
readout,
};
model.validate_for(circuit)?;
Ok(model)
}
}
fn emit(
rule: &Rule,
idx: usize,
instr: &Instruction,
idle: &Option<Vec<Vec<usize>>>,
pre_measure: &Option<Vec<Option<usize>>>,
after_gate: &mut [Vec<NoiseEvent>],
) {
let slot = &mut after_gate[idx];
match rule {
Rule::PerTarget { filter, channel } => {
let Instruction::Gate { gate, targets } = instr else {
return;
};
if !filter.matches(gate, targets) {
return;
}
for &qubit in targets.iter().filter(|&&q| filter.allows(q)) {
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: smallvec![qubit],
});
}
}
Rule::Joint { filter, channel } => {
let Instruction::Gate { gate, targets } = instr else {
return;
};
if targets.len() != channel.num_qubits()
|| !filter.matches(gate, targets)
|| !targets.iter().all(|&q| filter.allows(q))
{
return;
}
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: targets.iter().copied().collect(),
});
}
Rule::Crosstalk {
filter,
coupling,
channel,
} => {
let Instruction::Gate { gate, targets } = instr else {
return;
};
if !filter.matches(gate, targets) {
return;
}
emit_crosstalk(filter, coupling, channel, targets, slot);
}
Rule::OverRotation { filter, relative } => {
let Instruction::Gate { gate, targets } = instr else {
return;
};
if !filter.matches(gate, targets) {
return;
}
let Some(channel) = over_rotation_channel(gate, *relative) else {
return;
};
for &qubit in targets.iter().filter(|&&q| filter.allows(q)) {
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: smallvec![qubit],
});
}
}
Rule::Idle { channel } => {
let Some(idle) = idle else { return };
for &qubit in &idle[idx] {
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: smallvec![qubit],
});
}
}
Rule::AfterReset { channel } => {
if let Instruction::Reset { qubit } = instr {
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: smallvec![*qubit],
});
}
}
Rule::BeforeMeasure { channel } => {
let Some(pre_measure) = pre_measure else {
return;
};
if let Some(qubit) = pre_measure[idx] {
slot.push(NoiseEvent {
channel: channel.clone(),
qubits: smallvec![qubit],
});
}
}
}
}
fn emit_crosstalk(
filter: &GateFilter,
coupling: &[(usize, usize)],
channel: &NoiseChannel,
targets: &[usize],
slot: &mut Vec<NoiseEvent>,
) {
let mut seen: Vec<usize> = Vec::new();
for &target in targets.iter().filter(|&&q| filter.allows(q)) {
let mut spectators: Vec<usize> = coupling
.iter()
.filter_map(|&(a, b)| match (a == target, b == target) {
(true, false) => Some(b),
(false, true) => Some(a),
_ => None,
})
.filter(|spectator| !targets.contains(spectator))
.collect();
spectators.sort_unstable();
spectators.dedup();
for spectator in spectators {
let qubits: SmallVec<[usize; 2]> = if channel.num_qubits() == 2 {
smallvec![target, spectator]
} else {
if seen.contains(&spectator) {
continue;
}
seen.push(spectator);
smallvec![spectator]
};
slot.push(NoiseEvent {
channel: channel.clone(),
qubits,
});
}
}
}
fn over_rotation_channel(gate: &Gate, relative: f64) -> Option<NoiseChannel> {
let excess = match gate {
Gate::Rx(theta) => Gate::Rx(relative * theta),
Gate::Ry(theta) => Gate::Ry(relative * theta),
Gate::Rz(theta) => Gate::Rz(relative * theta),
Gate::P(theta) => Gate::P(relative * theta),
_ => return None,
};
Some(NoiseChannel::Custom {
kraus: vec![excess.matrix_2x2()],
})
}
fn instruction_qubits(instr: &Instruction) -> SmallVec<[usize; 4]> {
match instr {
Instruction::Gate { targets, .. } | Instruction::Conditional { targets, .. } => {
targets.clone()
}
Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => smallvec![*qubit],
Instruction::Barrier { qubits } | Instruction::Save { qubits, .. } => qubits.clone(),
Instruction::Region(region) => region.qubits().iter().copied().collect(),
}
}
fn idle_qubits_by_layer(circuit: &Circuit) -> Vec<Vec<usize>> {
let num_qubits = circuit.num_qubits;
let mut qubit_depth = vec![0usize; num_qubits];
let mut layers: Vec<(Vec<bool>, usize)> = Vec::new();
for (idx, instr) in circuit.instructions.iter().enumerate() {
let qubits = instruction_qubits(instr);
if qubits.is_empty() {
continue;
}
let layer = qubits.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
if matches!(instr, Instruction::Barrier { .. }) {
for &qubit in &qubits {
qubit_depth[qubit] = layer;
}
continue;
}
while layers.len() <= layer {
layers.push((vec![false; num_qubits], 0));
}
let (touched, last) = &mut layers[layer];
for &qubit in &qubits {
touched[qubit] = true;
qubit_depth[qubit] = layer + 1;
}
*last = (*last).max(idx);
}
let mut idle = vec![Vec::new(); circuit.instructions.len()];
for (touched, last) in layers {
idle[last] = touched
.iter()
.enumerate()
.filter_map(|(qubit, &used)| (!used).then_some(qubit))
.collect();
}
idle
}
fn pre_measure_qubits(circuit: &Circuit) -> Result<Vec<Option<usize>>> {
if let Some(Instruction::Measure { .. }) = circuit.instructions.first() {
return Err(PrismError::InvalidParameter {
message: "pre-measurement noise needs a preceding instruction to attach to, and \
instruction 0 is a measurement; prepend a barrier"
.into(),
});
}
let mut out = vec![None; circuit.instructions.len()];
for (idx, instr) in circuit.instructions.iter().enumerate().skip(1) {
if let Instruction::Measure { qubit, .. } = instr {
out[idx - 1] = Some(*qubit);
}
}
Ok(out)
}