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