Skip to main content

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}