use std::fmt::{self, Display, Formatter};
use std::ops::Mul;
use std::str::FromStr;
#[derive(Clone)]
pub struct Flow {
text: String,
input: crate::PauliString,
output: crate::PauliString,
measurements: Vec<i32>,
observables: Vec<u64>,
}
impl PartialEq for Flow {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
}
}
impl Eq for Flow {}
impl PartialOrd for Flow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Flow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.text.cmp(&other.text)
}
}
impl std::hash::Hash for Flow {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.text.hash(state);
}
}
impl Flow {
pub(crate) fn from_canonical_text(text: String) -> Self {
let (input, output, measurements, observables) = parse_flow_text(&text);
Self {
text,
input,
output,
measurements,
observables,
}
}
pub fn new(text: &str) -> crate::Result<Self> {
stim_cxx::canonicalize_flow_text(text)
.map(Self::from_canonical_text)
.map_err(crate::StimError::from)
}
#[must_use]
pub fn input(&self) -> &crate::PauliString {
&self.input
}
#[must_use]
pub fn output(&self) -> &crate::PauliString {
&self.output
}
#[must_use]
pub fn measurements(&self) -> &[i32] {
&self.measurements
}
#[must_use]
pub fn included_observables(&self) -> &[u64] {
&self.observables
}
}
impl FromStr for Flow {
type Err = crate::StimError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl Display for Flow {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.text)
}
}
impl fmt::Debug for Flow {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "stim::Flow({:?})", self.text)
}
}
impl Mul for Flow {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let text = stim_cxx::multiply_flow_texts(&self.text, &rhs.text)
.expect("flow multiplication between previously valid canonical flows should succeed");
Self::from_canonical_text(text)
}
}
fn parse_flow_text(text: &str) -> (crate::PauliString, crate::PauliString, Vec<i32>, Vec<u64>) {
let (input_text, rest) = text
.split_once(" -> ")
.expect("canonical flow text should contain an arrow");
let input = parse_flow_pauli(input_text);
let mut output = crate::PauliString::new(0);
let mut measurements = Vec::new();
let mut observables = Vec::new();
for segment in rest.split(" xor ") {
if let Some(value) = segment
.strip_prefix("rec[")
.and_then(|tail| tail.strip_suffix(']'))
{
measurements.push(
value
.parse::<i32>()
.expect("canonical rec target should contain an integer"),
);
} else if let Some(value) = segment
.strip_prefix("obs[")
.and_then(|tail| tail.strip_suffix(']'))
{
observables.push(
value
.parse::<u64>()
.expect("canonical obs target should contain an integer"),
);
} else {
output = parse_flow_pauli(segment);
}
}
(input, output, measurements, observables)
}
fn parse_flow_pauli(text: &str) -> crate::PauliString {
if text == "1" {
crate::PauliString::new(0)
} else {
text.parse::<crate::PauliString>()
.expect("canonical flow pauli term should be parseable as a PauliString")
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::Flow;
#[test]
fn flow_from_text_canonicalizes_documented_examples() {
assert_eq!(
Flow::new("X2 -> -Y2*Z4 xor rec[-1]").unwrap().to_string(),
"__X -> -__Y_Z xor rec[-1]"
);
assert_eq!(
Flow::new("Z -> 1 xor rec[-1]").unwrap().to_string(),
"Z -> rec[-1]"
);
assert_eq!(
Flow::new("X2 -> Y2*Y2 xor rec[-2] xor rec[-2]")
.unwrap()
.to_string(),
"__X -> ___"
);
assert_eq!(
Flow::new("X -> Y xor obs[3] xor obs[3] xor obs[3]")
.unwrap()
.to_string(),
"X -> Y xor obs[3]"
);
}
#[test]
fn flow_display_debug_and_from_str_match() {
let flow = Flow::from_str("X2 -> obs[3]").unwrap();
assert_eq!(flow.to_string(), "__X -> obs[3]");
assert_eq!(format!("{flow:?}"), "stim::Flow(\"__X -> obs[3]\")");
assert_eq!(Flow::new("X2 -> obs[3]").unwrap(), flow);
}
#[test]
fn flow_mul_matches_documented_examples() {
assert_eq!(
Flow::new("X -> X").unwrap() * Flow::new("Z -> Z").unwrap(),
Flow::new("Y -> Y").unwrap()
);
assert_eq!(
Flow::new("1 -> XX").unwrap() * Flow::new("1 -> ZZ").unwrap(),
Flow::new("1 -> -YY").unwrap()
);
assert_eq!(
Flow::new("X -> rec[-1]").unwrap() * Flow::new("X -> rec[-2]").unwrap(),
Flow::new("_ -> rec[-2] xor rec[-1]").unwrap()
);
}
#[test]
fn flow_copy_accessors_match_documented_examples() {
let flow = Flow::new("X -> Y xor obs[3]").unwrap();
assert_eq!(flow.input(), &"X".parse::<crate::PauliString>().unwrap());
assert_eq!(flow.output(), &"Y".parse::<crate::PauliString>().unwrap());
assert_eq!(flow.included_observables(), &[3]);
let flow = Flow::new("X -> rec[-1]").unwrap();
assert_eq!(flow.output(), &crate::PauliString::new(0));
assert_eq!(flow.measurements(), &[-1]);
let flow = Flow::new("1 -> X xor rec[-1] xor obs[2]").unwrap();
assert_eq!(flow.input(), &crate::PauliString::new(0));
assert_eq!(flow.output(), &"X".parse::<crate::PauliString>().unwrap());
assert_eq!(flow.measurements(), &[-1]);
assert_eq!(flow.included_observables(), &[2]);
}
#[test]
fn flow_identity_traits_delegate_to_canonical_text() {
let x = Flow::new("X -> Y").unwrap();
let z = Flow::new("Z -> Y").unwrap();
assert_eq!(x.partial_cmp(&z), Some(std::cmp::Ordering::Less));
let ordered = [z.clone(), x.clone()]
.into_iter()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(ordered, vec![x.clone(), z.clone()]);
let hashed = [x.clone(), x, z]
.into_iter()
.collect::<std::collections::HashSet<_>>();
assert_eq!(hashed.len(), 2);
}
}