Skip to main content

ppvm_pauli_word/pattern/
contains.rs

1// SPDX-FileCopyrightText: 2026 The PPVM Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use super::data::{Decorated, NotIdentity, OpPattern, PauliPattern};
5use ppvm_traits::char::Pauli;
6use ppvm_traits::traits::PauliIter;
7use std::iter::Peekable;
8
9/// Membership test for pattern types.
10///
11/// Implemented by Pauli patterns over symbols (`Contains<Pauli>`) and
12/// over whole words (`Contains<W: PauliIter>`); used by the masking
13/// machinery to ask "does this pattern accept this concrete word?"
14pub trait Contains<T> {
15    /// `true` if `item` matches this pattern.
16    fn contains(&self, item: &T) -> bool;
17}
18
19impl Contains<Pauli> for NotIdentity {
20    fn contains(&self, item: &Pauli) -> bool {
21        (*self as u8) == (*item as u8)
22    }
23}
24
25impl Contains<Pauli> for OpPattern {
26    fn contains(&self, item: &Pauli) -> bool {
27        match self {
28            OpPattern::Identity => *item == Pauli::I,
29            OpPattern::Single(op) => op.contains(item),
30            OpPattern::Double(left, right) => left.contains(item) || right.contains(item),
31            OpPattern::AnyNonIdentity => {
32                *item == Pauli::X || *item == Pauli::Y || *item == Pauli::Z
33            }
34            OpPattern::SingleOrIdentity(op) => op.contains(item) || *item == Pauli::I,
35            OpPattern::DoubleOrIdentity(left, right) => {
36                left.contains(item) || right.contains(item) || *item == Pauli::I
37            }
38            OpPattern::AnyPauliOrIdentity => *item == Pauli::I,
39        }
40    }
41}
42
43enum Step {
44    NextPattern,
45    DontMatch,
46}
47
48fn match_position<I: Iterator<Item = (usize, Pauli)>>(
49    chars: &mut Peekable<I>,
50    pattern: &OpPattern,
51    pos: &usize,
52) -> Step {
53    for (ch_pos, ch) in chars.by_ref() {
54        if ch_pos == *pos {
55            if pattern.contains(&ch) {
56                return Step::NextPattern;
57            } else {
58                return Step::DontMatch;
59            }
60        } else if ch == Pauli::I {
61            continue;
62        } else {
63            // there are non-identity
64            // before matching this pattern
65            return Step::DontMatch;
66        }
67    }
68    // all identity and length < pos
69    Step::DontMatch
70}
71
72fn match_star<I: Iterator<Item = (usize, Pauli)>>(
73    chars: &mut Peekable<I>,
74    pattern: &OpPattern,
75) -> Step {
76    while let Some((_, ch)) = chars.peek() {
77        if pattern.contains(ch) {
78            chars.next();
79            continue;
80        } else {
81            // doesn't match but ok
82            // match next pattern
83            return Step::NextPattern;
84        }
85    }
86    // exhausted all Pauli all matches
87    Step::NextPattern
88}
89
90fn match_repeat<I: Iterator<Item = (usize, Pauli)>>(
91    chars: &mut Peekable<I>,
92    pattern: &OpPattern,
93    count: usize,
94) -> Step {
95    let mut matched = 0;
96    while let Some((_, ch)) = chars.peek() {
97        if pattern.contains(ch) {
98            chars.next();
99            matched += 1;
100            if matched == count {
101                return Step::NextPattern;
102            }
103        } else {
104            // doesn't match
105            if matched < count {
106                return Step::DontMatch;
107            } else {
108                return Step::NextPattern;
109            }
110        }
111    }
112    // exhausted all Pauli all matches
113    if matched == count {
114        Step::NextPattern
115    } else {
116        Step::DontMatch
117    }
118}
119
120impl<W> Contains<W> for PauliPattern
121where
122    W: PauliIter,
123{
124    fn contains(&self, item: &W) -> bool {
125        let mut chars = item.iter().enumerate().peekable();
126        let patterns = self.iter();
127        for current in patterns {
128            let step = match current {
129                Decorated::Position(op, pos) => match_position(&mut chars, op, pos),
130                Decorated::Star(op) => match_star(&mut chars, op),
131                Decorated::Repeat(op, count) => match_repeat(&mut chars, op, *count),
132            };
133
134            match step {
135                Step::NextPattern => continue,
136                Step::DontMatch => return false,
137            }
138        }
139
140        // check if there are remaining non-ident in `item`
141        for (_, ch) in chars {
142            if ch == Pauli::I {
143                continue;
144            } else {
145                // found a non-identity
146                return false;
147            }
148        }
149        true
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::word::PauliWord;
157
158    #[test]
159    fn test_match_not_ident() {
160        assert!(NotIdentity::X.contains(&Pauli::X));
161        assert!(NotIdentity::Y.contains(&Pauli::Y));
162        assert!(NotIdentity::Z.contains(&Pauli::Z));
163    }
164
165    #[test]
166    fn test_match() {
167        let pat = PauliPattern::parse("X0Y1Z2").unwrap();
168        let word: PauliWord<u64> = "XYZ".into();
169        assert!(pat.contains(&word));
170
171        let pat = PauliPattern::parse("X?0Y1Z2").unwrap();
172        let word: PauliWord<u64> = "XYZ".into();
173        assert!(pat.contains(&word));
174        let word: PauliWord<u64> = "IYZ".into();
175        assert!(pat.contains(&word));
176
177        let pat = PauliPattern::parse("[XY]0Y1Z2").unwrap();
178        let word: PauliWord<u64> = "XYZ".into();
179        assert!(pat.contains(&word));
180        let word: PauliWord<u64> = "YYZ".into();
181        assert!(pat.contains(&word));
182
183        let pat = PauliPattern::parse("[XY]?0Y1Z2").unwrap();
184        let word: PauliWord<u64> = "XYZ".into();
185        assert!(pat.contains(&word));
186        let word: PauliWord<u64> = "YYZ".into();
187        assert!(pat.contains(&word));
188        let word: PauliWord<u64> = "IYZ".into();
189        assert!(pat.contains(&word));
190
191        let pat = PauliPattern::parse("[XY]?*").unwrap();
192        let word: PauliWord<u64> = "XYX".into();
193        assert!(pat.contains(&word));
194        let word: PauliWord<u64> = "YYX".into();
195        assert!(pat.contains(&word));
196        let word: PauliWord<u64> = "IYX".into();
197        assert!(pat.contains(&word));
198
199        let pat = PauliPattern::parse("[XY]?{2}Z2").unwrap();
200        let word: PauliWord<u64> = "XYZ".into();
201        assert!(pat.contains(&word));
202        let word: PauliWord<u64> = "YYZ".into();
203        assert!(pat.contains(&word));
204        let word: PauliWord<u64> = "IYZ".into();
205        assert!(pat.contains(&word));
206    }
207
208    #[test]
209    fn test_not_match() {
210        let pat: PauliPattern = "Z?*".into();
211        let word: PauliWord<u64> = "XYY".into();
212        assert!(!pat.contains(&word));
213    }
214}