use crate::air::Air;
use crate::air_expr::AirExpr;
use crate::column::{Column, ColumnCount};
use crate::error::Error;
use crate::trace::Trace;
use plonkish_cat::Field;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StepCount(usize);
impl StepCount {
#[must_use]
pub fn new(n: usize) -> Self {
Self(n)
}
#[must_use]
pub fn count(self) -> usize {
self.0
}
}
#[derive(Debug, Clone)]
pub struct FibonacciInput<F: Field> {
initial_a: F,
initial_b: F,
num_steps: StepCount,
}
impl<F: Field> FibonacciInput<F> {
#[must_use]
pub fn new(initial_a: F, initial_b: F, num_steps: StepCount) -> Self {
Self {
initial_a,
initial_b,
num_steps,
}
}
#[must_use]
pub fn initial_a(&self) -> &F {
&self.initial_a
}
#[must_use]
pub fn initial_b(&self) -> &F {
&self.initial_b
}
#[must_use]
pub fn num_steps(&self) -> StepCount {
self.num_steps
}
}
#[derive(Debug, Clone, Copy)]
pub struct FibonacciAir;
impl<F: Field> Air<F> for FibonacciAir {
type Input = FibonacciInput<F>;
fn column_count(&self) -> ColumnCount {
ColumnCount::new(2)
}
fn constraints(&self) -> Vec<AirExpr<F>> {
let current_a = AirExpr::current(Column::new(0));
let current_b = AirExpr::current(Column::new(1));
let next_a = AirExpr::next(Column::new(0));
let next_b = AirExpr::next(Column::new(1));
vec![
next_a - current_b.clone(),
next_b - current_a - current_b,
]
}
fn generate_trace(&self, input: &FibonacciInput<F>) -> Result<Trace<F>, Error> {
let num_rows = input.num_steps.0 + 1;
let rows: Vec<Vec<F>> = std::iter::successors(
Some((input.initial_a.clone(), input.initial_b.clone())),
|prev| Some((prev.1.clone(), prev.0.clone() + prev.1.clone())),
)
.take(num_rows)
.map(|(a, b)| vec![a, b])
.collect();
Trace::from_rows(ColumnCount::new(2), &rows)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::column::Column;
use plonkish_cat::F101;
#[test]
fn fibonacci_trace_correctness() -> Result<(), Error> {
let air = FibonacciAir;
let input = FibonacciInput::new(F101::new(1), F101::new(1), StepCount::new(7));
let trace = air.generate_trace(&input)?;
assert_eq!(trace.row_count().count(), 8);
assert_eq!(trace.get(0, Column::new(0))?, F101::new(1));
assert_eq!(trace.get(0, Column::new(1))?, F101::new(1));
assert_eq!(trace.get(7, Column::new(0))?, F101::new(21));
assert_eq!(trace.get(7, Column::new(1))?, F101::new(34));
Ok(())
}
#[test]
fn fibonacci_constraints_satisfied() -> Result<(), Error> {
let air = FibonacciAir;
let input = FibonacciInput::new(F101::new(1), F101::new(1), StepCount::new(7));
let trace = air.generate_trace(&input)?;
let constraints = air.constraints();
(0..trace.row_count().count() - 1).try_for_each(|row| {
let assign = trace.row_pair_assignment(row)?;
constraints.iter().try_for_each(|c| {
let val = c.evaluate(&assign)?;
if val == F101::zero() {
Ok(())
} else {
Err(Error::UnsatisfiedAirConstraint { row })
}
})
})
}
#[test]
fn fibonacci_single_step() -> Result<(), Error> {
let air = FibonacciAir;
let input = FibonacciInput::new(F101::new(3), F101::new(5), StepCount::new(1));
let trace = air.generate_trace(&input)?;
assert_eq!(trace.row_count().count(), 2);
assert_eq!(trace.get(0, Column::new(0))?, F101::new(3));
assert_eq!(trace.get(0, Column::new(1))?, F101::new(5));
assert_eq!(trace.get(1, Column::new(0))?, F101::new(5));
assert_eq!(trace.get(1, Column::new(1))?, F101::new(8));
Ok(())
}
}