1use alloc::vec::Vec;
7use core::fmt::{Display, Formatter};
8
9use math::{FieldElement, StarkField};
10
11use crate::air::Assertion;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct ConstraintDivisor<B: StarkField> {
29 pub(super) numerator: Vec<(usize, B)>,
30 pub(super) exemptions: Vec<B>,
31}
32
33impl<B: StarkField> ConstraintDivisor<B> {
34 fn new(numerator: Vec<(usize, B)>, exemptions: Vec<B>) -> Self {
39 ConstraintDivisor { numerator, exemptions }
40 }
41
42 pub fn from_transition(
54 constraint_enforcement_domain_size: usize,
55 num_exemptions: usize,
56 ) -> Self {
57 let exemptions = (constraint_enforcement_domain_size - num_exemptions
58 ..constraint_enforcement_domain_size)
59 .map(|step| get_trace_domain_value_at::<B>(constraint_enforcement_domain_size, step))
60 .collect();
61 Self::new(vec![(constraint_enforcement_domain_size, B::ONE)], exemptions)
62 }
63
64 pub fn from_assertion<E>(assertion: &Assertion<E>, trace_length: usize) -> Self
88 where
89 E: FieldElement<BaseField = B>,
90 {
91 let num_steps = assertion.get_num_steps(trace_length);
92 if assertion.first_step == 0 {
93 Self::new(vec![(num_steps, B::ONE)], vec![])
94 } else {
95 let trace_offset = num_steps * assertion.first_step;
96 let offset = get_trace_domain_value_at::<B>(trace_length, trace_offset);
97 Self::new(vec![(num_steps, offset)], vec![])
98 }
99 }
100
101 pub fn numerator(&self) -> &[(usize, B)] {
106 &self.numerator
107 }
108
109 pub fn exemptions(&self) -> &[B] {
111 &self.exemptions
112 }
113
114 pub fn degree(&self) -> usize {
116 let numerator_degree = self.numerator.iter().fold(0, |degree, term| degree + term.0);
117 let denominator_degree = self.exemptions.len();
118 numerator_degree - denominator_degree
119 }
120
121 pub fn evaluate_at<E: FieldElement<BaseField = B>>(&self, x: E) -> E {
125 let mut numerator = E::ONE;
127 for (degree, constant) in self.numerator.iter() {
128 let v = x.exp((*degree as u32).into());
129 let v = v - E::from(*constant);
130 numerator *= v;
131 }
132
133 let denominator = self.evaluate_exemptions_at(x);
135
136 numerator / denominator
137 }
138
139 #[inline(always)]
142 pub fn evaluate_exemptions_at<E: FieldElement<BaseField = B>>(&self, x: E) -> E {
143 self.exemptions.iter().fold(E::ONE, |r, &e| r * (x - E::from(e)))
144 }
145}
146
147impl<B: StarkField> Display for ConstraintDivisor<B> {
148 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
149 for (degree, offset) in self.numerator.iter() {
150 write!(f, "(x^{degree} - {offset})")?;
151 }
152 if !self.exemptions.is_empty() {
153 write!(f, " / ")?;
154 for x in self.exemptions.iter() {
155 write!(f, "(x - {x})")?;
156 }
157 }
158 Ok(())
159 }
160}
161
162fn get_trace_domain_value_at<B: StarkField>(trace_length: usize, step: usize) -> B {
167 debug_assert!(step < trace_length, "step must be in the trace domain [0, {trace_length})");
168 let g = B::get_root_of_unity(trace_length.ilog2());
169 g.exp((step as u64).into())
170}
171
172#[cfg(test)]
176mod tests {
177 use math::{fields::f128::BaseElement, polynom};
178
179 use super::*;
180
181 #[test]
182 fn constraint_divisor_degree() {
183 let div = ConstraintDivisor::new(vec![(4, BaseElement::ONE)], vec![]);
185 assert_eq!(4, div.degree());
186
187 let div = ConstraintDivisor::new(
189 vec![(4, BaseElement::ONE), (2, BaseElement::new(2)), (3, BaseElement::new(3))],
190 vec![],
191 );
192 assert_eq!(9, div.degree());
193
194 let div = ConstraintDivisor::new(
196 vec![(4, BaseElement::ONE), (2, BaseElement::new(2)), (3, BaseElement::new(3))],
197 vec![BaseElement::ONE, BaseElement::new(2)],
198 );
199 assert_eq!(7, div.degree());
200 }
201
202 #[test]
203 fn constraint_divisor_evaluation() {
204 let div = ConstraintDivisor::new(vec![(4, BaseElement::ONE)], vec![]);
206 assert_eq!(BaseElement::new(15), div.evaluate_at(BaseElement::new(2)));
207
208 let div = ConstraintDivisor::new(
210 vec![(4, BaseElement::ONE), (2, BaseElement::new(2)), (3, BaseElement::new(3))],
211 vec![],
212 );
213 let expected = BaseElement::new(15) * BaseElement::new(2) * BaseElement::new(5);
214 assert_eq!(expected, div.evaluate_at(BaseElement::new(2)));
215
216 let div = ConstraintDivisor::new(
219 vec![(4, BaseElement::ONE), (2, BaseElement::new(2)), (3, BaseElement::new(3))],
220 vec![BaseElement::ONE, BaseElement::new(2)],
221 );
222 let expected = BaseElement::new(255) * BaseElement::new(14) * BaseElement::new(61)
223 / BaseElement::new(6);
224 assert_eq!(expected, div.evaluate_at(BaseElement::new(4)));
225 }
226
227 #[test]
228 fn constraint_divisor_equivalence() {
229 let n = 8_usize;
230 let g = BaseElement::get_root_of_unity(n.trailing_zeros());
231 let k = 4_u32;
232 let j = n as u32 / k;
233
234 let assertion = Assertion::periodic(0, 0, j as usize, BaseElement::ONE);
238 let divisor = ConstraintDivisor::from_assertion(&assertion, n);
239
240 let poly = polynom::mul(
242 &polynom::mul(
243 &[-BaseElement::ONE, BaseElement::ONE],
244 &[-g.exp(j.into()), BaseElement::ONE],
245 ),
246 &polynom::mul(
247 &[-g.exp((2 * j).into()), BaseElement::ONE],
248 &[-g.exp((3 * j).into()), BaseElement::ONE],
249 ),
250 );
251
252 for i in 0..n {
253 let expected = polynom::eval(&poly, g.exp((i as u32).into()));
254 let actual = divisor.evaluate_at(g.exp((i as u32).into()));
255 assert_eq!(expected, actual);
256 if i.is_multiple_of(j as usize) {
257 assert_eq!(BaseElement::ZERO, actual);
258 }
259 }
260
261 let offset = 1_u32;
265 let assertion = Assertion::periodic(0, offset as usize, j as usize, BaseElement::ONE);
266 let divisor = ConstraintDivisor::from_assertion(&assertion, n);
267 assert_eq!(ConstraintDivisor::new(vec![(k as usize, g.exp(k.into()))], vec![]), divisor);
268
269 let poly = polynom::mul(
271 &polynom::mul(
272 &[-g.exp(offset.into()), BaseElement::ONE],
273 &[-g.exp((offset + j).into()), BaseElement::ONE],
274 ),
275 &polynom::mul(
276 &[-g.exp((offset + 2 * j).into()), BaseElement::ONE],
277 &[-g.exp((offset + 3 * j).into()), BaseElement::ONE],
278 ),
279 );
280
281 for i in 0..n {
282 let expected = polynom::eval(&poly, g.exp((i as u32).into()));
283 let actual = divisor.evaluate_at(g.exp((i as u32).into()));
284 assert_eq!(expected, actual);
285 if i % (j as usize) == offset as usize {
286 assert_eq!(BaseElement::ZERO, actual);
287 }
288 }
289
290 let offset = 3_u32;
292 let k = 2_u32;
293 let j = n as u32 / k;
294 let assertion = Assertion::periodic(0, offset as usize, j as usize, BaseElement::ONE);
295 let divisor = ConstraintDivisor::from_assertion(&assertion, n);
296 assert_eq!(
297 ConstraintDivisor::new(vec![(k as usize, g.exp((offset * k).into()))], vec![]),
298 divisor
299 );
300
301 let poly = polynom::mul(
303 &[-g.exp(offset.into()), BaseElement::ONE],
304 &[-g.exp((offset + j).into()), BaseElement::ONE],
305 );
306
307 for i in 0..n {
308 let expected = polynom::eval(&poly, g.exp((i as u32).into()));
309 let actual = divisor.evaluate_at(g.exp((i as u32).into()));
310 assert_eq!(expected, actual);
311 if i % (j as usize) == offset as usize {
312 assert_eq!(BaseElement::ZERO, actual);
313 }
314 }
315 }
316}