haloumi_core/slot/
cell.rs

1//! Types for handling references to cells in the PLONK table.
2
3use std::fmt;
4
5use thiserror::Error;
6
7use crate::eqv::{EqvRelation, SymbolicEqv};
8
9/// Used for comparing cells' offsets.
10#[derive(Copy, Clone, Eq, PartialEq)]
11enum Offset {
12    Rel(usize),
13    Abs(usize),
14}
15
16/// A reference to a cell in the circuit.
17#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
18pub struct CellRef {
19    col: usize,
20    base: Option<usize>,
21    offset: usize,
22}
23
24impl CellRef {
25    /// Creates a new cell reference using absolute coordinates.
26    pub fn absolute(col: usize, row: usize) -> Self {
27        Self {
28            col,
29            base: None,
30            offset: row,
31        }
32    }
33
34    /// Creates a new cell reference using coordinates relative to a row (usually the region's
35    /// first row).
36    pub fn relative(col: usize, base: usize, offset: usize) -> Self {
37        log::debug!("Creating relative reference (col: {col}, base: {base}, offset: {offset})");
38        Self {
39            col,
40            base: Some(base),
41            offset,
42        }
43    }
44
45    /// Returns the absolute row the cell points to.
46    pub fn row(&self) -> usize {
47        self.base.unwrap_or_default() + self.offset
48    }
49
50    /// Returns the offset of the cell.
51    fn offset(&self) -> Offset {
52        match self.base {
53            Some(_) => Offset::Rel(self.offset),
54            None => Offset::Abs(self.offset),
55        }
56    }
57
58    /// Returns the column number.
59    pub fn col(&self) -> usize {
60        self.col
61    }
62
63    /// Returns true if is an absolute reference.
64    pub fn is_absolute(&self) -> bool {
65        self.base.is_none()
66    }
67
68    /// Tries to convert an absolute reference into a relative reference w.r.t. the given base.
69    ///
70    /// For a non-panicking version see [`try_relativize`](CellRef::try_relativize).
71    ///
72    /// # Panics
73    ///
74    /// If the base is larger than the row number or if the reference is already relative.
75    pub fn relativize(&self, base: usize) -> Self {
76        self.try_relativize(base)
77            .map_err(|e| panic!("{e}"))
78            .unwrap()
79    }
80
81    /// Tries to convert an absolute reference into a relative reference w.r.t. the given base.
82    ///
83    /// Fails if the base is larger than the row number or if the reference is already relative.
84    pub fn try_relativize(&self, base: usize) -> Result<Self, CellError> {
85        match self.offset() {
86            Offset::Abs(offset) => {
87                if base > offset {
88                    return Err(CellError::BaseAfterOffset { base, offset });
89                }
90                let offset = offset - base;
91                Ok(Self::relative(self.col, base, offset))
92            }
93            Offset::Rel(_) => Err(CellError::AttemptedRelativizeRelativeCell),
94        }
95    }
96
97    /// Converts the cell reference to an absolute reference.
98    ///
99    /// If the reference is already absolute it does nothing.
100    pub fn absolutize(&self) -> Self {
101        Self::absolute(self.col, self.row())
102    }
103}
104
105impl fmt::Debug for CellRef {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self.base {
108            Some(base) => write!(f, "[{}, {base}(+{})]", self.col, self.offset),
109            None => write!(f, "[{}, {}]", self.col, self.offset),
110        }
111    }
112}
113
114impl fmt::Display for CellRef {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self.base {
117            Some(base) => write!(f, "[{},{base}(+{})]", self.col, self.offset),
118            None => write!(f, "[{},{}]", self.col, self.offset),
119        }
120    }
121}
122
123impl PartialEq for CellRef {
124    fn eq(&self, other: &Self) -> bool {
125        self.col() == other.col() && self.row() == other.row()
126    }
127}
128
129impl std::hash::Hash for CellRef {
130    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
131        self.col().hash(state);
132        self.row().hash(state);
133    }
134}
135
136impl EqvRelation<CellRef> for SymbolicEqv {
137    /// Two cell refs are equivalent if they point to the same absolute cell or the point to the
138    /// same relative cell regardless of their base.
139    fn equivalent(lhs: &CellRef, rhs: &CellRef) -> bool {
140        lhs.col() == rhs.col()
141            && (
142                // Either they point to the same cell
143                lhs.row() == rhs.row() ||
144                // Or they relatively point to the same cell
145                lhs.offset() == rhs.offset()
146            )
147    }
148}
149
150/// Errors related to cells.
151#[derive(Error, Copy, Clone, Debug)]
152pub enum CellError {
153    /// Happens if during [`CellRef::relativize`](crate::slot::cell::CellRef::relativize) the base
154    /// was larger than the reference's offset.
155    #[error("failed to relativize cell reference: offset {offset} points to a row before {base}")]
156    BaseAfterOffset {
157        /// Base the relativization as attempted on.
158        base: usize,
159        /// The cell's offset.
160        offset: usize,
161    },
162    /// Happens when attempting to [`CellRef::relativize`](crate::slot::cell::CellRef::relativize) a
163    /// relative reference.
164    #[error("cannot relativize a relative cell")]
165    AttemptedRelativizeRelativeCell,
166}
167
168#[cfg(test)]
169mod tests {
170    use std::hash::{DefaultHasher, Hash as _, Hasher as _};
171
172    use log::LevelFilter;
173    use quickcheck_macros::quickcheck;
174    use simplelog::{Config, TestLogger};
175
176    use super::*;
177
178    macro_rules! checked {
179        ($name:ident, $e:expr) => {
180            let Some($name) = $e else {
181                return true;
182            };
183        };
184        ($e:expr) => {
185            if let None = $e {
186                return true;
187            }
188        };
189    }
190
191    /// Tests that a relative reference and an absolute cell that point to the same cell must be
192    /// equal and any that do not are not equal.
193    #[quickcheck]
194    fn same_absolute_and_relative_equal(col: usize, base: usize, offset: usize) -> bool {
195        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
196        // Ignore tests where there's overflow
197        checked!(base_offset, base.checked_add(offset));
198        CellRef::absolute(col, base_offset) == CellRef::relative(col, base, offset)
199    }
200
201    /// Tests that a relative reference and an absolute cell that point to the same row in different
202    /// columns are not equal.
203    #[quickcheck]
204    fn diff_col_absolute_and_relative_not_equal(col: usize, base: usize, offset: usize) -> bool {
205        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
206        // Ignore tests where there's overflow
207        checked!(base_offset, base.checked_add(offset));
208        checked!(col_1, col.checked_add(1));
209        CellRef::absolute(col, base_offset) != CellRef::relative(col_1, base, offset)
210    }
211
212    /// Tests that a relative reference and an absolute cell that point to the same column in
213    /// different rows are not equal.
214    #[quickcheck]
215    fn diff_row_absolute_and_relative_not_equal_1(col: usize, base: usize, offset: usize) -> bool {
216        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
217        // Ignore tests where there's overflow
218        checked!(base_offset, base.checked_add(offset));
219        checked!(base_offset.checked_add(1));
220        CellRef::absolute(col, base_offset) != CellRef::relative(col, base + 1, offset)
221    }
222
223    /// Tests that a relative reference and an absolute cell that point to the same column in
224    /// different rows are not equal.
225    #[quickcheck]
226    fn diff_row_absolute_and_relative_not_equal_2(col: usize, base: usize, offset: usize) -> bool {
227        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
228        // Ignore tests where there's overflow
229        checked!(base_offset, base.checked_add(offset));
230        checked!(base_offset.checked_add(1));
231        CellRef::absolute(col, base_offset) != CellRef::relative(col, base, offset + 1)
232    }
233
234    fn hash(cell: CellRef) -> u64 {
235        let mut h = DefaultHasher::new();
236        cell.hash(&mut h);
237        h.finish()
238    }
239
240    /// Tests that a relative reference and an absolute cell that point to the same cell must be
241    /// equal and any that do not are not equal.
242    #[quickcheck]
243    fn same_absolute_and_relative_equal_hash(col: usize, base: usize, offset: usize) -> bool {
244        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
245        // Ignore tests where there's overflow
246        checked!(base_offset, base.checked_add(offset));
247        hash(CellRef::absolute(col, base_offset)) == hash(CellRef::relative(col, base, offset))
248    }
249
250    /// Tests that a relative reference and an absolute cell that point to the same row in different
251    /// columns are not equal.
252    #[quickcheck]
253    fn diff_col_absolute_and_relative_not_equal_hash(
254        col: usize,
255        base: usize,
256        offset: usize,
257    ) -> bool {
258        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
259        // Ignore tests where there's overflow
260        checked!(base_offset, base.checked_add(offset));
261        checked!(col_1, col.checked_add(1));
262        hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col_1, base, offset))
263    }
264
265    /// Tests that a relative reference and an absolute cell that point to the same column in
266    /// different rows are not equal.
267    #[quickcheck]
268    fn diff_row_absolute_and_relative_not_equal_1_hash(
269        col: usize,
270        base: usize,
271        offset: usize,
272    ) -> bool {
273        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
274        // Ignore tests where there's overflow
275        checked!(base_offset, base.checked_add(offset));
276        checked!(base_1, base.checked_add(1));
277        checked!(base_1.checked_add(offset));
278        hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base_1, offset))
279    }
280
281    /// Tests that a relative reference and an absolute cell that point to the same column in
282    /// different rows are not equal.
283    #[quickcheck]
284    fn diff_row_absolute_and_relative_not_equal_2_hash(
285        col: usize,
286        base: usize,
287        offset: usize,
288    ) -> bool {
289        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
290        // Ignore tests where there's overflow
291        checked!(base_offset, base.checked_add(offset));
292        checked!(base_offset.checked_add(1));
293        checked!(offset_1, offset.checked_add(1));
294        hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base, offset_1))
295    }
296
297    /// Tests that two relative references with the same column and offset are symbolically
298    /// equivalent.
299    #[quickcheck]
300    fn same_relative_sym_eqv(col: usize, base: usize, offset: usize) -> bool {
301        let _ = TestLogger::init(LevelFilter::Debug, Config::default());
302        // Ignore tests where there's overflow
303        checked!(base_offset, base.checked_add(offset));
304        checked!(base_1, base.checked_add(1));
305        checked!(base_offset.checked_add(1));
306        SymbolicEqv::equivalent(
307            &CellRef::relative(col, base_1, offset),
308            &CellRef::relative(col, base, offset),
309        )
310    }
311}