1use std::{collections::HashMap, hash::Hash};
16
17use crate::{
18 BooleanVarConstraint,
19 expressions::{
20 Atomic, BooleanConnective, BooleanExpression, IntegerExpression, Variable,
21 fraction::Fraction,
22 },
23 lia_threshold_automaton::{
24 ComparisonConstraint, LIAVariableConstraint, SingleAtomConstraint, SumAtomConstraint,
25 integer_thresholds::{
26 DeriveFromIntegerComp, IntoNoDivBooleanExpr, Threshold, ThresholdCompOp,
27 ThresholdConstraint,
28 },
29 },
30};
31
32use super::{ConstraintRewriteError, remove_boolean_neg::NonNegatedBooleanExpression};
33
34impl TryFrom<&BooleanVarConstraint> for LIAVariableConstraint {
35 type Error = ConstraintRewriteError;
36
37 fn try_from(expr: &BooleanExpression<Variable>) -> Result<Self, Self::Error> {
38 let non_negated = expr.remove_boolean_negations();
39 non_negated.try_into()
40 }
41}
42
43impl TryFrom<BooleanVarConstraint> for LIAVariableConstraint {
44 type Error = ConstraintRewriteError;
45
46 fn try_from(expr: BooleanExpression<Variable>) -> Result<Self, Self::Error> {
47 (&expr).try_into()
48 }
49}
50
51impl TryFrom<NonNegatedBooleanExpression<Variable>> for LIAVariableConstraint {
52 type Error = ConstraintRewriteError;
53 fn try_from(value: NonNegatedBooleanExpression<Variable>) -> Result<Self, Self::Error> {
56 match value {
57 NonNegatedBooleanExpression::True => Ok(LIAVariableConstraint::True),
58 NonNegatedBooleanExpression::False => Ok(LIAVariableConstraint::False),
59 NonNegatedBooleanExpression::BinaryExpression(lhs, op, rhs) => {
60 let lhs: LIAVariableConstraint = (*lhs).try_into()?;
61 let rhs = (*rhs).try_into()?;
62
63 if lhs.is_top() {
65 if op == BooleanConnective::And {
66 return Ok(rhs);
67 }
68 return Ok(LIAVariableConstraint::True);
69 }
70 if rhs.is_top() {
71 if op == BooleanConnective::And {
72 return Ok(lhs);
73 }
74 return Ok(LIAVariableConstraint::True);
75 }
76
77 Ok(LIAVariableConstraint::BinaryGuard(
78 Box::new(lhs),
79 op,
80 Box::new(rhs),
81 ))
82 }
83 NonNegatedBooleanExpression::ComparisonExpression(lhs, op, rhs) => {
84 LIAVariableConstraint::from_integer_expr(*lhs, op, *rhs)
85 }
86 }
87 }
88}
89
90pub(crate) fn split_pairs_into_atom_and_threshold<T: Atomic>(
94 lhs: IntegerExpression<T>,
95 rhs: IntegerExpression<T>,
96) -> Result<(HashMap<T, Fraction>, Threshold), ConstraintRewriteError> {
97 let lhs = lhs
98 .remove_unary_neg_and_sub()
99 .remove_div()?
100 .split_into_factor_pairs()?;
101
102 let rhs = rhs
103 .remove_unary_neg_and_sub()
104 .remove_div()?
105 .split_into_factor_pairs()?;
106
107 let atoms = combine_pair_iterators(lhs.get_atom_factor_pairs(), rhs.get_atom_factor_pairs());
108
109 let params = combine_pair_iterators(rhs.get_param_factor_pairs(), lhs.get_param_factor_pairs());
110
111 let c = rhs.get_const_factor() - lhs.get_const_factor();
112 let t = Threshold::new(params, c);
113
114 Ok((atoms, t))
115}
116
117impl DeriveFromIntegerComp<Variable, ConstraintRewriteError> for LIAVariableConstraint {
118 fn form_ordered_constr(
119 scaled_t: HashMap<Variable, Fraction>,
120 thr_c: ThresholdConstraint,
121 ) -> Result<Self, ConstraintRewriteError> {
122 if scaled_t.is_empty() {
123 if let Some(c) = thr_c.get_threshold().get_const() {
125 let zero = Fraction::from(0);
126 let holds = match thr_c.get_op() {
127 ThresholdCompOp::Geq => zero >= c,
128 ThresholdCompOp::Gt => zero > c,
129 ThresholdCompOp::Lt => zero < c,
130 ThresholdCompOp::Leq => zero <= c,
131 };
132
133 if holds {
134 return Ok(LIAVariableConstraint::True);
135 }
136 return Ok(LIAVariableConstraint::False);
137 }
138
139 return Err(ConstraintRewriteError::ParameterConstraint(Box::new(
143 BooleanExpression::ComparisonExpression(
144 Box::new(IntegerExpression::Const(0)),
145 thr_c.get_op().into(),
146 Box::new(thr_c.get_threshold().get_scaled_integer_expression(1)),
147 ),
148 )));
149 }
150
151 if scaled_t.len() == 1 {
152 let (var, factor) = scaled_t.into_iter().next().unwrap();
153 let mut thr = thr_c;
154 thr.scale(factor.inverse());
155
156 let svar_guard = SingleAtomConstraint::new(var, thr);
157
158 return Ok(LIAVariableConstraint::SingleVarConstraint(svar_guard));
159 }
160
161 if let Ok(g) = SumAtomConstraint::try_new(scaled_t.clone(), thr_c.clone()) {
162 return Ok(LIAVariableConstraint::SumVarConstraint(g));
163 }
164
165 Ok(LIAVariableConstraint::ComparisonConstraint(
166 ComparisonConstraint::try_new(scaled_t, thr_c)
167 .expect("Failed to categorize guard type."),
168 ))
169 }
170}
171
172fn combine_pair_iterators<'a, T: Clone + Eq + Hash + 'a>(
176 reg_it: impl Iterator<Item = (&'a T, &'a Fraction)>,
177 negated_it: impl Iterator<Item = (&'a T, &'a Fraction)>,
178) -> HashMap<T, Fraction> {
179 reg_it
180 .map(|(t, f)| (t.clone(), *f))
181 .chain(negated_it.map(|(t, f)| (t.clone(), -*f)))
183 .fold(HashMap::new(), |mut acc, (p, f)| {
184 acc.entry(p).and_modify(|e| *e += f).or_insert(f);
186
187 acc
188 })
189}
190
191#[cfg(test)]
192mod tests {
193 use std::collections::BTreeMap;
194
195 use crate::{
196 expressions::{
197 BooleanConnective, ComparisonOp, IntegerExpression, Parameter, Variable,
198 fraction::Fraction,
199 },
200 lia_threshold_automaton::{
201 ConstraintRewriteError::{self},
202 LIAVariableConstraint, SingleAtomConstraint, SumAtomConstraint,
203 general_to_lia::remove_boolean_neg::NonNegatedBooleanExpression,
204 integer_thresholds::{ThresholdCompOp, ThresholdConstraint},
205 },
206 };
207
208 #[test]
209 fn test_try_into_single_var() {
210 let expr = NonNegatedBooleanExpression::ComparisonExpression(
211 Box::new(IntegerExpression::Atom(Variable::new("var"))),
212 ComparisonOp::Gt,
213 Box::new(IntegerExpression::Const(0)),
214 );
215
216 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
217 Variable::new("var"),
218 ThresholdConstraint::new(ThresholdCompOp::Gt, Vec::<(Parameter, Fraction)>::new(), 0),
219 ));
220 let got = LIAVariableConstraint::try_from(expr).unwrap();
221 assert_eq!(got, expected_expr);
222 }
223
224 #[test]
225 fn test_try_into_single_var_eq() {
226 let expr = NonNegatedBooleanExpression::ComparisonExpression(
228 Box::new(IntegerExpression::Atom(Variable::new("var"))),
229 ComparisonOp::Eq,
230 Box::new(IntegerExpression::Const(0)),
231 );
232
233 let expected_expr = LIAVariableConstraint::BinaryGuard(
235 Box::new(LIAVariableConstraint::SingleVarConstraint(
236 SingleAtomConstraint::new(
237 Variable::new("var"),
238 ThresholdConstraint::new(
239 ThresholdCompOp::Leq,
240 Vec::<(Parameter, Fraction)>::new(),
241 0,
242 ),
243 ),
244 )),
245 BooleanConnective::And,
246 Box::new(LIAVariableConstraint::SingleVarConstraint(
247 SingleAtomConstraint::new(
248 Variable::new("var"),
249 ThresholdConstraint::new(
250 ThresholdCompOp::Geq,
251 Vec::<(Parameter, Fraction)>::new(),
252 0,
253 ),
254 ),
255 )),
256 );
257 let got = LIAVariableConstraint::try_from(expr).unwrap();
258 assert_eq!(got, expected_expr);
259 }
260
261 #[test]
262 fn test_try_into_single_var_neq() {
263 let expr = NonNegatedBooleanExpression::ComparisonExpression(
265 Box::new(IntegerExpression::Atom(Variable::new("var"))),
266 ComparisonOp::Neq,
267 Box::new(IntegerExpression::Const(1)),
268 );
269
270 let expected_expr = LIAVariableConstraint::BinaryGuard(
272 Box::new(LIAVariableConstraint::SingleVarConstraint(
273 SingleAtomConstraint::new(
274 Variable::new("var"),
275 ThresholdConstraint::new(
276 ThresholdCompOp::Lt,
277 Vec::<(Parameter, Fraction)>::new(),
278 1,
279 ),
280 ),
281 )),
282 BooleanConnective::Or,
283 Box::new(LIAVariableConstraint::SingleVarConstraint(
284 SingleAtomConstraint::new(
285 Variable::new("var"),
286 ThresholdConstraint::new(
287 ThresholdCompOp::Gt,
288 Vec::<(Parameter, Fraction)>::new(),
289 1,
290 ),
291 ),
292 )),
293 );
294 let got = LIAVariableConstraint::try_from(expr).unwrap();
295 assert_eq!(got, expected_expr);
296 }
297
298 #[test]
299 fn test_try_into_single_var_scale_factor_correct() {
300 let expr = NonNegatedBooleanExpression::ComparisonExpression(
302 Box::new(IntegerExpression::Const(2) * IntegerExpression::Atom(Variable::new("var"))),
303 ComparisonOp::Gt,
304 Box::new(IntegerExpression::Const(7)),
305 );
306
307 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
308 Variable::new("var"),
309 ThresholdConstraint::new(
310 ThresholdCompOp::Gt,
311 Vec::<(Parameter, Fraction)>::new(),
312 Fraction::new(7, 2, false),
313 ),
314 ));
315 let got = LIAVariableConstraint::try_from(expr).unwrap();
316 assert_eq!(got, expected_expr);
317 }
318
319 #[test]
320 fn test_simple_guards() {
321 let expr: NonNegatedBooleanExpression<Variable> = NonNegatedBooleanExpression::True;
322 let expected = LIAVariableConstraint::True;
323
324 let result = LIAVariableConstraint::try_from(expr).unwrap();
325 assert_eq!(result, expected);
326
327 let expr: NonNegatedBooleanExpression<Variable> = NonNegatedBooleanExpression::False;
328 let expected = LIAVariableConstraint::False;
329
330 let result = LIAVariableConstraint::try_from(expr).unwrap();
331 assert_eq!(result, expected);
332 }
333
334 #[test]
335 fn test_binary_guard() {
336 let expr = NonNegatedBooleanExpression::BinaryExpression(
338 Box::new(NonNegatedBooleanExpression::True),
339 BooleanConnective::And,
340 Box::new(NonNegatedBooleanExpression::True),
341 );
342
343 let expected_expr = LIAVariableConstraint::True;
344 let got = LIAVariableConstraint::try_from(expr).unwrap();
345 assert_eq!(got, expected_expr);
346
347 let expr = NonNegatedBooleanExpression::BinaryExpression(
349 Box::new(NonNegatedBooleanExpression::False),
350 BooleanConnective::Or,
351 Box::new(NonNegatedBooleanExpression::True),
352 );
353
354 let expected_expr = LIAVariableConstraint::True;
355 let got = LIAVariableConstraint::try_from(expr).unwrap();
356 assert_eq!(got, expected_expr);
357 }
358
359 #[test]
360 fn test_try_into_single_var_double_var_lhs() {
361 let expr = NonNegatedBooleanExpression::ComparisonExpression(
362 Box::new(
363 IntegerExpression::Atom(Variable::new("var"))
364 + IntegerExpression::Atom(Variable::new("var")),
365 ),
366 ComparisonOp::Gt,
367 Box::new(IntegerExpression::Const(1)),
368 );
369
370 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
371 Variable::new("var"),
372 ThresholdConstraint::new(
373 ThresholdCompOp::Gt,
374 Vec::<(Parameter, Fraction)>::new(),
375 Fraction::new(1, 2, false),
376 ),
377 ));
378
379 let got = LIAVariableConstraint::try_from(expr).unwrap();
380 assert_eq!(got, expected_expr);
381 }
382
383 #[test]
384 fn test_try_into_single_var_double_var_rhs() {
385 let expr = NonNegatedBooleanExpression::ComparisonExpression(
386 Box::new(IntegerExpression::Const(1)),
387 ComparisonOp::Leq,
388 Box::new(
389 IntegerExpression::Atom(Variable::new("var"))
390 + IntegerExpression::Atom(Variable::new("var")),
391 ),
392 );
393 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
394 Variable::new("var"),
395 ThresholdConstraint::new(
396 ThresholdCompOp::Geq,
397 Vec::<(Parameter, Fraction)>::new(),
398 Fraction::new(1, 2, false),
399 ),
400 ));
401 let got = LIAVariableConstraint::try_from(expr).unwrap();
402 assert_eq!(got, expected_expr);
403
404 let expr = NonNegatedBooleanExpression::ComparisonExpression(
406 Box::new(IntegerExpression::Const(1)),
407 ComparisonOp::Lt,
408 Box::new(
409 IntegerExpression::Atom(Variable::new("var"))
410 + IntegerExpression::Atom(Variable::new("var")),
411 ),
412 );
413 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
414 Variable::new("var"),
415 ThresholdConstraint::new(
416 ThresholdCompOp::Gt,
417 Vec::<(Parameter, Fraction)>::new(),
418 Fraction::new(1, 2, false),
419 ),
420 ));
421 let got = LIAVariableConstraint::try_from(expr).unwrap();
422 assert_eq!(got, expected_expr);
423
424 let expr = NonNegatedBooleanExpression::ComparisonExpression(
425 Box::new(IntegerExpression::Const(6) - IntegerExpression::Atom(Variable::new("var"))),
426 ComparisonOp::Leq,
427 Box::new(
428 IntegerExpression::Atom(Variable::new("var"))
429 + IntegerExpression::Atom(Variable::new("var")),
430 ),
431 );
432 let expected_expr = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
433 Variable::new("var"),
434 ThresholdConstraint::new(
435 ThresholdCompOp::Geq,
436 Vec::<(Parameter, Fraction)>::new(),
437 Fraction::new(2, 1, false),
438 ),
439 ));
440 let got = LIAVariableConstraint::try_from(expr).unwrap();
441 assert_eq!(got, expected_expr);
442 }
443
444 #[test]
445 fn test_try_into_multi_var() {
446 let expr = NonNegatedBooleanExpression::ComparisonExpression(
447 Box::new(
448 IntegerExpression::Atom(Variable::new("var1"))
449 + IntegerExpression::Atom(Variable::new("var2")),
450 ),
451 ComparisonOp::Lt,
452 Box::new(IntegerExpression::Const(0)),
453 );
454
455 let expected_expr = LIAVariableConstraint::SumVarConstraint(
456 SumAtomConstraint::try_new(
457 BTreeMap::from([(Variable::new("var1"), 1), (Variable::new("var2"), 1)]),
458 ThresholdConstraint::new(
459 ThresholdCompOp::Lt,
460 Vec::<(Parameter, Fraction)>::new(),
461 0,
462 ),
463 )
464 .unwrap(),
465 );
466
467 let got = LIAVariableConstraint::try_from(expr).unwrap();
468 assert_eq!(got, expected_expr);
469 }
470
471 #[test]
472 fn test_try_into_multi_var_double_var_lhs() {
473 let expr = NonNegatedBooleanExpression::ComparisonExpression(
474 Box::new(
475 IntegerExpression::Atom(Variable::new("var1"))
476 + IntegerExpression::Atom(Variable::new("var2"))
477 + IntegerExpression::Atom(Variable::new("var1")) * IntegerExpression::Const(2),
478 ),
479 ComparisonOp::Lt,
480 Box::new(IntegerExpression::Const(0)),
481 );
482
483 let expected_expr = LIAVariableConstraint::SumVarConstraint(
484 SumAtomConstraint::try_new(
485 BTreeMap::from([(Variable::new("var1"), 3), (Variable::new("var2"), 1)]),
486 ThresholdConstraint::new(
487 ThresholdCompOp::Lt,
488 Vec::<(Parameter, Fraction)>::new(),
489 0,
490 ),
491 )
492 .unwrap(),
493 );
494
495 let got = LIAVariableConstraint::try_from(expr).unwrap();
496 assert_eq!(got, expected_expr);
497 }
498
499 #[test]
500 fn test_try_into_multi_var_double_var_rhs() {
501 let expr = NonNegatedBooleanExpression::ComparisonExpression(
503 Box::new(
504 IntegerExpression::Atom(Variable::new("var1"))
505 + IntegerExpression::Atom(Variable::new("var2"))
506 + IntegerExpression::Atom(Variable::new("var1")) * IntegerExpression::Const(5),
507 ),
508 ComparisonOp::Lt,
509 Box::new(
510 IntegerExpression::Const(0)
511 + (IntegerExpression::Atom(Variable::new("var1"))
512 * IntegerExpression::Const(2)),
513 ),
514 );
515
516 let expected_expr = LIAVariableConstraint::SumVarConstraint(
517 SumAtomConstraint::try_new(
518 BTreeMap::from([(Variable::new("var1"), 4), (Variable::new("var2"), 1)]),
519 ThresholdConstraint::new(
520 ThresholdCompOp::Lt,
521 Vec::<(Parameter, Fraction)>::new(),
522 0,
523 ),
524 )
525 .unwrap(),
526 );
527
528 let got = LIAVariableConstraint::try_from(expr).unwrap();
529 assert_eq!(got, expected_expr);
530 }
531
532 #[test]
533 fn test_try_into_comp_guard() {
534 let expr = NonNegatedBooleanExpression::ComparisonExpression(
536 Box::new(
537 IntegerExpression::Atom(Variable::new("var1"))
538 + IntegerExpression::Atom(Variable::new("var2"))
539 + IntegerExpression::Atom(Variable::new("var1")) * IntegerExpression::Const(2),
540 ),
541 ComparisonOp::Lt,
542 Box::new(
543 IntegerExpression::Const(0)
544 + (IntegerExpression::Atom(Variable::new("var1"))
545 * IntegerExpression::Const(5)),
546 ),
547 );
548
549 let expected_expr = LIAVariableConstraint::ComparisonConstraint(
550 crate::lia_threshold_automaton::ComparisonConstraint::try_new(
551 BTreeMap::from([
552 (Variable::new("var1"), -Fraction::from(2)),
553 (Variable::new("var2"), 1.into()),
554 ]),
555 ThresholdConstraint::new(
556 ThresholdCompOp::Lt,
557 Vec::<(Parameter, Fraction)>::new(),
558 0,
559 ),
560 )
561 .unwrap(),
562 );
563
564 let got = LIAVariableConstraint::try_from(expr).unwrap();
565 assert_eq!(got, expected_expr);
566 }
567
568 #[test]
569 fn test_try_into_only_const() {
570 let expr = NonNegatedBooleanExpression::ComparisonExpression(
572 Box::new(IntegerExpression::Const(0)),
573 ComparisonOp::Eq,
574 Box::new(IntegerExpression::Const(0)),
575 );
576
577 let got = LIAVariableConstraint::try_from(expr);
578
579 assert!(got.is_ok());
580 assert_eq!(
581 got.unwrap(),
582 LIAVariableConstraint::BinaryGuard(
583 Box::new(LIAVariableConstraint::True),
584 BooleanConnective::And,
585 Box::new(LIAVariableConstraint::True),
586 )
587 );
588 }
589
590 #[test]
591 fn test_error_on_parameter_constraint() {
592 let expr = NonNegatedBooleanExpression::ComparisonExpression(
593 Box::new(IntegerExpression::Param(Parameter::new("n"))),
594 ComparisonOp::Gt,
595 Box::new(IntegerExpression::Const(0)),
596 );
597
598 let got = LIAVariableConstraint::try_from(expr);
599 assert!(got.is_err(), "Got {got:?}");
600 assert!(matches!(
601 got,
602 Err(ConstraintRewriteError::ParameterConstraint(_))
603 ));
604 }
605
606 #[test]
607 fn test_try_into_const_comparisons_evaluated() {
608 let cases = [
609 (0, ComparisonOp::Lt, 0, LIAVariableConstraint::False),
610 (1, ComparisonOp::Lt, 2, LIAVariableConstraint::True),
611 (2, ComparisonOp::Lt, 1, LIAVariableConstraint::False),
612 (1, ComparisonOp::Leq, 2, LIAVariableConstraint::True),
613 (2, ComparisonOp::Gt, 1, LIAVariableConstraint::True),
614 (1, ComparisonOp::Gt, 2, LIAVariableConstraint::False),
615 (2, ComparisonOp::Geq, 1, LIAVariableConstraint::True),
616 (0, ComparisonOp::Neq, 1, LIAVariableConstraint::True),
617 (1, ComparisonOp::Eq, 2, LIAVariableConstraint::False),
618 ];
619
620 for (l, op, r, expected) in cases {
621 let expr = NonNegatedBooleanExpression::ComparisonExpression(
622 Box::new(IntegerExpression::Const(l)),
623 op,
624 Box::new(IntegerExpression::Const(r)),
625 );
626 let got = LIAVariableConstraint::try_from(expr).unwrap();
627 assert_eq!(got.is_top(), expected.is_top(), "{l} {op} {r}: got {got}");
628 assert_eq!(got.is_bot(), expected.is_bot(), "{l} {op} {r}: got {got}");
629 }
630 }
631}