Skip to main content

machine_cat/
fibonacci.rs

1//! Fibonacci AIR: a concrete example of the [`Air`] trait.
2//!
3//! Proves correct computation of the Fibonacci sequence.
4//! The trace has 2 columns (`a`, `b`) and 2 transition constraints:
5//!
6//! - `next_a - current_b = 0`
7//! - `next_b - current_a - current_b = 0`
8//!
9//! Given initial values `(a_0, b_0)`, the trace computes:
10//!
11//! | Row | a | b |
12//! |-----|---|---|
13//! | 0   | a_0 | b_0 |
14//! | 1   | b_0 | a_0 + b_0 |
15//! | 2   | a_0 + b_0 | a_0 + 2*b_0 |
16//! | ... | ... | ... |
17
18use crate::air::Air;
19use crate::air_expr::AirExpr;
20use crate::column::{Column, ColumnCount};
21use crate::error::Error;
22use crate::trace::Trace;
23use field_cat::Field;
24
25/// Number of computation steps (rows = steps + 1).
26///
27/// # Examples
28///
29/// ```
30/// use machine_cat::StepCount;
31///
32/// let steps = StepCount::new(7);
33/// assert_eq!(steps.count(), 7);
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct StepCount(usize);
37
38impl StepCount {
39    /// Create a new step count.
40    #[must_use]
41    pub fn new(n: usize) -> Self {
42        Self(n)
43    }
44
45    /// The underlying count.
46    #[must_use]
47    pub fn count(self) -> usize {
48        self.0
49    }
50}
51
52/// Input to the Fibonacci AIR: initial values and step count.
53///
54/// # Examples
55///
56/// ```
57/// use field_cat::F101;
58/// use machine_cat::{FibonacciInput, StepCount};
59///
60/// let input = FibonacciInput::new(F101::new(1), F101::new(1), StepCount::new(7));
61/// ```
62#[derive(Debug, Clone)]
63pub struct FibonacciInput<F: Field> {
64    initial_a: F,
65    initial_b: F,
66    num_steps: StepCount,
67}
68
69impl<F: Field> FibonacciInput<F> {
70    /// Create a Fibonacci input.
71    #[must_use]
72    pub fn new(initial_a: F, initial_b: F, num_steps: StepCount) -> Self {
73        Self {
74            initial_a,
75            initial_b,
76            num_steps,
77        }
78    }
79
80    /// The initial value of column `a`.
81    #[must_use]
82    pub fn initial_a(&self) -> &F {
83        &self.initial_a
84    }
85
86    /// The initial value of column `b`.
87    #[must_use]
88    pub fn initial_b(&self) -> &F {
89        &self.initial_b
90    }
91
92    /// The number of computation steps.
93    #[must_use]
94    pub fn num_steps(&self) -> StepCount {
95        self.num_steps
96    }
97}
98
99/// The Fibonacci AIR.
100///
101/// Two columns (`a` = column 0, `b` = column 1) with transition
102/// constraints ensuring each row is the Fibonacci successor of
103/// the previous row.
104///
105/// # Examples
106///
107/// ```
108/// use field_cat::{F101, Field};
109/// use machine_cat::{Air, FibonacciAir, FibonacciInput, StepCount};
110///
111/// let air = FibonacciAir;
112/// let input = FibonacciInput::new(
113///     F101::new(1), F101::new(1), StepCount::new(3),
114/// );
115/// let trace = air.generate_trace(&input)?;
116///
117/// // 4 rows: (1,1), (1,2), (2,3), (3,5)
118/// assert_eq!(trace.row_count().count(), 4);
119/// # Ok::<(), machine_cat::Error>(())
120/// ```
121#[derive(Debug, Clone, Copy)]
122pub struct FibonacciAir;
123
124impl<F: Field> Air<F> for FibonacciAir {
125    type Input = FibonacciInput<F>;
126
127    fn column_count(&self) -> ColumnCount {
128        ColumnCount::new(2)
129    }
130
131    fn constraints(&self) -> Vec<AirExpr<F>> {
132        let current_a = AirExpr::current(Column::new(0));
133        let current_b = AirExpr::current(Column::new(1));
134        let next_a = AirExpr::next(Column::new(0));
135        let next_b = AirExpr::next(Column::new(1));
136
137        vec![
138            // next_a - current_b = 0
139            next_a - current_b.clone(),
140            // next_b - current_a - current_b = 0
141            next_b - current_a - current_b,
142        ]
143    }
144
145    fn generate_trace(&self, input: &FibonacciInput<F>) -> Result<Trace<F>, Error> {
146        let num_rows = input.num_steps.0 + 1;
147        // Build rows via successors: (a, b) -> (b, a+b).
148        let rows: Vec<Vec<F>> = std::iter::successors(
149            Some((input.initial_a.clone(), input.initial_b.clone())),
150            |prev| Some((prev.1.clone(), prev.0.clone() + prev.1.clone())),
151        )
152        .take(num_rows)
153        .map(|(a, b)| vec![a, b])
154        .collect();
155
156        Trace::from_rows(ColumnCount::new(2), &rows)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::column::Column;
164    use field_cat::F101;
165
166    #[test]
167    fn fibonacci_trace_correctness() -> Result<(), Error> {
168        let air = FibonacciAir;
169        let input = FibonacciInput::new(F101::new(1), F101::new(1), StepCount::new(7));
170        let trace = air.generate_trace(&input)?;
171
172        // 8 rows: (1,1), (1,2), (2,3), (3,5), (5,8), (8,13), (13,21), (21,34)
173        assert_eq!(trace.row_count().count(), 8);
174        assert_eq!(trace.get(0, Column::new(0))?, F101::new(1));
175        assert_eq!(trace.get(0, Column::new(1))?, F101::new(1));
176        assert_eq!(trace.get(7, Column::new(0))?, F101::new(21));
177        assert_eq!(trace.get(7, Column::new(1))?, F101::new(34));
178        Ok(())
179    }
180
181    #[test]
182    fn fibonacci_constraints_satisfied() -> Result<(), Error> {
183        let air = FibonacciAir;
184        let input = FibonacciInput::new(F101::new(1), F101::new(1), StepCount::new(7));
185        let trace = air.generate_trace(&input)?;
186        let constraints = air.constraints();
187
188        // Check constraints at every row pair.
189        (0..trace.row_count().count() - 1).try_for_each(|row| {
190            let assign = trace.row_pair_assignment(row)?;
191            constraints.iter().try_for_each(|c| {
192                let val = c.evaluate(&assign)?;
193                if val == F101::zero() {
194                    Ok(())
195                } else {
196                    Err(Error::UnsatisfiedAirConstraint { row })
197                }
198            })
199        })
200    }
201
202    #[test]
203    fn fibonacci_single_step() -> Result<(), Error> {
204        let air = FibonacciAir;
205        let input = FibonacciInput::new(F101::new(3), F101::new(5), StepCount::new(1));
206        let trace = air.generate_trace(&input)?;
207
208        // 2 rows: (3, 5), (5, 8)
209        assert_eq!(trace.row_count().count(), 2);
210        assert_eq!(trace.get(0, Column::new(0))?, F101::new(3));
211        assert_eq!(trace.get(0, Column::new(1))?, F101::new(5));
212        assert_eq!(trace.get(1, Column::new(0))?, F101::new(5));
213        assert_eq!(trace.get(1, Column::new(1))?, F101::new(8));
214        Ok(())
215    }
216}