Skip to main content

plonkish_cat/
wire.rs

1//! Wire newtypes: the objects and allocated resources of the circuit category.
2//!
3//! - [`Wire`]: a single wire index (identifies a field-element carrier)
4//! - [`WireCount`]: number of wires (the category's objects)
5//! - [`WireRange`]: a contiguous block of allocated wires
6//! - [`WireAllocator`]: functional state for fresh wire allocation
7
8use crate::error::Error;
9
10/// A wire index: identifies a single wire in a circuit.
11///
12/// Wires carry field elements at evaluation time.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct Wire(usize);
15
16impl Wire {
17    /// Create a new wire identifier.
18    #[must_use]
19    pub fn new(index: usize) -> Self {
20        Self(index)
21    }
22
23    /// The underlying index.
24    #[must_use]
25    pub fn index(self) -> usize {
26        self.0
27    }
28}
29
30impl core::fmt::Display for Wire {
31    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32        write!(f, "w{}", self.0)
33    }
34}
35
36/// A wire count: the number of wires in a bundle.
37///
38/// This is the "object" type in the circuit category.
39/// Morphisms go from one wire count to another.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct WireCount(usize);
42
43impl WireCount {
44    /// Create a new wire count.
45    #[must_use]
46    pub fn new(n: usize) -> Self {
47        Self(n)
48    }
49
50    /// The underlying count.
51    #[must_use]
52    pub fn count(self) -> usize {
53        self.0
54    }
55
56    /// The zero wire count (unit object in the monoidal category).
57    #[must_use]
58    pub fn zero() -> Self {
59        Self(0)
60    }
61
62    /// Tensor product: parallel composition of wire bundles.
63    #[must_use]
64    pub fn tensor(self, other: Self) -> Self {
65        Self(self.0 + other.0)
66    }
67}
68
69impl std::ops::Add for WireCount {
70    type Output = Self;
71    fn add(self, rhs: Self) -> Self {
72        self.tensor(rhs)
73    }
74}
75
76impl core::fmt::Display for WireCount {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        write!(f, "{}", self.0)
79    }
80}
81
82/// A contiguous range of wire indices.
83///
84/// Represents an allocated block of wires during interpretation.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct WireRange {
87    start: Wire,
88    count: WireCount,
89}
90
91impl WireRange {
92    /// Create a new wire range.
93    #[must_use]
94    pub fn new(start: Wire, count: WireCount) -> Self {
95        Self { start, count }
96    }
97
98    /// The first wire index in this range.
99    #[must_use]
100    pub fn start(&self) -> Wire {
101        self.start
102    }
103
104    /// The number of wires in this range.
105    #[must_use]
106    pub fn count(&self) -> WireCount {
107        self.count
108    }
109
110    /// Get the wire at a given offset within this range.
111    ///
112    /// # Errors
113    ///
114    /// Returns [`Error::WireOutOfBounds`] if `offset >= count`.
115    pub fn wire_at(&self, offset: usize) -> Result<Wire, Error> {
116        if offset < self.count.0 {
117            Ok(Wire::new(self.start.0 + offset))
118        } else {
119            Err(Error::WireOutOfBounds {
120                wire_index: self.start.0 + offset,
121                allocated: self.start.0 + self.count.0,
122            })
123        }
124    }
125}
126
127/// Wire allocation state: tracks the next available wire index.
128///
129/// Threaded through interpretation as an immutable value,
130/// producing new states via functional update.
131#[derive(Debug, Clone, Copy)]
132pub struct WireAllocator {
133    next: usize,
134}
135
136impl WireAllocator {
137    /// A fresh allocator starting at wire index 0.
138    #[must_use]
139    pub fn new() -> Self {
140        Self { next: 0 }
141    }
142
143    /// Allocate a block of wires, returning the range and a new allocator.
144    #[must_use]
145    pub fn allocate(self, count: WireCount) -> (WireRange, Self) {
146        let range = WireRange::new(Wire::new(self.next), count);
147        let next_alloc = Self {
148            next: self.next + count.count(),
149        };
150        (range, next_alloc)
151    }
152
153    /// The total number of wires allocated so far.
154    #[must_use]
155    pub fn total_allocated(self) -> usize {
156        self.next
157    }
158}
159
160impl Default for WireAllocator {
161    fn default() -> Self {
162        Self::new()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn allocate_produces_non_overlapping_ranges() {
172        let alloc = WireAllocator::new();
173        let (r1, alloc) = alloc.allocate(WireCount::new(3));
174        let (r2, _alloc) = alloc.allocate(WireCount::new(2));
175        assert_eq!(r1.start(), Wire::new(0));
176        assert_eq!(r1.count(), WireCount::new(3));
177        assert_eq!(r2.start(), Wire::new(3));
178        assert_eq!(r2.count(), WireCount::new(2));
179    }
180
181    #[test]
182    fn wire_at_in_bounds() -> Result<(), Error> {
183        let range = WireRange::new(Wire::new(5), WireCount::new(3));
184        assert_eq!(range.wire_at(0)?, Wire::new(5));
185        assert_eq!(range.wire_at(1)?, Wire::new(6));
186        assert_eq!(range.wire_at(2)?, Wire::new(7));
187        Ok(())
188    }
189
190    #[test]
191    fn wire_at_out_of_bounds() {
192        let range = WireRange::new(Wire::new(0), WireCount::new(2));
193        assert!(range.wire_at(2).is_err());
194    }
195
196    #[test]
197    fn wire_count_tensor_is_addition() {
198        let a = WireCount::new(3);
199        let b = WireCount::new(4);
200        assert_eq!(a.tensor(b), WireCount::new(7));
201        assert_eq!(a + b, WireCount::new(7));
202    }
203}