-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solve bit decomposition with negative coefficients. #2554
base: main
Are you sure you want to change the base?
Changes from all commits
5f9c452
bf43c73
99558c2
f4f8a28
f5fe3f5
1a378c3
fcc5190
7b4780e
94a8cc4
44bdd08
d2980dd
a3b210c
2e7d3a7
4ddae64
721cd36
589963b
977f106
14647d1
2e87944
0906258
1059fa6
5cf50ee
00750c8
f71190c
d398c25
85a3a4d
1c1eaa2
ea0a974
cbf290f
73f11ce
84d05ce
392a6d1
8275be7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,13 @@ use std::{ | |
|
||
use itertools::Itertools; | ||
use num_traits::Zero; | ||
use powdr_number::FieldElement; | ||
use powdr_number::{log2_exact, FieldElement}; | ||
|
||
use crate::witgen::jit::effect::Assertion; | ||
|
||
use super::{ | ||
super::range_constraints::RangeConstraint, effect::Effect, | ||
super::range_constraints::RangeConstraint, | ||
effect::{BitDecomposition, BitDecompositionComponent, Effect}, | ||
symbolic_expression::SymbolicExpression, | ||
}; | ||
|
||
|
@@ -215,23 +216,19 @@ impl<T: FieldElement, V: Ord + Clone + Display> AffineSymbolicExpression<T, V> { | |
} | ||
_ => { | ||
let r = self.solve_bit_decomposition()?; | ||
|
||
if r.complete { | ||
r | ||
} else { | ||
let negated = -self; | ||
let r = negated.solve_bit_decomposition()?; | ||
if r.complete { | ||
r | ||
} else { | ||
let effects = self | ||
.transfer_constraints() | ||
.into_iter() | ||
.chain(negated.transfer_constraints()) | ||
.collect(); | ||
ProcessResult { | ||
effects, | ||
complete: false, | ||
} | ||
let effects = self | ||
.transfer_constraints() | ||
.into_iter() | ||
.chain(negated.transfer_constraints()) | ||
.collect(); | ||
ProcessResult { | ||
effects, | ||
complete: false, | ||
} | ||
} | ||
} | ||
|
@@ -257,41 +254,55 @@ impl<T: FieldElement, V: Ord + Clone + Display> AffineSymbolicExpression<T, V> { | |
|
||
// Check if they are mutually exclusive and compute assignments. | ||
let mut covered_bits: <T as FieldElement>::Integer = 0.into(); | ||
let mut effects = vec![]; | ||
for (var, coeff, constraint) in constrained_coefficients { | ||
let mask = *constraint.multiple(coeff).mask(); | ||
if !(mask & covered_bits).is_zero() { | ||
let mut components = vec![]; | ||
for (variable, coeff, constraint) in constrained_coefficients { | ||
let is_negative = !coeff.is_in_lower_half(); | ||
let coeff_abs = if is_negative { -coeff } else { coeff }; | ||
let Some(exponent) = log2_exact(coeff_abs.to_arbitrary_integer()) else { | ||
// We could work with non-powers of two, but it would require | ||
// division instead of shifts. | ||
return Ok(ProcessResult::empty()); | ||
}; | ||
let bit_mask = *constraint.multiple(coeff_abs).mask(); | ||
if !(bit_mask & covered_bits).is_zero() { | ||
// Overlapping range constraints. | ||
return Ok(ProcessResult::empty()); | ||
} else { | ||
covered_bits |= mask; | ||
covered_bits |= bit_mask; | ||
} | ||
let masked = -&self.offset & mask; | ||
effects.push(Effect::Assignment( | ||
var.clone(), | ||
masked.integer_div(&coeff.into()), | ||
)); | ||
components.push(BitDecompositionComponent { | ||
variable, | ||
// We negate here because we are solving | ||
// c_1 * x_1 + c_2 * x_2 + ... + offset = 0, | ||
// instead of | ||
// c_1 * x_1 + c_2 * x_2 + ... = offset. | ||
is_negative: !is_negative, | ||
exponent: exponent as u64, | ||
bit_mask, | ||
}); | ||
} | ||
|
||
if covered_bits >= T::modulus() { | ||
return Ok(ProcessResult::empty()); | ||
} | ||
|
||
// We need to assert that the masks cover "-offset", | ||
// otherwise the equation is not solvable. | ||
// We assert -offset & !masks == 0 | ||
if let Some(offset) = self.offset.try_to_number() { | ||
if (-offset).to_integer() & !covered_bits != 0.into() { | ||
return Err(Error::ConflictingRangeConstraints); | ||
if !components.iter().any(|c| c.is_negative) { | ||
// If all coefficients are positive and the offset is known, we can check | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean if the offset is a known number, we can actually determine that in all cases, but the ones with negative coefficients are more complicated... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, if the offset is a known constant, we could just "run" the algorithm and make all the components known constants as well. Let's do that if we need it. |
||
// that all bits are covered. If not, then there is no way to extract | ||
// the components and thus we have a conflict. | ||
if let Some(offset) = self.offset.try_to_number() { | ||
if offset.to_integer() & !covered_bits != 0.into() { | ||
return Err(Error::ConflictingRangeConstraints); | ||
} | ||
} | ||
} else { | ||
effects.push(Assertion::assert_eq( | ||
-&self.offset & !covered_bits, | ||
T::from(0).into(), | ||
)); | ||
} | ||
|
||
Ok(ProcessResult::complete(effects)) | ||
Ok(ProcessResult::complete(vec![Effect::BitDecomposition( | ||
BitDecomposition { | ||
value: self.offset.clone(), | ||
components, | ||
}, | ||
)])) | ||
} | ||
|
||
fn transfer_constraints(&self) -> Option<Effect<T, V>> { | ||
|
@@ -521,10 +532,9 @@ mod test { | |
let b = Ase::from_unknown_variable("b", rc.clone()); | ||
let c = Ase::from_unknown_variable("c", rc.clone()); | ||
let z = Ase::from_known_symbol("Z", Default::default()); | ||
// a * 0x100 + b * 0x10000 + c * 0x1000000 + 10 + Z = 0 | ||
// a * 0x100 - b * 0x10000 + c * 0x1000000 + 10 + Z = 0 | ||
let ten = from_number(10); | ||
let constr = mul(&a, &from_number(0x100)) | ||
+ mul(&b, &from_number(0x10000)) | ||
let constr = mul(&a, &from_number(0x100)) - mul(&b, &from_number(0x10000)) | ||
+ mul(&c, &from_number(0x1000000)) | ||
+ ten.clone() | ||
+ z.clone(); | ||
|
@@ -533,38 +543,39 @@ mod test { | |
assert!(!result.complete && result.effects.is_empty()); | ||
// Now add the range constraint on a, it should be solvable. | ||
let a = Ase::from_unknown_variable("a", rc.clone()); | ||
let constr = mul(&a, &from_number(0x100)) | ||
+ mul(&b, &from_number(0x10000)) | ||
let constr = mul(&a, &from_number(0x100)) - mul(&b, &from_number(0x10000)) | ||
+ mul(&c, &from_number(0x1000000)) | ||
+ ten.clone() | ||
+ z; | ||
let result = constr.solve().unwrap(); | ||
assert!(result.complete); | ||
let effects = result | ||
.effects | ||
.into_iter() | ||
.map(|effect| match effect { | ||
Effect::Assignment(v, expr) => format!("{v} = {expr};\n"), | ||
Effect::Assertion(Assertion { | ||
lhs, | ||
rhs, | ||
expected_equal, | ||
}) => { | ||
format!( | ||
"assert {lhs} {} {rhs};\n", | ||
if expected_equal { "==" } else { "!=" } | ||
) | ||
} | ||
_ => panic!(), | ||
|
||
let [effect] = &result.effects[..] else { | ||
panic!(); | ||
}; | ||
let Effect::BitDecomposition(BitDecomposition { value, components }) = effect else { | ||
panic!(); | ||
}; | ||
assert_eq!(format!("{value}"), "(10 + Z)"); | ||
let formatted = components | ||
.iter() | ||
.map(|c| { | ||
format!( | ||
"{} = (({value} & 0x{:0x}) >> {}){};\n", | ||
c.variable, | ||
c.bit_mask, | ||
c.exponent, | ||
if c.is_negative { " [negative]" } else { "" } | ||
) | ||
}) | ||
.format("") | ||
.to_string(); | ||
.join(""); | ||
|
||
assert_eq!( | ||
effects, | ||
"a = ((-(10 + Z) & 0xff00) // 256); | ||
b = ((-(10 + Z) & 0xff0000) // 65536); | ||
c = ((-(10 + Z) & 0xff000000) // 16777216); | ||
assert (-(10 + Z) & 0xffffffff000000ff) == 0; | ||
formatted, | ||
"\ | ||
a = (((10 + Z) & 0xff00) >> 8) [negative]; | ||
b = (((10 + Z) & 0xff0000) >> 16); | ||
c = (((10 + Z) & 0xff000000) >> 24) [negative]; | ||
" | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The negated version is now covered already by the non-negated check.