1use smallvec::smallvec;
8
9use crate::circuit::{Circuit, Instruction, SmallVec};
10use crate::error::{PrismError, Result};
11use crate::gates::Gate;
12use crate::sim::noise::{NoiseChannel, NoiseEvent, NoiseModel, ReadoutError};
13
14#[derive(Debug, Clone, Default)]
19pub struct GateFilter {
20 arity: Option<usize>,
21 name: Option<String>,
22 qubits: Option<Vec<usize>>,
23 targets: Option<Vec<usize>>,
24}
25
26impl GateFilter {
27 pub fn all() -> Self {
28 Self::default()
29 }
30
31 pub fn arity(mut self, arity: usize) -> Self {
33 self.arity = Some(arity);
34 self
35 }
36
37 pub fn named(mut self, name: impl Into<String>) -> Self {
44 self.name = Some(name.into());
45 self
46 }
47
48 pub fn on_qubits(mut self, qubits: impl IntoIterator<Item = usize>) -> Self {
53 let mut listed: Vec<usize> = qubits.into_iter().collect();
54 listed.sort_unstable();
55 listed.dedup();
56 self.qubits = Some(listed);
57 self
58 }
59
60 pub fn on_targets(mut self, targets: impl IntoIterator<Item = usize>) -> Self {
66 self.targets = Some(targets.into_iter().collect());
67 self
68 }
69
70 fn matches(&self, gate: &Gate, targets: &[usize]) -> bool {
71 if self.arity.is_some_and(|arity| arity != targets.len()) {
72 return false;
73 }
74 if self.name.as_deref().is_some_and(|name| name != gate.name()) {
75 return false;
76 }
77 if self
78 .targets
79 .as_deref()
80 .is_some_and(|listed| listed != targets)
81 {
82 return false;
83 }
84 true
85 }
86
87 fn allows(&self, qubit: usize) -> bool {
88 self.qubits
89 .as_ref()
90 .is_none_or(|listed| listed.binary_search(&qubit).is_ok())
91 }
92}
93
94enum Rule {
95 PerTarget {
96 filter: GateFilter,
97 channel: NoiseChannel,
98 },
99 Joint {
100 filter: GateFilter,
101 channel: NoiseChannel,
102 },
103 Crosstalk {
104 filter: GateFilter,
105 coupling: Vec<(usize, usize)>,
106 channel: NoiseChannel,
107 },
108 OverRotation {
109 filter: GateFilter,
110 relative: f64,
111 },
112 Idle {
113 channel: NoiseChannel,
114 },
115 AfterReset {
116 channel: NoiseChannel,
117 },
118 BeforeMeasure {
119 channel: NoiseChannel,
120 },
121}
122
123#[derive(Default)]
151pub struct NoiseBuilder {
152 rules: Vec<Rule>,
153 readout: Vec<(usize, ReadoutError)>,
154 uniform_readout: Option<ReadoutError>,
155}
156
157impl NoiseBuilder {
158 pub fn new() -> Self {
159 Self::default()
160 }
161
162 pub fn after_gates(mut self, filter: GateFilter, channel: NoiseChannel) -> Self {
164 self.rules.push(Rule::PerTarget { filter, channel });
165 self
166 }
167
168 pub fn after_gates_joint(mut self, filter: GateFilter, channel: NoiseChannel) -> Self {
173 self.rules.push(Rule::Joint { filter, channel });
174 self
175 }
176
177 pub fn crosstalk(
185 mut self,
186 filter: GateFilter,
187 coupling: impl IntoIterator<Item = (usize, usize)>,
188 channel: NoiseChannel,
189 ) -> Self {
190 self.rules.push(Rule::Crosstalk {
191 filter,
192 coupling: coupling.into_iter().collect(),
193 channel,
194 });
195 self
196 }
197
198 pub fn over_rotation(mut self, filter: GateFilter, relative: f64) -> Self {
207 self.rules.push(Rule::OverRotation { filter, relative });
208 self
209 }
210
211 pub fn on_idle_qubits(mut self, channel: NoiseChannel) -> Self {
225 self.rules.push(Rule::Idle { channel });
226 self
227 }
228
229 pub fn after_resets(mut self, channel: NoiseChannel) -> Self {
231 self.rules.push(Rule::AfterReset { channel });
232 self
233 }
234
235 pub fn before_measurements(mut self, channel: NoiseChannel) -> Self {
251 self.rules.push(Rule::BeforeMeasure { channel });
252 self
253 }
254
255 pub fn readout_error(mut self, bit: usize, p01: f64, p10: f64) -> Self {
258 self.readout.push((bit, ReadoutError { p01, p10 }));
259 self
260 }
261
262 pub fn uniform_readout_error(mut self, p01: f64, p10: f64) -> Self {
265 self.uniform_readout = Some(ReadoutError { p01, p10 });
266 self
267 }
268
269 pub fn build(&self, circuit: &Circuit) -> Result<NoiseModel> {
278 let mut after_gate: Vec<Vec<NoiseEvent>> = vec![Vec::new(); circuit.instructions.len()];
279
280 let idle = self
281 .rules
282 .iter()
283 .any(|rule| matches!(rule, Rule::Idle { .. }))
284 .then(|| idle_qubits_by_layer(circuit));
285 let pre_measure = self
286 .rules
287 .iter()
288 .any(|rule| matches!(rule, Rule::BeforeMeasure { .. }))
289 .then(|| pre_measure_qubits(circuit))
290 .transpose()?;
291
292 for (idx, instr) in circuit.instructions.iter().enumerate() {
293 for rule in &self.rules {
294 emit(rule, idx, instr, &idle, &pre_measure, &mut after_gate);
295 }
296 }
297
298 let mut readout = vec![self.uniform_readout.clone(); circuit.num_classical_bits];
299 for (bit, error) in &self.readout {
300 if *bit >= readout.len() {
301 return Err(PrismError::InvalidParameter {
302 message: format!(
303 "readout error on classical bit {bit} is outside the {}-bit register",
304 readout.len()
305 ),
306 });
307 }
308 readout[*bit] = Some(error.clone());
309 }
310
311 let model = NoiseModel {
312 after_gate,
313 readout,
314 };
315 model.validate_for(circuit)?;
316 Ok(model)
317 }
318}
319
320fn emit(
321 rule: &Rule,
322 idx: usize,
323 instr: &Instruction,
324 idle: &Option<Vec<Vec<usize>>>,
325 pre_measure: &Option<Vec<Option<usize>>>,
326 after_gate: &mut [Vec<NoiseEvent>],
327) {
328 let slot = &mut after_gate[idx];
329 match rule {
330 Rule::PerTarget { filter, channel } => {
331 let Instruction::Gate { gate, targets } = instr else {
332 return;
333 };
334 if !filter.matches(gate, targets) {
335 return;
336 }
337 for &qubit in targets.iter().filter(|&&q| filter.allows(q)) {
338 slot.push(NoiseEvent {
339 channel: channel.clone(),
340 qubits: smallvec![qubit],
341 });
342 }
343 }
344 Rule::Joint { filter, channel } => {
345 let Instruction::Gate { gate, targets } = instr else {
346 return;
347 };
348 if targets.len() != channel.num_qubits()
349 || !filter.matches(gate, targets)
350 || !targets.iter().all(|&q| filter.allows(q))
351 {
352 return;
353 }
354 slot.push(NoiseEvent {
355 channel: channel.clone(),
356 qubits: targets.iter().copied().collect(),
357 });
358 }
359 Rule::Crosstalk {
360 filter,
361 coupling,
362 channel,
363 } => {
364 let Instruction::Gate { gate, targets } = instr else {
365 return;
366 };
367 if !filter.matches(gate, targets) {
368 return;
369 }
370 emit_crosstalk(filter, coupling, channel, targets, slot);
371 }
372 Rule::OverRotation { filter, relative } => {
373 let Instruction::Gate { gate, targets } = instr else {
374 return;
375 };
376 if !filter.matches(gate, targets) {
377 return;
378 }
379 let Some(channel) = over_rotation_channel(gate, *relative) else {
380 return;
381 };
382 for &qubit in targets.iter().filter(|&&q| filter.allows(q)) {
383 slot.push(NoiseEvent {
384 channel: channel.clone(),
385 qubits: smallvec![qubit],
386 });
387 }
388 }
389 Rule::Idle { channel } => {
390 let Some(idle) = idle else { return };
391 for &qubit in &idle[idx] {
392 slot.push(NoiseEvent {
393 channel: channel.clone(),
394 qubits: smallvec![qubit],
395 });
396 }
397 }
398 Rule::AfterReset { channel } => {
399 if let Instruction::Reset { qubit } = instr {
400 slot.push(NoiseEvent {
401 channel: channel.clone(),
402 qubits: smallvec![*qubit],
403 });
404 }
405 }
406 Rule::BeforeMeasure { channel } => {
407 let Some(pre_measure) = pre_measure else {
408 return;
409 };
410 if let Some(qubit) = pre_measure[idx] {
411 slot.push(NoiseEvent {
412 channel: channel.clone(),
413 qubits: smallvec![qubit],
414 });
415 }
416 }
417 }
418}
419
420fn emit_crosstalk(
421 filter: &GateFilter,
422 coupling: &[(usize, usize)],
423 channel: &NoiseChannel,
424 targets: &[usize],
425 slot: &mut Vec<NoiseEvent>,
426) {
427 let mut seen: Vec<usize> = Vec::new();
428 for &target in targets.iter().filter(|&&q| filter.allows(q)) {
429 let mut spectators: Vec<usize> = coupling
430 .iter()
431 .filter_map(|&(a, b)| match (a == target, b == target) {
432 (true, false) => Some(b),
433 (false, true) => Some(a),
434 _ => None,
435 })
436 .filter(|spectator| !targets.contains(spectator))
437 .collect();
438 spectators.sort_unstable();
439 spectators.dedup();
440
441 for spectator in spectators {
442 let qubits: SmallVec<[usize; 2]> = if channel.num_qubits() == 2 {
443 smallvec![target, spectator]
444 } else {
445 if seen.contains(&spectator) {
446 continue;
447 }
448 seen.push(spectator);
449 smallvec![spectator]
450 };
451 slot.push(NoiseEvent {
452 channel: channel.clone(),
453 qubits,
454 });
455 }
456 }
457}
458
459fn over_rotation_channel(gate: &Gate, relative: f64) -> Option<NoiseChannel> {
462 let excess = match gate {
463 Gate::Rx(theta) => Gate::Rx(relative * theta),
464 Gate::Ry(theta) => Gate::Ry(relative * theta),
465 Gate::Rz(theta) => Gate::Rz(relative * theta),
466 Gate::P(theta) => Gate::P(relative * theta),
467 _ => return None,
468 };
469 Some(NoiseChannel::Custom {
470 kraus: vec![excess.matrix_2x2()],
471 })
472}
473
474fn instruction_qubits(instr: &Instruction) -> SmallVec<[usize; 4]> {
477 match instr {
478 Instruction::Gate { targets, .. } | Instruction::Conditional { targets, .. } => {
479 targets.clone()
480 }
481 Instruction::Measure { qubit, .. } | Instruction::Reset { qubit } => smallvec![*qubit],
482 Instruction::Barrier { qubits } => qubits.clone(),
483 Instruction::Region(region) => region.qubits().iter().copied().collect(),
484 }
485}
486
487fn idle_qubits_by_layer(circuit: &Circuit) -> Vec<Vec<usize>> {
494 let num_qubits = circuit.num_qubits;
495 let mut qubit_depth = vec![0usize; num_qubits];
496 let mut layers: Vec<(Vec<bool>, usize)> = Vec::new();
497
498 for (idx, instr) in circuit.instructions.iter().enumerate() {
499 let qubits = instruction_qubits(instr);
500 if qubits.is_empty() {
501 continue;
502 }
503 let layer = qubits.iter().map(|&q| qubit_depth[q]).max().unwrap_or(0);
504 if matches!(instr, Instruction::Barrier { .. }) {
505 for &qubit in &qubits {
506 qubit_depth[qubit] = layer;
507 }
508 continue;
509 }
510 while layers.len() <= layer {
511 layers.push((vec![false; num_qubits], 0));
512 }
513 let (touched, last) = &mut layers[layer];
514 for &qubit in &qubits {
515 touched[qubit] = true;
516 qubit_depth[qubit] = layer + 1;
517 }
518 *last = (*last).max(idx);
519 }
520
521 let mut idle = vec![Vec::new(); circuit.instructions.len()];
522 for (touched, last) in layers {
523 idle[last] = touched
524 .iter()
525 .enumerate()
526 .filter_map(|(qubit, &used)| (!used).then_some(qubit))
527 .collect();
528 }
529 idle
530}
531
532fn pre_measure_qubits(circuit: &Circuit) -> Result<Vec<Option<usize>>> {
535 if let Some(Instruction::Measure { .. }) = circuit.instructions.first() {
536 return Err(PrismError::InvalidParameter {
537 message: "pre-measurement noise needs a preceding instruction to attach to, and \
538 instruction 0 is a measurement; prepend a barrier"
539 .into(),
540 });
541 }
542 let mut out = vec![None; circuit.instructions.len()];
543 for (idx, instr) in circuit.instructions.iter().enumerate().skip(1) {
544 if let Instruction::Measure { qubit, .. } = instr {
545 out[idx - 1] = Some(*qubit);
546 }
547 }
548 Ok(out)
549}