Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1240dc8

Browse files
committedApr 3, 2025·
quadratic symbolic expressions.
1 parent 28307fb commit 1240dc8

File tree

4 files changed

+266
-21
lines changed

4 files changed

+266
-21
lines changed
 

‎executor/src/witgen/jit/affine_symbolic_expression.rs

-21
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,6 @@ impl<T: FieldElement, V: Ord + Clone + Display> AffineSymbolicExpression<T, V> {
132132
}
133133
}
134134

135-
/// Returns the range constraint of the whole expression.
136-
/// This only works for simple expressions since all coefficients
137-
/// must be known numbers.
138-
pub fn range_constraint(&self) -> RangeConstraint<T> {
139-
self.coefficients
140-
.iter()
141-
.map(|(var, coeff)| {
142-
let coeff = coeff.try_to_number()?;
143-
let rc = self.range_constraints.get(var)?;
144-
Some(rc.multiple(coeff))
145-
})
146-
.collect::<Option<Vec<_>>>()
147-
.and_then(|summands| {
148-
summands
149-
.into_iter()
150-
.chain(std::iter::once(self.offset.range_constraint()))
151-
.reduce(|c1, c2| c1.combine_sum(&c2))
152-
})
153-
.unwrap_or_default()
154-
}
155-
156135
/// If this expression contains a single unknown variable, returns it.
157136
pub fn single_unknown_variable(&self) -> Option<&V> {
158137
if self.coefficients.len() == 1 {

‎executor/src/witgen/jit/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod identity_queue;
99
mod interpreter;
1010
mod processor;
1111
mod prover_function_heuristics;
12+
pub(crate) mod quadratic_symbolic_expression;
1213
mod single_step_processor;
1314
mod symbolic_expression;
1415
mod variable;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use std::{
2+
collections::BTreeMap,
3+
fmt::Display,
4+
ops::{Add, Mul, MulAssign, Neg, Sub},
5+
};
6+
7+
use itertools::Itertools;
8+
use powdr_number::FieldElement;
9+
10+
use crate::witgen::range_constraints::RangeConstraint;
11+
12+
use super::symbolic_expression::SymbolicExpression;
13+
14+
/// A symbolic expression in unknown variables of type `V` and (symbolically)
15+
/// known terms, representing a sum of (super-)quadratic, linear and constant parts.
16+
/// The quadratic terms are of the form `X * Y`, where `X` and `Y` are
17+
/// `QuadraticSymbolicExpression`s that have at least one unknown.
18+
/// The linear terms are of the form `a * X`, where `a` is a (symbolically) known
19+
/// value and `X` is an unknown variable.
20+
/// The constant term is a (symbolically) known value.
21+
///
22+
/// It also provides ways to quickly update the expression when the value of
23+
/// an unknown variable gets known and provides functions to solve
24+
/// (some kinds of) equations.
25+
#[derive(Debug, Clone)]
26+
pub struct QuadraticSymbolicExpression<T: FieldElement, V> {
27+
/// Quadratic terms of the form `a * X * Y`, where `a` is a (symbolically)
28+
/// known value and `X` and `Y` are quadratic symbolic expressions that
29+
/// have at least one unknown.
30+
quadratic: Vec<(Self, Self)>,
31+
/// Linear terms of the form `a * X`, where `a` is a (symbolically) known
32+
/// value and `X` is an unknown variable.
33+
linear: BTreeMap<V, SymbolicExpression<T, V>>,
34+
/// Constant term, a (symbolically) known value.
35+
constant: SymbolicExpression<T, V>,
36+
}
37+
// TODO We need occurrence lists for all variables, both in their unknon
38+
// version and in their known version (in the symbolic expressions),
39+
// because range constraints therein can also change.
40+
// they could also change to simpler expressions if one sub-expression turns to one or zero.
41+
// So we also need update functions for the symbolic expressions.
42+
43+
impl<T: FieldElement, V> From<SymbolicExpression<T, V>> for QuadraticSymbolicExpression<T, V> {
44+
fn from(k: SymbolicExpression<T, V>) -> Self {
45+
Self {
46+
quadratic: Default::default(),
47+
linear: Default::default(),
48+
constant: k,
49+
}
50+
}
51+
}
52+
53+
impl<T: FieldElement, V> From<T> for QuadraticSymbolicExpression<T, V> {
54+
fn from(k: T) -> Self {
55+
SymbolicExpression::from(k).into()
56+
}
57+
}
58+
59+
impl<T: FieldElement, V: Ord + Clone> QuadraticSymbolicExpression<T, V> {
60+
pub fn from_known_symbol(symbol: V, rc: RangeConstraint<T>) -> Self {
61+
SymbolicExpression::from_symbol(symbol, rc).into()
62+
}
63+
pub fn from_unknown_variable(var: V) -> Self {
64+
Self {
65+
quadratic: Default::default(),
66+
linear: [(var.clone(), T::from(1).into())].into_iter().collect(),
67+
constant: T::from(0).into(),
68+
}
69+
}
70+
71+
/// If this expression does not contain unknown variables, returns the symbolic expression.
72+
pub fn try_to_known(&self) -> Option<&SymbolicExpression<T, V>> {
73+
if self.quadratic.is_empty() && self.linear.is_empty() {
74+
Some(&self.constant)
75+
} else {
76+
None
77+
}
78+
}
79+
}
80+
81+
impl<T: FieldElement, V: Clone + Ord> Add for QuadraticSymbolicExpression<T, V> {
82+
type Output = QuadraticSymbolicExpression<T, V>;
83+
84+
fn add(mut self, rhs: Self) -> Self {
85+
self.quadratic.extend(rhs.quadratic);
86+
for (var, coeff) in rhs.linear {
87+
self.linear
88+
.entry(var)
89+
.and_modify(|f| *f += coeff.clone())
90+
.or_insert_with(|| coeff);
91+
}
92+
self.linear.retain(|_, f| !f.is_known_zero());
93+
self.constant += rhs.constant;
94+
self
95+
}
96+
}
97+
98+
impl<T: FieldElement, V: Clone + Ord> Add for &QuadraticSymbolicExpression<T, V> {
99+
type Output = QuadraticSymbolicExpression<T, V>;
100+
101+
fn add(self, rhs: Self) -> Self::Output {
102+
self.clone() + rhs.clone()
103+
}
104+
}
105+
106+
impl<T: FieldElement, V: Clone + Ord> Sub for &QuadraticSymbolicExpression<T, V> {
107+
type Output = QuadraticSymbolicExpression<T, V>;
108+
109+
fn sub(self, rhs: Self) -> Self::Output {
110+
self + &-rhs
111+
}
112+
}
113+
114+
impl<T: FieldElement, V: Clone + Ord> Sub for QuadraticSymbolicExpression<T, V> {
115+
type Output = QuadraticSymbolicExpression<T, V>;
116+
117+
fn sub(self, rhs: Self) -> Self::Output {
118+
&self - &rhs
119+
}
120+
}
121+
122+
impl<T: FieldElement, V: Clone + Ord> QuadraticSymbolicExpression<T, V> {
123+
fn negate(&mut self) {
124+
for (first, _) in &mut self.quadratic {
125+
first.negate()
126+
}
127+
for coeff in self.linear.values_mut() {
128+
*coeff = -coeff.clone();
129+
}
130+
self.constant = -self.constant.clone();
131+
}
132+
}
133+
134+
impl<T: FieldElement, V: Clone + Ord> Neg for QuadraticSymbolicExpression<T, V> {
135+
type Output = QuadraticSymbolicExpression<T, V>;
136+
137+
fn neg(mut self) -> Self {
138+
self.negate();
139+
self
140+
}
141+
}
142+
143+
impl<T: FieldElement, V: Clone + Ord> Neg for &QuadraticSymbolicExpression<T, V> {
144+
type Output = QuadraticSymbolicExpression<T, V>;
145+
146+
fn neg(self) -> Self::Output {
147+
-((*self).clone())
148+
}
149+
}
150+
151+
/// Multiply by known symbolic expression.
152+
impl<T: FieldElement, V: Clone + Ord> Mul<&SymbolicExpression<T, V>>
153+
for QuadraticSymbolicExpression<T, V>
154+
{
155+
type Output = QuadraticSymbolicExpression<T, V>;
156+
157+
fn mul(mut self, rhs: &SymbolicExpression<T, V>) -> Self {
158+
self *= rhs;
159+
self
160+
}
161+
}
162+
163+
impl<T: FieldElement, V: Clone + Ord> Mul<SymbolicExpression<T, V>>
164+
for QuadraticSymbolicExpression<T, V>
165+
{
166+
type Output = QuadraticSymbolicExpression<T, V>;
167+
168+
fn mul(self, rhs: SymbolicExpression<T, V>) -> Self {
169+
self * &rhs
170+
}
171+
}
172+
173+
impl<T: FieldElement, V: Clone + Ord> MulAssign<&SymbolicExpression<T, V>>
174+
for QuadraticSymbolicExpression<T, V>
175+
{
176+
fn mul_assign(&mut self, rhs: &SymbolicExpression<T, V>) {
177+
if rhs.is_known_zero() {
178+
*self = T::zero().into();
179+
} else {
180+
for (first, _) in &mut self.quadratic {
181+
*first *= rhs;
182+
}
183+
for coeff in self.linear.values_mut() {
184+
*coeff *= rhs.clone();
185+
}
186+
self.constant *= rhs.clone();
187+
}
188+
}
189+
}
190+
191+
impl<T: FieldElement, V: Clone + Ord> Mul for QuadraticSymbolicExpression<T, V> {
192+
type Output = QuadraticSymbolicExpression<T, V>;
193+
194+
fn mul(self, rhs: QuadraticSymbolicExpression<T, V>) -> Self {
195+
if let Some(k) = rhs.try_to_known() {
196+
self * k
197+
} else if let Some(k) = self.try_to_known() {
198+
rhs * k
199+
} else {
200+
Self {
201+
quadratic: vec![(self, rhs)],
202+
linear: Default::default(),
203+
constant: T::from(0).into(),
204+
}
205+
}
206+
}
207+
}
208+
209+
impl<T: FieldElement, V: Clone + Ord + Display> Display for QuadraticSymbolicExpression<T, V> {
210+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211+
write!(
212+
f,
213+
"{}",
214+
self.quadratic
215+
.iter()
216+
.map(|(a, b)| format!("({a}) * ({b})"))
217+
.chain(
218+
self.linear
219+
.iter()
220+
.map(|(var, coeff)| match coeff.try_to_number() {
221+
Some(k) if k == 1.into() => format!("{var}"),
222+
Some(k) if k == (-1).into() => format!("-{var}"),
223+
_ => format!("{coeff} * {var}"),
224+
})
225+
)
226+
.chain(match self.constant.try_to_number() {
227+
Some(k) if k == T::zero() => None,
228+
_ => Some(format!("{}", self.constant)),
229+
})
230+
.format(" + ")
231+
)
232+
}
233+
}
234+
235+
#[cfg(test)]
236+
mod tests {
237+
use super::*;
238+
use crate::witgen::range_constraints::RangeConstraint;
239+
use powdr_number::GoldilocksField;
240+
241+
use pretty_assertions::assert_eq;
242+
243+
#[test]
244+
fn test_mul_zero() {
245+
type Qse = QuadraticSymbolicExpression<GoldilocksField, String>;
246+
let x = Qse::from_unknown_variable("X".to_string());
247+
let y = Qse::from_unknown_variable("Y".to_string());
248+
let a = Qse::from_known_symbol("A".to_string(), RangeConstraint::default());
249+
let t = x * y + a;
250+
assert_eq!(t.to_string(), "(X) * (Y) + A");
251+
}
252+
}

‎executor/src/witgen/jit/symbolic_expression.rs

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use itertools::Itertools;
33
use powdr_ast::parsed::visitor::AllChildren;
44
use powdr_number::FieldElement;
55
use std::hash::Hash;
6+
use std::ops::{AddAssign, MulAssign};
67
use std::{
78
fmt::{self, Display, Formatter},
89
iter,
@@ -191,6 +192,12 @@ impl<T: FieldElement, V: Clone> Add for SymbolicExpression<T, V> {
191192
}
192193
}
193194

195+
impl<T: FieldElement, V: Clone> AddAssign for SymbolicExpression<T, V> {
196+
fn add_assign(&mut self, rhs: Self) {
197+
*self = self.clone() + rhs;
198+
}
199+
}
200+
194201
impl<T: FieldElement, V: Clone> Neg for &SymbolicExpression<T, V> {
195202
type Output = SymbolicExpression<T, V>;
196203

@@ -251,6 +258,12 @@ impl<T: FieldElement, V: Clone> Mul for SymbolicExpression<T, V> {
251258
}
252259
}
253260

261+
impl<T: FieldElement, V: Clone> MulAssign for SymbolicExpression<T, V> {
262+
fn mul_assign(&mut self, rhs: Self) {
263+
*self = self.clone() * rhs;
264+
}
265+
}
266+
254267
impl<T: FieldElement, V: Clone> SymbolicExpression<T, V> {
255268
/// Field element division. See `integer_div` for integer division.
256269
/// If you use this, you must ensure that the divisor is not zero.

0 commit comments

Comments
 (0)
Please sign in to comment.