extern crate alloc;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ZxColor {
Z,
X,
}
impl ZxColor {
#[must_use]
#[inline]
pub const fn flip(self) -> Self {
match self {
Self::Z => Self::X,
Self::X => Self::Z,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZxSpider {
pub color: ZxColor,
pub phase_num: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZxDiagram {
pub phase_denom: u32,
pub spiders: Vec<ZxSpider>,
pub edges: Vec<(u32, u32)>,
}
#[must_use]
pub fn apply_spider_fusion(mut diagram: ZxDiagram) -> ZxDiagram {
loop {
let merge_pair = diagram.edges.iter().copied().find(|(u, v)| {
u != v && diagram.spiders[*u as usize].color == diagram.spiders[*v as usize].color
});
let Some((u, v)) = merge_pair else {
break;
};
let combined = (diagram.spiders[u as usize].phase_num
+ diagram.spiders[v as usize].phase_num)
% diagram.phase_denom;
diagram.spiders[u as usize].phase_num = combined;
let mut next_edges = Vec::with_capacity(diagram.edges.len() - 1);
for &(a, b) in &diagram.edges {
if (a == u && b == v) || (a == v && b == u) {
continue;
}
let new_a = if a == v { u } else { a };
let new_b = if b == v { u } else { b };
next_edges.push((new_a, new_b));
}
diagram.edges = next_edges;
diagram.spiders.remove(v as usize);
for e in &mut diagram.edges {
if e.0 > v {
e.0 -= 1;
}
if e.1 > v {
e.1 -= 1;
}
}
}
diagram
}
#[must_use]
pub fn apply_identity_removal(mut diagram: ZxDiagram) -> ZxDiagram {
loop {
let mut removable: Option<u32> = None;
for v in 0..diagram.spiders.len() {
let s = diagram.spiders[v];
if s.phase_num != 0 {
continue;
}
let neighbors: Vec<u32> = diagram
.edges
.iter()
.filter_map(|&(a, b)| {
if a as usize == v {
Some(b)
} else if b as usize == v {
Some(a)
} else {
None
}
})
.collect();
if neighbors.len() != 2 {
continue;
}
let color_match = neighbors
.iter()
.all(|&n| diagram.spiders[n as usize].color == s.color);
if color_match {
removable = Some(v as u32);
break;
}
}
let Some(v) = removable else { break };
let mut next_edges = Vec::new();
let mut endpoints = Vec::new();
for &(a, b) in &diagram.edges {
if a == v {
endpoints.push(b);
} else if b == v {
endpoints.push(a);
} else {
next_edges.push((a, b));
}
}
if endpoints.len() == 2 {
next_edges.push((endpoints[0], endpoints[1]));
}
diagram.edges = next_edges;
diagram.spiders.remove(v as usize);
for e in &mut diagram.edges {
if e.0 > v {
e.0 -= 1;
}
if e.1 > v {
e.1 -= 1;
}
}
}
diagram
}
pub fn apply_color_change(diagram: &mut ZxDiagram, v: u32) {
let s = &mut diagram.spiders[v as usize];
s.color = s.color.flip();
}
#[cfg(test)]
mod tests {
use super::*;
fn z(phase: u32) -> ZxSpider {
ZxSpider {
color: ZxColor::Z,
phase_num: phase,
}
}
fn x(phase: u32) -> ZxSpider {
ZxSpider {
color: ZxColor::X,
phase_num: phase,
}
}
#[test]
fn fusion_merges_two_z_spiders() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(3)],
edges: vec![(0, 1)],
};
let out = apply_spider_fusion(d);
assert_eq!(out.spiders.len(), 1);
assert_eq!(out.spiders[0].phase_num, 4);
assert!(out.edges.is_empty());
}
#[test]
fn fusion_does_not_merge_cross_color() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), x(2)],
edges: vec![(0, 1)],
};
let out = apply_spider_fusion(d.clone());
assert_eq!(out, d);
}
#[test]
fn fusion_phase_wraps_mod_denom() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(5), z(5)],
edges: vec![(0, 1)],
};
let out = apply_spider_fusion(d);
assert_eq!(out.spiders[0].phase_num, 2);
}
#[test]
fn fusion_chain_collapses_to_one() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(2), z(3)],
edges: vec![(0, 1), (1, 2)],
};
let out = apply_spider_fusion(d);
assert_eq!(out.spiders.len(), 1);
assert_eq!(out.spiders[0].phase_num, 6);
}
#[test]
fn identity_removal_drops_phase_zero_degree_two() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(0), z(2)],
edges: vec![(0, 1), (1, 2)],
};
let out = apply_identity_removal(d);
assert_eq!(out.spiders.len(), 2);
assert_eq!(out.edges, vec![(0, 1)]);
}
#[test]
fn identity_removal_keeps_nonzero_phase() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(1), z(2)],
edges: vec![(0, 1), (1, 2)],
};
let out = apply_identity_removal(d.clone());
assert_eq!(out, d);
}
#[test]
fn identity_removal_iterates_to_fixpoint() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(0), z(0), z(2)],
edges: vec![(0, 1), (1, 2), (2, 3)],
};
let out = apply_identity_removal(d);
assert_eq!(out.spiders.len(), 2);
assert_eq!(out.edges.len(), 1);
}
#[test]
fn color_change_flips_color_keeps_phase() {
let mut d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(3)],
edges: vec![],
};
apply_color_change(&mut d, 0);
assert_eq!(d.spiders[0].color, ZxColor::X);
assert_eq!(d.spiders[0].phase_num, 3);
apply_color_change(&mut d, 0);
assert_eq!(d.spiders[0].color, ZxColor::Z);
}
#[test]
fn mixed_diagram_preserves_cross_color_structure() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1), z(2), x(3)],
edges: vec![(0, 1), (1, 2)],
};
let out = apply_spider_fusion(d);
assert_eq!(out.spiders.len(), 2);
assert_eq!(out.edges.len(), 1);
}
#[test]
fn self_loop_does_not_fuse() {
let d = ZxDiagram {
phase_denom: 8,
spiders: vec![z(1)],
edges: vec![(0, 0)],
};
let out = apply_spider_fusion(d.clone());
assert_eq!(out, d);
}
}