ppvm_tableau/qubit_status.rs
1// SPDX-FileCopyrightText: 2026 The PPVM Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use num::complex::Complex;
5
6use crate::data::GeneralizedTableau;
7use crate::sparsevec::SparseVector;
8use ppvm_traits::config::Config;
9
10/// Per-qubit status relative to the computational subspace.
11///
12/// Loss and leakage both take a qubit out of the computational subspace, so
13/// gates skip any status other than [`QubitStatus::Live`]. They differ only
14/// at measurement: a lost qubit reports `None`, a leaked qubit reports the
15/// pinned computational bit. Loss overwrites leakage — a leaked qubit that is
16/// later lost is [`QubitStatus::Lost`], indistinguishable from a qubit lost
17/// from the computational subspace.
18#[repr(u8)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
20pub enum QubitStatus {
21 /// Qubit is in the computational subspace; gates apply normally.
22 #[default]
23 Live = 0,
24 /// Qubit is pinned to a computational basis state. Gates skip it;
25 /// measurement returns the pinned `0`/`1`.
26 Leaked = 1,
27 /// Qubit has been lost. Gates skip it; measurement returns `None`.
28 Lost = 2,
29}
30
31impl QubitStatus {
32 /// Outside the computational subspace — lost or leaked.
33 #[inline]
34 pub const fn is_inactive(self) -> bool {
35 (self as u8) != 0
36 }
37
38 #[inline]
39 pub const fn is_lost(self) -> bool {
40 matches!(self, QubitStatus::Lost)
41 }
42
43 #[inline]
44 pub const fn is_leaked(self) -> bool {
45 matches!(self, QubitStatus::Leaked)
46 }
47}
48
49impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> GeneralizedTableau<T, I, C> {
50 /// Whether qubit `addr0` is outside the computational subspace — either lost
51 /// or leaked. Gates skip such qubits.
52 #[inline]
53 pub fn is_inactive(&self, addr0: usize) -> bool {
54 self.qubit_status[addr0].is_inactive()
55 }
56
57 /// Whether qubit `addr0` is lost. Measurement of a lost qubit returns `None`.
58 #[inline]
59 pub fn is_lost(&self, addr0: usize) -> bool {
60 self.qubit_status[addr0].is_lost()
61 }
62
63 /// Whether qubit `addr0` is leaked. Measurement of a leaked qubit returns
64 /// the pinned computational bit.
65 #[inline]
66 pub fn is_leaked(&self, addr0: usize) -> bool {
67 self.qubit_status[addr0].is_leaked()
68 }
69
70 /// Whether any qubit in `indices` is lost or leaked.
71 #[inline]
72 pub(crate) fn any_inactive(&self, indices: &[usize]) -> bool {
73 indices.iter().any(|&i| self.is_inactive(i))
74 }
75
76 /// Whether any pair has a lost or leaked control or target.
77 #[inline]
78 pub(crate) fn any_inactive_pair(&self, pairs: &[(usize, usize)]) -> bool {
79 pairs
80 .iter()
81 .any(|&(c, t)| self.is_inactive(c) || self.is_inactive(t))
82 }
83}