1use std::{
27 collections::{HashMap, HashSet},
28 error,
29 fmt::{self},
30 ops::Not,
31};
32
33use log::{debug, error, warn};
34use taco_display_utils::join_iterator;
35use taco_smt_encoder::expression_encoding::{EncodeToSMT, SMTSolverError, SMTVariableContext};
36use taco_threshold_automaton::{
37 ModifiableThresholdAutomaton, VariableConstraint,
38 expressions::{
39 BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
40 fraction::Fraction,
41 },
42 lia_threshold_automaton::{
43 ConstraintRewriteError, LIAVariableConstraint, integer_thresholds::Threshold,
44 },
45};
46
47use crate::{
48 ModelCheckerContext, SpecificationTrait, TargetSpec,
49 eltl::{ELTLExpression, remove_negations::NonNegatedELTLExpression},
50};
51
52#[derive(Clone, Debug, PartialEq)]
54pub struct ReachabilityProperty {
55 name: String,
57 disj: Vec<Reachability>,
59}
60
61impl ReachabilityProperty {
62 pub fn from_named_eltl<S: ToString>(
64 name: S,
65 expr: ELTLExpression,
66 ) -> Result<Self, ReachabilityTransformationError> {
67 let name = name.to_string();
68 let disj = Reachability::try_from_eltl(name.clone(), expr.not().into())?;
69
70 Ok(Self { name, disj })
71 }
72
73 pub fn contains_reachability_constraint(&self) -> bool {
77 self.disj
78 .iter()
79 .any(|c| c.contains_reachability_constraint())
80 }
81
82 pub fn create_tas_to_check<T: ModifiableThresholdAutomaton>(
84 &self,
85 ta: &T,
86 ) -> impl Iterator<Item = (DisjunctionTargetConfig, T)> {
87 self.clone().disj.into_iter().map(move |reach| {
88 let mut ta = ta.clone();
89
90 ta.set_name(ta.name().to_string() + "-" + reach.target.name());
91
92 ta.add_initial_location_constraints(reach.precondition_loc);
93 ta.add_initial_variable_constraints(reach.precondition_var);
94 ta.add_resilience_conditions(reach.precondition_par);
95
96 (reach.target, ta)
97 })
98 }
99
100 pub fn name(&self) -> &str {
102 &self.name
103 }
104
105 pub fn new<S: Into<String>, T: Into<Reachability>>(name: S, targets: T) -> Self {
107 let spec = targets.into();
108 Self {
109 name: name.into(),
110 disj: vec![spec],
111 }
112 }
113}
114
115impl<C: ModelCheckerContext> SpecificationTrait<C> for ReachabilityProperty {
116 type TransformationError = ReachabilityTransformationError;
117
118 fn try_from_eltl(
119 spec: impl Iterator<Item = (String, crate::eltl::ELTLExpression)>,
120 _ctx: &C,
121 ) -> Result<Vec<Self>, Self::TransformationError> {
122 let mut res_properties = Vec::new();
123
124 for (name, expr) in spec {
125 let res = Self::from_named_eltl(name.clone(), expr.clone());
126 if let Err(ReachabilityTransformationError::LivenessSpecification) = res {
127 warn!(
128 "Specification '{name}' is a liveness specification, which are currently not supported by TACO. Skipping ..."
129 );
130 debug!("Classified property '{name}' as liveness. Expression: {expr}");
131 continue;
132 }
133
134 let res = res?;
135 res_properties.push(res);
136 }
137
138 Ok(res_properties)
139 }
140
141 type InternalSpecType = DisjunctionTargetConfig;
142
143 fn transform_threshold_automaton<
144 TA: taco_threshold_automaton::ThresholdAutomaton + ModifiableThresholdAutomaton,
145 >(
146 ta: TA,
147 specs: Vec<Self>,
148 _ctx: &C,
149 ) -> Vec<(Self::InternalSpecType, TA)> {
150 specs
151 .into_iter()
152 .flat_map(|spec| spec.create_tas_to_check(&ta).collect::<Vec<_>>())
153 .collect::<Vec<_>>()
154 }
155}
156
157#[derive(Debug, Clone, PartialEq)]
182pub struct Reachability {
183 precondition_par: Vec<BooleanExpression<Parameter>>,
185 precondition_loc: Vec<BooleanExpression<Location>>,
188 precondition_var: Vec<BooleanExpression<Variable>>,
190 target: DisjunctionTargetConfig,
192}
193
194impl And for Reachability {
195 fn and(mut self, other: Reachability) -> Self {
196 self.precondition_par.reserve(other.precondition_par.len());
197 self.precondition_par.extend(other.precondition_par);
198
199 self.precondition_loc.reserve(other.precondition_loc.len());
200 self.precondition_loc.extend(other.precondition_loc);
201
202 self.precondition_var.reserve(other.precondition_var.len());
203 self.precondition_var.extend(other.precondition_var);
204
205 self.target = self.target.and(other.target);
206
207 self
208 }
209}
210
211impl Reachability {
212 fn new_unconstrained(name: String) -> Self {
214 Reachability {
215 precondition_par: vec![],
216 precondition_loc: vec![],
217 precondition_var: vec![],
218 target: DisjunctionTargetConfig::new_empty(name),
219 }
220 }
221
222 fn new_with_parameter_constr(
225 name: String,
226 lhs: Box<IntegerExpression<Parameter>>,
227 op: ComparisonOp,
228 rhs: Box<IntegerExpression<Parameter>>,
229 ) -> Self {
230 Reachability {
231 precondition_par: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
232 precondition_loc: vec![],
233 precondition_var: vec![],
234 target: DisjunctionTargetConfig::new_empty(name),
235 }
236 }
237
238 fn new_with_initial_loc_constr(
241 name: String,
242 lhs: Box<IntegerExpression<Location>>,
243 op: ComparisonOp,
244 rhs: Box<IntegerExpression<Location>>,
245 ) -> Self {
246 Reachability {
247 precondition_par: vec![],
248 precondition_loc: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
249 precondition_var: vec![],
250 target: DisjunctionTargetConfig::new_empty(name),
251 }
252 }
253
254 fn new_with_initial_var_constr(
257 name: String,
258 lhs: Box<IntegerExpression<Variable>>,
259 op: ComparisonOp,
260 rhs: Box<IntegerExpression<Variable>>,
261 ) -> Self {
262 Reachability {
263 precondition_par: vec![],
264 precondition_loc: vec![],
265 precondition_var: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
266 target: DisjunctionTargetConfig::new_empty(name),
267 }
268 }
269
270 fn new_with_eventual_reach(disj: DisjunctionTargetConfig) -> Self {
272 Reachability {
273 precondition_par: vec![],
274 precondition_loc: vec![],
275 precondition_var: vec![],
276 target: disj,
277 }
278 }
279
280 pub fn contains_reachability_constraint(&self) -> bool {
284 self.target.contains_reachability_constraint()
285 }
286
287 pub fn get_target_conditions(&self) -> &DisjunctionTargetConfig {
293 &self.target
294 }
295
296 fn try_from_eltl(
298 name: String,
299 spec: NonNegatedELTLExpression,
300 ) -> Result<Vec<Self>, ReachabilityTransformationError> {
301 match spec {
302 NonNegatedELTLExpression::Globally(_expr) => {
303 Err(ReachabilityTransformationError::LivenessSpecification)
304 }
305 NonNegatedELTLExpression::Eventually(expr) => {
306 let inner = Self::parse_in_eventually(name, *expr)?;
307 Ok(vec![Self::new_with_eventual_reach(inner)])
308 }
309 NonNegatedELTLExpression::And(lhs, rhs) => {
310 let lhs = Self::try_from_eltl(name.clone(), *lhs)?;
311 let rhs = Self::try_from_eltl(name, *rhs)?;
312
313 let mut new_constrs = Vec::with_capacity(lhs.len() * rhs.len());
314 for constr in &lhs {
315 for o_constr in &rhs {
316 new_constrs.push(constr.clone().and(o_constr.clone()));
317 }
318 }
319
320 Ok(new_constrs)
321 }
322 NonNegatedELTLExpression::Or(lhs, rhs) => {
323 let mut lhs = Reachability::try_from_eltl(name.clone(), *lhs)?;
324 let mut rhs = Reachability::try_from_eltl(name, *rhs)?;
325
326 lhs.reserve(rhs.len());
327 lhs.append(&mut rhs);
328 Ok(lhs)
329 }
330 NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
331 Ok(vec![Self::new_with_initial_loc_constr(name, lhs, op, rhs)])
332 }
333 NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
334 Ok(vec![Self::new_with_initial_var_constr(name, lhs, op, rhs)])
335 }
336 NonNegatedELTLExpression::ParameterExpr(lhs, op, rhs) => {
337 Ok(vec![Self::new_with_parameter_constr(name, lhs, op, rhs)])
338 }
339 NonNegatedELTLExpression::True => Ok(vec![Self::new_unconstrained(name)]),
340 NonNegatedELTLExpression::False => Ok(vec![]),
341 }
342 }
343
344 fn parse_in_eventually(
347 name: String,
348 expr: NonNegatedELTLExpression,
349 ) -> Result<DisjunctionTargetConfig, ReachabilityTransformationError> {
350 match expr {
351 NonNegatedELTLExpression::Eventually(expr) => Self::parse_in_eventually(name, *expr),
352 NonNegatedELTLExpression::Or(lhs, rhs) => {
353 let lhs = Self::parse_in_eventually(name.clone(), *lhs)?;
354 let rhs = Self::parse_in_eventually(name, *rhs)?;
355
356 Ok(lhs.or(rhs))
357 }
358 NonNegatedELTLExpression::And(lhs, rhs) => {
359 let lhs = Self::parse_in_eventually(name.clone(), *lhs)?;
360 let rhs = Self::parse_in_eventually(name, *rhs)?;
361
362 Ok(lhs.and(rhs))
363 }
364 NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
365 let reach = TargetConfig::try_from_loc_constr((*lhs, op, *rhs))?;
366
367 Ok(DisjunctionTargetConfig::new_with_single_disjunct(
368 name, reach,
369 ))
370 }
371 NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
372 let var = BooleanExpression::ComparisonExpression(lhs, op, rhs);
373 let loc = TargetConfig::new_with_var_constr(var)?;
374
375 Ok(DisjunctionTargetConfig::new_with_single_disjunct(name, loc))
376 }
377 NonNegatedELTLExpression::True => {
378 Ok(DisjunctionTargetConfig::new_with_single_disjunct(
379 name,
380 TargetConfig::new_unconstrained(),
381 ))
382 }
383 NonNegatedELTLExpression::False => Ok(DisjunctionTargetConfig::new_empty(name)),
384 NonNegatedELTLExpression::Globally(_expr) => {
385 Err(ReachabilityTransformationError::LivenessSpecification)
386 }
387 NonNegatedELTLExpression::ParameterExpr(_, _, _) => unreachable!(
388 "This case should have been filtered out when validating the specification"
389 ),
390 }
391 }
392}
393
394impl<D: Into<DisjunctionTargetConfig>> From<D> for Reachability {
395 fn from(value: D) -> Self {
396 let disj = value.into();
397 Reachability::new_with_eventual_reach(disj)
398 }
399}
400
401#[derive(Debug, Clone, PartialEq)]
405pub struct DisjunctionTargetConfig {
406 property_name: String,
408 targets: Vec<TargetConfig>,
410}
411
412impl fmt::Display for DisjunctionTargetConfig {
413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414 write!(f, "{}", join_iterator(self.targets.iter(), " || "))
415 }
416}
417
418impl Or for DisjunctionTargetConfig {
419 fn or(mut self, other: Self) -> Self {
420 self.targets.reserve(other.targets.len());
421 self.targets.extend(other.targets);
422 self
423 }
424}
425
426impl And for DisjunctionTargetConfig {
427 fn and(self, other: DisjunctionTargetConfig) -> DisjunctionTargetConfig {
428 if self.targets.is_empty() {
429 return other;
430 }
431
432 if other.targets.is_empty() {
433 return self;
434 }
435
436 let mut constrs = Vec::with_capacity(self.targets.len() * other.targets.len());
437
438 for constr in self.targets {
439 for o_constr in other.targets.iter() {
440 constrs.push(constr.clone().and(o_constr.clone()));
441 }
442 }
443
444 debug_assert!(
445 self.property_name == other.property_name,
446 "Combined target configurations from different properties"
447 );
448
449 Self {
450 property_name: self.property_name,
451 targets: constrs,
452 }
453 }
454}
455
456impl TargetSpec for DisjunctionTargetConfig {
457 fn get_locations_in_target(&self) -> impl IntoIterator<Item = &Location> {
458 self.get_locations_appearing()
459 }
460
461 fn get_variable_constraint(
462 &self,
463 ) -> impl IntoIterator<
464 Item = &taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint,
465 > {
466 self.targets.iter().map(|t| t.get_variable_constraint())
467 }
468}
469
470impl DisjunctionTargetConfig {
471 fn new_empty(name: String) -> Self {
473 Self {
474 property_name: name,
475 targets: Vec::new(),
476 }
477 }
478
479 fn new_with_single_disjunct(name: String, u: TargetConfig) -> Self {
481 Self {
482 property_name: name,
483 targets: vec![u],
484 }
485 }
486
487 pub fn new_from_targets<I: IntoIterator<Item = TargetConfig>>(name: String, disj: I) -> Self {
489 Self {
490 property_name: name,
491 targets: disj.into_iter().collect(),
492 }
493 }
494
495 pub fn contains_reachability_constraint(&self) -> bool {
499 self.targets.iter().any(|c| c.is_reachability_constraint())
500 }
501
502 pub fn get_locations_appearing(&self) -> HashSet<&Location> {
504 self.targets
505 .iter()
506 .map(|t| t.get_locations_appearing())
507 .fold(HashSet::new(), |mut acc, x| {
508 acc.reserve(x.len());
509 acc.extend(x);
510 acc
511 })
512 }
513
514 pub fn name(&self) -> &str {
516 &self.property_name
517 }
518
519 pub fn get_target_configs(&self) -> impl Iterator<Item = &TargetConfig> {
521 self.targets.iter()
522 }
523}
524
525impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
526 EncodeToSMT<DisjunctionTargetConfig, C> for DisjunctionTargetConfig
527{
528 fn encode_to_smt_with_ctx(
529 &self,
530 solver: &taco_smt_encoder::SMTSolver,
531 ctx: &C,
532 ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
533 if self.targets.is_empty() {
534 return Ok(solver.false_());
535 }
536
537 let disjuncts = self
538 .targets
539 .iter()
540 .map(|c| c.encode_to_smt_with_ctx(solver, ctx))
541 .collect::<Result<Vec<_>, SMTSolverError>>()?;
542
543 Ok(solver.or_many(disjuncts))
544 }
545}
546
547#[derive(Debug, Clone, PartialEq)]
553pub struct TargetConfig {
554 lower_bounds: HashMap<Location, u32>,
557 locations_not_covered: HashSet<Location>,
559 variable_constr: LIAVariableConstraint,
561}
562
563impl And for TargetConfig {
564 fn and(mut self, other: Self) -> Self {
565 self.lower_bounds.reserve(other.lower_bounds.len());
566 self.lower_bounds.extend(other.lower_bounds);
567
568 self.locations_not_covered
569 .reserve(other.locations_not_covered.len());
570 self.locations_not_covered
571 .extend(other.locations_not_covered);
572
573 self.variable_constr = if self.variable_constr == LIAVariableConstraint::True {
574 other.variable_constr
575 } else if other.variable_constr == LIAVariableConstraint::True {
576 self.variable_constr
577 } else {
578 self.variable_constr & other.variable_constr
579 };
580 self
581 }
582}
583
584impl TargetConfig {
585 fn new_unconstrained() -> Self {
588 TargetConfig {
589 lower_bounds: HashMap::new(),
590 locations_not_covered: HashSet::new(),
591 variable_constr: LIAVariableConstraint::True,
592 }
593 }
594
595 fn new_loc_not_covered(loc: Location) -> Self {
598 TargetConfig {
599 lower_bounds: HashMap::new(),
600 locations_not_covered: HashSet::from([loc]),
601 variable_constr: LIAVariableConstraint::True,
602 }
603 }
604
605 pub fn new_cover<L: IntoIterator<Item = Location>>(
608 cover: L,
609 ) -> Result<Self, ReachabilityTransformationError> {
610 let locs = cover
611 .into_iter()
612 .map(|loc| (loc, 1))
613 .collect::<HashMap<_, _>>();
614
615 Self::new_reach_with_var_constr(locs, [], BooleanExpression::True)
616 }
617
618 pub fn new_general_cover<L: IntoIterator<Item = (Location, u32)>>(
621 cover: L,
622 ) -> Result<Self, ReachabilityTransformationError> {
623 let locs = cover.into_iter().collect::<HashMap<_, _>>();
624
625 Self::new_reach_with_var_constr(locs, [], BooleanExpression::True)
626 }
627
628 pub fn new_reach<LI: IntoIterator<Item = Location>, LII: IntoIterator<Item = Location>>(
632 cover: LI,
633 uncover: LII,
634 ) -> Result<Self, ReachabilityTransformationError> {
635 let cover = cover
636 .into_iter()
637 .map(|loc| (loc, 1))
638 .collect::<HashMap<_, _>>();
639 let uncover = uncover.into_iter().collect::<HashSet<_>>();
640
641 Self::new_reach_with_var_constr(cover, uncover, BooleanExpression::True)
642 }
643
644 pub fn new_general_reach<
648 U: IntoIterator<Item = Location>,
649 C: IntoIterator<Item = (Location, u32)>,
650 >(
651 cover: C,
652 uncover: U,
653 ) -> Result<Self, ReachabilityTransformationError> {
654 let cover = cover.into_iter().collect::<HashMap<_, _>>();
655 let uncover = uncover.into_iter().collect::<HashSet<_>>();
656
657 Self::new_reach_with_var_constr(cover, uncover, BooleanExpression::True)
658 }
659
660 pub fn get_locations_appearing(&self) -> HashSet<&Location> {
662 self.lower_bounds
663 .keys()
664 .chain(self.locations_not_covered.iter())
665 .collect()
666 }
667
668 pub fn get_variable_constraint(&self) -> &LIAVariableConstraint {
670 &self.variable_constr
671 }
672
673 pub fn new_reach_with_var_constr<
675 LC: Into<HashMap<Location, u32>>,
676 LU: Into<HashSet<Location>>,
677 >(
678 locs: LC,
679 uncover: LU,
680 var_constr: BooleanExpression<Variable>,
681 ) -> Result<Self, ReachabilityTransformationError> {
682 let lia_constr = (&var_constr).try_into().map_err(|err| {
683 ReachabilityTransformationError::VariableConstraintNotLinearArithmetic(err, var_constr)
684 })?;
685
686 Ok(TargetConfig {
687 lower_bounds: locs.into(),
688 locations_not_covered: uncover.into(),
689 variable_constr: lia_constr,
690 })
691 }
692
693 fn new_with_var_constr(
696 var_constr: BooleanExpression<Variable>,
697 ) -> Result<Self, ReachabilityTransformationError> {
698 let lia_constr = (&var_constr).try_into().map_err(|err| {
699 ReachabilityTransformationError::VariableConstraintNotLinearArithmetic(err, var_constr)
700 })?;
701
702 Ok(TargetConfig {
703 lower_bounds: HashMap::new(),
704 locations_not_covered: HashSet::new(),
705 variable_constr: lia_constr,
706 })
707 }
708
709 fn is_reachability_constraint(&self) -> bool {
713 !self.locations_not_covered.is_empty()
714 }
715
716 pub fn get_locations_to_cover(&self) -> impl Iterator<Item = (&Location, &u32)> {
719 self.lower_bounds.iter()
720 }
721
722 pub fn get_locations_to_uncover(&self) -> impl Iterator<Item = &Location> {
724 self.locations_not_covered.iter()
725 }
726
727 pub fn into_disjunct_with_name<S: ToString>(self, name: S) -> DisjunctionTargetConfig {
730 DisjunctionTargetConfig::new_with_single_disjunct(name.to_string(), self)
731 }
732
733 fn try_from_loc_constr(
742 mut constr: LocConstraint,
743 ) -> Result<Self, ReachabilityTransformationError> {
744 let single_loc = Threshold::from_integer_comp_expr(constr.0.clone(), constr.2.clone());
745
746 if let Err(err) = single_loc {
749 let err =
750 UpwardsClosedSetExtractionError::parse_from_constraint_rewrite_err(err, constr);
751 return Err(
752 ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
753 );
754 }
755 let (locs, mut thr) = single_loc.unwrap();
756
757 if locs.len() != 1 {
758 let err = UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(
759 Box::new(constr),
760 );
761
762 return Err(
763 ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
764 );
765 }
766
767 if !thr.is_constant() {
769 let err =
770 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(constr));
771
772 return Err(
773 ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
774 );
775 }
776
777 let (loc, scale) = locs.into_iter().next().unwrap();
778
779 if scale.is_negative() {
781 thr.scale(-Fraction::from(1));
782 constr.1 = constr.1.get_swap_side();
783 }
784
785 let c = thr.get_const().unwrap();
786
787 match constr.1 {
788 ComparisonOp::Gt | ComparisonOp::Geq => {
789 if c.is_negative() {
791 return Ok(Self::new_unconstrained());
792 }
793
794 let c = if let Ok(c_i64) = i64::try_from(c) {
795 let mut c = match u32::try_from(c_i64) {
797 Ok(val) => val,
798 Err(_) => {
799 error!(
801 "Threshold constant {} is out of bounds for u32 (must be between 0 and {}). Truncating to u32::MAX.",
802 c_i64,
803 u32::MAX
804 );
805 u32::MAX
806 }
807 };
808 if constr.1 == ComparisonOp::Gt {
810 if c < u32::MAX {
811 c += 1
812 } else {
813 error!(
814 "Fraction value for lower bound is out of range: {}. Returning unconstrained specification.",
815 constr.1
816 );
817 return Ok(Self::new_unconstrained());
818 }
819 }
820
821 c
822 } else {
823 let num = c.numerator() as f64;
826 let den = c.denominator() as f64;
827
828 let lower_bound = (num / den).ceil();
830 lower_bound as u32
831 };
832
833 return Self::new_reach_with_var_constr([(loc, c)], [], BooleanExpression::True);
834 }
835 ComparisonOp::Eq | ComparisonOp::Leq => {
836 if c == Fraction::from(0) {
837 return Ok(Self::new_loc_not_covered(loc));
838 }
839 }
840 ComparisonOp::Neq => {
841 if c == Fraction::from(0) {
842 return Self::new_cover([loc]);
843 }
844 }
845 ComparisonOp::Lt => {
846 if c == Fraction::from(1) {
847 return Ok(Self::new_loc_not_covered(loc));
848 }
849 }
850 }
851
852 let err =
853 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(constr));
854 Err(ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err))
855 }
856}
857
858impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
859 EncodeToSMT<TargetConfig, C> for TargetConfig
860{
861 fn encode_to_smt_with_ctx(
862 &self,
863 solver: &taco_smt_encoder::SMTSolver,
864 ctx: &C,
865 ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
866 let lower_bounds = self
868 .lower_bounds
869 .iter()
870 .map(|(loc, n)| {
871 let loc = ctx.get_expr_for(loc)?;
872 let n = solver.numeral(*n);
873
874 Ok(solver.gte(loc, n))
875 })
876 .collect::<Result<Vec<_>, SMTSolverError>>()?;
877
878 let loc_uncover = self
880 .locations_not_covered
881 .iter()
882 .map(|loc| {
883 let loc = ctx.get_expr_for(loc)?;
884 let zero = solver.numeral(0);
885
886 Ok(solver.eq(loc, zero))
887 })
888 .collect::<Result<Vec<_>, SMTSolverError>>()?;
889
890 let var_constr = self
892 .variable_constr
893 .as_boolean_expr()
894 .encode_to_smt_with_ctx(solver, ctx)?;
895
896 let res = solver.and_many(
897 lower_bounds
898 .into_iter()
899 .chain(loc_uncover)
900 .chain([var_constr]),
901 );
902
903 Ok(res)
904 }
905}
906
907impl fmt::Display for TargetConfig {
908 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909 let lower_bounds = join_iterator(
910 self.lower_bounds
911 .iter()
912 .map(|(loc, n)| format!("({loc} >= {n})")),
913 " && ",
914 );
915
916 let not_covered = join_iterator(
917 self.locations_not_covered
918 .iter()
919 .map(|loc| format!("({loc} == 0)")),
920 " && ",
921 );
922
923 write!(
924 f,
925 "{lower_bounds} && {not_covered} && {}",
926 self.variable_constr
927 )
928 }
929}
930
931#[derive(Debug, Clone, PartialEq)]
934pub enum UpwardsClosedSetExtractionError {
935 LocationConstraintOverMultipleLocs(Box<LocConstraint>),
939 LocationConstraintNonLinear(Box<LocConstraint>),
944 LocationConstraintNotUpwardsClosed(Box<LocConstraint>),
947 LocationConstraintWithParameters(Box<LocConstraint>),
950}
951
952impl UpwardsClosedSetExtractionError {
953 pub fn parse_from_constraint_rewrite_err(
956 err: ConstraintRewriteError,
957 constr: LocConstraint,
958 ) -> Self {
959 match err {
960 ConstraintRewriteError::NotLinearArithmetic => {
961 UpwardsClosedSetExtractionError::LocationConstraintNonLinear(Box::new(constr))
962 }
963 ConstraintRewriteError::ParameterConstraint(_) => {
964 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(constr))
965 }
966 }
967 }
968}
969
970impl fmt::Display for UpwardsClosedSetExtractionError {
971 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
972 match self {
973 UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(constr) => {
974 write!(
975 f,
976 "Location constraints referencing multiple locations unsupported in clauses behind temporal operators. Clause '{} {} {}' references more than one location",
977 constr.0, constr.1, constr.2
978 )
979 }
980 UpwardsClosedSetExtractionError::LocationConstraintNonLinear(constr) => write!(
981 f,
982 "Location constraints containing non-linear arithmetic unsupported. Clause '{} {} {}' is not a linear arithmetic expression",
983 constr.0, constr.1, constr.2
984 ),
985 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(constr) => {
986 write!(
987 f,
988 "Locations constraint does not have an upwards closed set of error states. Such specifications are not supported. Clause '{} {} {}'",
989 constr.0, constr.1, constr.2
990 )
991 }
992 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(constr) => {
993 write!(
994 f,
995 "Locations constraint with parameters not supported behind temporal operator. Clause '{} {} {}'",
996 constr.0, constr.1, constr.2
997 )
998 }
999 }
1000 }
1001}
1002
1003impl error::Error for UpwardsClosedSetExtractionError {}
1004
1005#[derive(Clone, Debug, PartialEq)]
1007pub enum ReachabilityTransformationError {
1008 LivenessSpecification,
1010 ReachableConfigurationsNotUpwardsClosed(UpwardsClosedSetExtractionError),
1012 VariableConstraintNotLinearArithmetic(ConstraintRewriteError, BooleanExpression<Variable>),
1014}
1015
1016impl fmt::Display for ReachabilityTransformationError {
1017 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018 match self {
1019 Self::LivenessSpecification => write!(
1020 f,
1021 "Property contains liveness specification that are not yet supported"
1022 ),
1023 Self::ReachableConfigurationsNotUpwardsClosed(err) => {
1024 write!(
1025 f,
1026 "Specification extraction failed because the set of error configurations is not upwards closed. Detailed error: {err}"
1027 )
1028 }
1029 Self::VariableConstraintNotLinearArithmetic(rewrite_err, bexpr) => write!(
1030 f,
1031 "Specification extraction failed because the variable constraint '{bexpr}'. Detailed error: {rewrite_err}"
1032 ),
1033 }
1034 }
1035}
1036
1037impl error::Error for ReachabilityTransformationError {}
1038
1039impl From<UpwardsClosedSetExtractionError> for ReachabilityTransformationError {
1040 fn from(value: UpwardsClosedSetExtractionError) -> Self {
1041 ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(value)
1042 }
1043}
1044
1045type LocConstraint = (
1051 IntegerExpression<Location>,
1052 ComparisonOp,
1053 IntegerExpression<Location>,
1054);
1055
1056trait And {
1058 fn and(self, other: Self) -> Self;
1060}
1061
1062trait Or {
1064 fn or(self, other: Self) -> Self;
1066}
1067
1068#[cfg(test)]
1069mod test {
1070 use std::collections::{HashMap, HashSet};
1071
1072 use taco_smt_encoder::{
1073 SMTSolverBuilder, SMTSolverContext,
1074 expression_encoding::{EncodeToSMT, SMTVariableContext, StaticSMTContext},
1075 };
1076 use taco_threshold_automaton::{
1077 BooleanVarConstraint,
1078 expressions::{
1079 BooleanExpression, ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter,
1080 Variable,
1081 },
1082 general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder,
1083 lia_threshold_automaton::LIAVariableConstraint,
1084 };
1085
1086 use crate::{
1087 TargetSpec,
1088 eltl::ELTLExpression,
1089 reachability_specification::{
1090 DisjunctionTargetConfig, Reachability, ReachabilityProperty,
1091 ReachabilityTransformationError, TargetConfig, UpwardsClosedSetExtractionError,
1092 },
1093 };
1094
1095 #[test]
1096 fn test_target_config_new_cover() {
1097 let cover = TargetConfig::new_cover([
1098 Location::new("l1"),
1099 Location::new("l2"),
1100 Location::new("l3"),
1101 ])
1102 .unwrap();
1103
1104 let expected = TargetConfig {
1105 lower_bounds: HashMap::from([
1106 (Location::new("l1"), 1),
1107 (Location::new("l2"), 1),
1108 (Location::new("l3"), 1),
1109 ]),
1110 locations_not_covered: HashSet::new(),
1111 variable_constr: LIAVariableConstraint::True,
1112 };
1113
1114 assert_eq!(cover, expected);
1115 }
1116
1117 #[test]
1118 fn test_reach_property_new() {
1119 let cover = TargetConfig::new_cover([
1120 Location::new("l1"),
1121 Location::new("l2"),
1122 Location::new("l3"),
1123 ])
1124 .unwrap()
1125 .into_disjunct_with_name("test");
1126
1127 let reach_prop = ReachabilityProperty::new("test", cover);
1128
1129 let expected = ReachabilityProperty {
1130 name: "test".into(),
1131 disj: vec![Reachability {
1132 precondition_par: Vec::new(),
1133 precondition_loc: Vec::new(),
1134 precondition_var: Vec::new(),
1135 target: DisjunctionTargetConfig {
1136 property_name: "test".into(),
1137 targets: vec![TargetConfig {
1138 lower_bounds: HashMap::from([
1139 (Location::new("l1"), 1),
1140 (Location::new("l2"), 1),
1141 (Location::new("l3"), 1),
1142 ]),
1143 locations_not_covered: HashSet::new(),
1144 variable_constr: LIAVariableConstraint::True,
1145 }],
1146 },
1147 }],
1148 };
1149
1150 assert_eq!(reach_prop, expected);
1151 }
1152
1153 #[test]
1154 fn test_get_target_condition() {
1155 let cover = TargetConfig::new_cover([
1156 Location::new("l1"),
1157 Location::new("l2"),
1158 Location::new("l3"),
1159 ])
1160 .unwrap()
1161 .into_disjunct_with_name("test");
1162
1163 let reach: Reachability = cover.clone().into();
1164 assert_eq!(reach.get_target_conditions(), &cover);
1165 }
1166
1167 #[test]
1168 fn test_target_config_new_reach() {
1169 let reach = TargetConfig::new_reach(
1170 [
1171 Location::new("l1"),
1172 Location::new("l2"),
1173 Location::new("l3"),
1174 ],
1175 [
1176 Location::new("l4"),
1177 Location::new("l5"),
1178 Location::new("l6"),
1179 ],
1180 )
1181 .unwrap();
1182
1183 let expected = TargetConfig {
1184 lower_bounds: HashMap::from([
1185 (Location::new("l1"), 1),
1186 (Location::new("l2"), 1),
1187 (Location::new("l3"), 1),
1188 ]),
1189 locations_not_covered: HashSet::from([
1190 Location::new("l4"),
1191 Location::new("l5"),
1192 Location::new("l6"),
1193 ]),
1194 variable_constr: LIAVariableConstraint::True,
1195 };
1196
1197 assert_eq!(reach, expected);
1198 }
1199
1200 #[test]
1201 fn test_target_config_new_reach_with_var_constr() {
1202 let reach = TargetConfig::new_reach_with_var_constr(
1203 [
1204 (Location::new("l1"), 1),
1205 (Location::new("l2"), 2),
1206 (Location::new("l3"), 3),
1207 ],
1208 [
1209 Location::new("l4"),
1210 Location::new("l5"),
1211 Location::new("l6"),
1212 ],
1213 BooleanVarConstraint::ComparisonExpression(
1214 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1215 ComparisonOp::Eq,
1216 Box::new(IntegerExpression::Const(1)),
1217 ),
1218 )
1219 .unwrap();
1220
1221 let expected = TargetConfig {
1222 lower_bounds: HashMap::from([
1223 (Location::new("l1"), 1),
1224 (Location::new("l2"), 2),
1225 (Location::new("l3"), 3),
1226 ]),
1227 locations_not_covered: HashSet::from([
1228 Location::new("l4"),
1229 Location::new("l5"),
1230 Location::new("l6"),
1231 ]),
1232 variable_constr: (&BooleanVarConstraint::ComparisonExpression(
1233 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1234 ComparisonOp::Eq,
1235 Box::new(IntegerExpression::Const(1)),
1236 ))
1237 .try_into()
1238 .unwrap(),
1239 };
1240
1241 assert_eq!(reach, expected);
1242 }
1243
1244 #[test]
1246 fn test_target_config_try_loc_constr_translatable_cases() {
1247 let test_expr = (
1249 IntegerExpression::Atom(Location::new("l")),
1250 ComparisonOp::Eq,
1251 IntegerExpression::Const(0),
1252 );
1253
1254 let got = TargetConfig::try_from_loc_constr(test_expr);
1255 assert!(got.is_ok());
1256
1257 let expected = TargetConfig {
1258 lower_bounds: HashMap::new(),
1259 locations_not_covered: HashSet::from([Location::new("l")]),
1260 variable_constr: LIAVariableConstraint::True,
1261 };
1262
1263 assert_eq!(got.unwrap(), expected);
1264
1265 let test_expr = (
1267 IntegerExpression::Atom(Location::new("l")),
1268 ComparisonOp::Neq,
1269 IntegerExpression::Const(0),
1270 );
1271
1272 let got = TargetConfig::try_from_loc_constr(test_expr);
1273 assert!(got.is_ok());
1274
1275 let expected = TargetConfig {
1276 lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1277 locations_not_covered: HashSet::new(),
1278 variable_constr: LIAVariableConstraint::True,
1279 };
1280
1281 assert_eq!(got.unwrap(), expected);
1282
1283 let test_expr = (
1285 IntegerExpression::Atom(Location::new("l")),
1286 ComparisonOp::Gt,
1287 IntegerExpression::Const(0),
1288 );
1289
1290 let got = TargetConfig::try_from_loc_constr(test_expr);
1291 assert!(got.is_ok());
1292
1293 let expected = TargetConfig {
1294 lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1295 locations_not_covered: HashSet::new(),
1296 variable_constr: LIAVariableConstraint::True,
1297 };
1298
1299 assert_eq!(got.unwrap(), expected);
1300
1301 let test_expr = (
1303 IntegerExpression::Atom(Location::new("l")),
1304 ComparisonOp::Gt,
1305 IntegerExpression::Const(42),
1306 );
1307
1308 let got = TargetConfig::try_from_loc_constr(test_expr);
1309 assert!(got.is_ok());
1310
1311 let expected = TargetConfig {
1312 lower_bounds: HashMap::from([(Location::new("l"), 43)]),
1313 locations_not_covered: HashSet::new(),
1314 variable_constr: LIAVariableConstraint::True,
1315 };
1316
1317 assert_eq!(got.unwrap(), expected);
1318
1319 let test_expr = (
1321 IntegerExpression::Atom(Location::new("l")),
1322 ComparisonOp::Geq,
1323 IntegerExpression::Const(42),
1324 );
1325
1326 let got = TargetConfig::try_from_loc_constr(test_expr);
1327 assert!(got.is_ok());
1328
1329 let expected = TargetConfig {
1330 lower_bounds: HashMap::from([(Location::new("l"), 42)]),
1331 locations_not_covered: HashSet::new(),
1332 variable_constr: LIAVariableConstraint::True,
1333 };
1334
1335 assert_eq!(got.unwrap(), expected);
1336
1337 let test_expr = (
1339 IntegerExpression::Atom(Location::new("l")),
1340 ComparisonOp::Gt,
1341 IntegerExpression::BinaryExpr(
1342 Box::new(IntegerExpression::Const(2)),
1343 IntegerOp::Div,
1344 Box::new(IntegerExpression::Const(3)),
1345 ),
1346 );
1347
1348 let got = TargetConfig::try_from_loc_constr(test_expr);
1349 assert!(got.is_ok());
1350
1351 let expected = TargetConfig {
1352 lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1353 locations_not_covered: HashSet::new(),
1354 variable_constr: LIAVariableConstraint::True,
1355 };
1356
1357 assert_eq!(got.unwrap(), expected);
1358
1359 let test_expr = (
1361 IntegerExpression::Atom(Location::new("l")),
1362 ComparisonOp::Gt,
1363 IntegerExpression::BinaryExpr(
1364 Box::new(IntegerExpression::Const(2)),
1365 IntegerOp::Div,
1366 Box::new(IntegerExpression::Const(3)),
1367 ),
1368 );
1369
1370 let got = TargetConfig::try_from_loc_constr(test_expr);
1371 assert!(got.is_ok());
1372
1373 let expected = TargetConfig {
1374 lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1375 locations_not_covered: HashSet::new(),
1376 variable_constr: LIAVariableConstraint::True,
1377 };
1378
1379 assert_eq!(got.unwrap(), expected);
1380
1381 let test_expr = (
1383 IntegerExpression::Const(42),
1384 ComparisonOp::Lt,
1385 IntegerExpression::Atom(Location::new("l")),
1386 );
1387
1388 let got = TargetConfig::try_from_loc_constr(test_expr);
1389 assert!(got.is_ok(), "{:#?}", got);
1390
1391 let expected = TargetConfig {
1392 lower_bounds: HashMap::from([(Location::new("l"), 43)]),
1393 locations_not_covered: HashSet::new(),
1394 variable_constr: LIAVariableConstraint::True,
1395 };
1396
1397 assert_eq!(got.unwrap(), expected);
1398
1399 let test_expr = (
1401 IntegerExpression::Atom(Location::new("l")),
1402 ComparisonOp::Gt,
1403 IntegerExpression::BinaryExpr(
1404 Box::new(IntegerExpression::Const(0)),
1405 IntegerOp::Add,
1406 Box::new(IntegerExpression::BinaryExpr(
1407 Box::new(IntegerExpression::Const(1)),
1408 IntegerOp::Add,
1409 Box::new(IntegerExpression::Const(2)),
1410 )),
1411 ),
1412 );
1413
1414 let got = TargetConfig::try_from_loc_constr(test_expr);
1415 assert!(got.is_ok());
1416
1417 let expected = TargetConfig {
1418 lower_bounds: HashMap::from([(Location::new("l"), 4)]),
1419 locations_not_covered: HashSet::new(),
1420 variable_constr: LIAVariableConstraint::True,
1421 };
1422
1423 assert_eq!(got.unwrap(), expected);
1424
1425 let test_expr = (
1427 IntegerExpression::Atom(Location::new("l")),
1428 ComparisonOp::Lt,
1429 IntegerExpression::Const(1),
1430 );
1431
1432 let got = TargetConfig::try_from_loc_constr(test_expr);
1433 assert!(got.is_ok());
1434
1435 let expected = TargetConfig {
1436 lower_bounds: HashMap::new(),
1437 locations_not_covered: HashSet::from([Location::new("l")]),
1438 variable_constr: LIAVariableConstraint::True,
1439 };
1440
1441 assert_eq!(got.unwrap(), expected);
1442
1443 let test_expr = (
1445 IntegerExpression::Atom(Location::new("l")),
1446 ComparisonOp::Leq,
1447 IntegerExpression::Const(0),
1448 );
1449
1450 let got = TargetConfig::try_from_loc_constr(test_expr);
1451 assert!(got.is_ok());
1452
1453 let expected = TargetConfig {
1454 lower_bounds: HashMap::new(),
1455 locations_not_covered: HashSet::from([Location::new("l")]),
1456 variable_constr: LIAVariableConstraint::True,
1457 };
1458
1459 assert_eq!(got.unwrap(), expected);
1460 }
1461
1462 #[test]
1464 fn test_target_config_try_loc_constr_error_cases() {
1465 let test_expr = (
1467 IntegerExpression::BinaryExpr(
1468 Box::new(IntegerExpression::Atom(Location::new("l1"))),
1469 IntegerOp::Add,
1470 Box::new(IntegerExpression::Atom(Location::new("l2"))),
1471 ),
1472 ComparisonOp::Eq,
1473 IntegerExpression::Const(0),
1474 );
1475
1476 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1477 assert!(got.is_err(), "{:#?}", got);
1478
1479 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1480 UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(Box::new(
1481 test_expr,
1482 )),
1483 );
1484 assert_eq!(got.unwrap_err(), expected);
1485
1486 let test_expr = (
1488 IntegerExpression::BinaryExpr(
1489 Box::new(IntegerExpression::Atom(Location::new("l"))),
1490 IntegerOp::Mul,
1491 Box::new(IntegerExpression::Atom(Location::new("l"))),
1492 ),
1493 ComparisonOp::Eq,
1494 IntegerExpression::Const(0),
1495 );
1496
1497 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1498 assert!(got.is_err());
1499
1500 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1501 UpwardsClosedSetExtractionError::LocationConstraintNonLinear(Box::new(test_expr)),
1502 );
1503 assert_eq!(got.unwrap_err(), expected);
1504
1505 let test_expr = (
1507 IntegerExpression::BinaryExpr(
1508 Box::new(IntegerExpression::Atom(Location::new("l"))),
1509 IntegerOp::Add,
1510 Box::new(IntegerExpression::Param(Parameter::new("n"))),
1511 ),
1512 ComparisonOp::Eq,
1513 IntegerExpression::Const(0),
1514 );
1515
1516 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1517 assert!(got.is_err());
1518
1519 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1520 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(test_expr)),
1521 );
1522 assert_eq!(got.unwrap_err(), expected);
1523
1524 let test_expr = (
1526 IntegerExpression::Atom(Location::new("l")),
1527 ComparisonOp::Lt,
1528 IntegerExpression::Const(3),
1529 );
1530
1531 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1532 assert!(got.is_err());
1533
1534 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1535 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1536 test_expr,
1537 )),
1538 );
1539 assert_eq!(got.unwrap_err(), expected);
1540
1541 let test_expr = (
1543 IntegerExpression::Atom(Location::new("l")),
1544 ComparisonOp::Leq,
1545 IntegerExpression::Const(3),
1546 );
1547
1548 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1549 assert!(got.is_err());
1550
1551 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1552 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1553 test_expr,
1554 )),
1555 );
1556 assert_eq!(got.unwrap_err(), expected);
1557
1558 let test_expr = (
1560 IntegerExpression::Atom(Location::new("l")),
1561 ComparisonOp::Eq,
1562 IntegerExpression::Const(3),
1563 );
1564
1565 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1566 assert!(got.is_err());
1567
1568 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1569 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1570 test_expr,
1571 )),
1572 );
1573 assert_eq!(got.unwrap_err(), expected);
1574
1575 let test_expr = (
1577 IntegerExpression::Atom(Location::new("l")),
1578 ComparisonOp::Neq,
1579 IntegerExpression::Const(3),
1580 );
1581
1582 let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1583 assert!(got.is_err());
1584
1585 let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1586 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1587 test_expr,
1588 )),
1589 );
1590 assert_eq!(got.unwrap_err(), expected);
1591 }
1592
1593 #[test]
1594 fn test_target_config_encode_target_config_unconstrained() {
1595 let solver_builder = SMTSolverBuilder::default();
1596
1597 let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1598
1599 let config = TargetConfig::new_unconstrained();
1600
1601 let got_expr = config
1602 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1603 .unwrap();
1604
1605 let expected_expr = ctx.get_true();
1606 assert_eq!(got_expr, expected_expr)
1607 }
1608
1609 #[test]
1610 fn test_target_config_encode_target_config_single_loc_cov() {
1611 let solver_builder = SMTSolverBuilder::default();
1612
1613 let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1614
1615 let config = TargetConfig::new_cover([Location::new("l")]).unwrap();
1616
1617 let got_expr = config
1618 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1619 .unwrap();
1620
1621 let solver = ctx.get_smt_solver();
1622
1623 let expected_expr = solver.and(
1624 solver.gte(
1625 ctx.get_expr_for(&Location::new("l")).unwrap(),
1626 solver.numeral(1),
1627 ),
1628 solver.true_(),
1629 );
1630
1631 assert_eq!(
1632 got_expr,
1633 expected_expr,
1634 "got: {}, expected: {}",
1635 solver.display(got_expr),
1636 solver.display(expected_expr)
1637 )
1638 }
1639
1640 #[test]
1641 fn test_target_config_encode_target_config_single_loc_uncover() {
1642 let solver_builder = SMTSolverBuilder::default();
1643
1644 let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1645
1646 let config = TargetConfig::new_loc_not_covered(Location::new("l"));
1647
1648 let got_expr = config
1649 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1650 .unwrap();
1651
1652 let solver = ctx.get_smt_solver();
1653
1654 let expected_expr = solver.and(
1655 solver.eq(
1656 ctx.get_expr_for(&Location::new("l")).unwrap(),
1657 solver.numeral(0),
1658 ),
1659 solver.true_(),
1660 );
1661
1662 assert_eq!(
1663 got_expr,
1664 expected_expr,
1665 "got: {}, expected: {}",
1666 solver.display(got_expr),
1667 solver.display(expected_expr)
1668 )
1669 }
1670
1671 #[test]
1672 fn test_target_config_encode_target_config_var_constr() {
1673 let solver_builder = SMTSolverBuilder::default();
1674
1675 let ctx = StaticSMTContext::new(
1676 solver_builder,
1677 [],
1678 [Location::new("l")],
1679 [Variable::new("v")],
1680 )
1681 .unwrap();
1682
1683 let config = TargetConfig::new_with_var_constr(BooleanVarConstraint::ComparisonExpression(
1684 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1685 ComparisonOp::Eq,
1686 Box::new(IntegerExpression::Const(1)),
1687 ))
1688 .unwrap();
1689
1690 let got_expr = config
1691 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1692 .unwrap();
1693
1694 let solver = ctx.get_smt_solver();
1695
1696 let expected_expr = solver.and(
1697 solver.lt(
1698 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1699 solver.numeral(2),
1700 ),
1701 solver.gte(
1702 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1703 solver.numeral(1),
1704 ),
1705 );
1706
1707 assert_eq!(
1708 got_expr,
1709 expected_expr,
1710 "got: {}, expected: {}",
1711 solver.display(got_expr),
1712 solver.display(expected_expr)
1713 )
1714 }
1715
1716 #[test]
1717 fn test_target_config_encode_target_config_all_constr() {
1718 let solver_builder = SMTSolverBuilder::default();
1719
1720 let ctx = StaticSMTContext::new(
1721 solver_builder,
1722 [],
1723 [Location::new("l1"), Location::new("l2")],
1724 [Variable::new("v")],
1725 )
1726 .unwrap();
1727
1728 let config = TargetConfig::new_reach_with_var_constr(
1729 [(Location::new("l1"), 42)],
1730 [Location::new("l2")],
1731 BooleanVarConstraint::ComparisonExpression(
1732 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1733 ComparisonOp::Eq,
1734 Box::new(IntegerExpression::Const(1)),
1735 ),
1736 )
1737 .unwrap();
1738 let got_expr = config
1739 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1740 .unwrap();
1741
1742 let solver = ctx.get_smt_solver();
1743
1744 let expected_expr = solver.and_many([
1745 solver.gte(
1746 ctx.get_expr_for(&Location::new("l1")).unwrap(),
1747 solver.numeral(42),
1748 ),
1749 solver.eq(
1750 ctx.get_expr_for(&Location::new("l2")).unwrap(),
1751 solver.numeral(0),
1752 ),
1753 solver.and(
1754 solver.lt(
1755 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1756 solver.numeral(2),
1757 ),
1758 solver.gte(
1759 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1760 solver.numeral(1),
1761 ),
1762 ),
1763 ]);
1764
1765 assert_eq!(
1766 got_expr,
1767 expected_expr,
1768 "got: {}, expected: {}",
1769 solver.display(got_expr),
1770 solver.display(expected_expr)
1771 )
1772 }
1773
1774 #[test]
1775 fn test_target_config_locations_appearing() {
1776 let config = TargetConfig::new_reach_with_var_constr(
1777 [(Location::new("l1"), 42)],
1778 [Location::new("l2")],
1779 BooleanVarConstraint::ComparisonExpression(
1780 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1781 ComparisonOp::Eq,
1782 Box::new(IntegerExpression::Const(1)),
1783 ),
1784 )
1785 .unwrap();
1786
1787 let got_expr: HashSet<Location> = config
1788 .get_locations_appearing()
1789 .into_iter()
1790 .cloned()
1791 .collect();
1792
1793 let expected_locs = HashSet::from([Location::new("l1"), Location::new("l2")]);
1794
1795 assert_eq!(got_expr, expected_locs)
1796 }
1797
1798 #[test]
1799 fn test_target_config_is_reachability_constr() {
1800 let config = TargetConfig::new_reach_with_var_constr(
1801 [(Location::new("l1"), 42)],
1802 [Location::new("l2")],
1803 BooleanVarConstraint::ComparisonExpression(
1804 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1805 ComparisonOp::Eq,
1806 Box::new(IntegerExpression::Const(1)),
1807 ),
1808 )
1809 .unwrap();
1810
1811 assert!(config.is_reachability_constraint());
1812
1813 let config = TargetConfig::new_reach_with_var_constr(
1814 [(Location::new("l1"), 42)],
1815 [],
1816 BooleanVarConstraint::ComparisonExpression(
1817 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1818 ComparisonOp::Eq,
1819 Box::new(IntegerExpression::Const(1)),
1820 ),
1821 )
1822 .unwrap();
1823
1824 assert!(!config.is_reachability_constraint());
1825 }
1826
1827 #[test]
1828 fn test_disjunction_target_config_get_locations_in_target() {
1829 let config1 = TargetConfig::new_reach_with_var_constr(
1830 [(Location::new("l1"), 42)],
1831 [Location::new("l2")],
1832 BooleanVarConstraint::ComparisonExpression(
1833 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1834 ComparisonOp::Eq,
1835 Box::new(IntegerExpression::Const(1)),
1836 ),
1837 )
1838 .unwrap();
1839
1840 let config2 = TargetConfig::new_reach_with_var_constr(
1841 [(Location::new("l3"), 42)],
1842 [Location::new("l4")],
1843 BooleanVarConstraint::ComparisonExpression(
1844 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1845 ComparisonOp::Eq,
1846 Box::new(IntegerExpression::Const(1)),
1847 ),
1848 )
1849 .unwrap();
1850
1851 let dis = DisjunctionTargetConfig {
1852 property_name: "test".into(),
1853 targets: vec![config1, config2],
1854 };
1855
1856 let got_set_locations: HashSet<Location> =
1857 dis.get_locations_in_target().into_iter().cloned().collect();
1858
1859 let expected_set_locations = HashSet::from([
1860 Location::new("l1"),
1861 Location::new("l2"),
1862 Location::new("l3"),
1863 Location::new("l4"),
1864 ]);
1865
1866 assert_eq!(got_set_locations, expected_set_locations);
1867
1868 let dis = DisjunctionTargetConfig::new_empty("test".into());
1869
1870 let got_set_locations: HashSet<_> = dis.get_locations_in_target().into_iter().collect();
1871
1872 let expected_set_locations = HashSet::from([]);
1873
1874 assert_eq!(got_set_locations, expected_set_locations);
1875 }
1876
1877 #[test]
1878 fn test_disj_target_config_contains_reachability_constraint() {
1879 let config1 = TargetConfig::new_reach_with_var_constr(
1880 [(Location::new("l1"), 42)],
1881 [],
1882 BooleanVarConstraint::ComparisonExpression(
1883 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1884 ComparisonOp::Eq,
1885 Box::new(IntegerExpression::Const(1)),
1886 ),
1887 )
1888 .unwrap();
1889
1890 let config2 = TargetConfig::new_reach_with_var_constr(
1891 [(Location::new("l3"), 42)],
1892 [Location::new("l4")],
1893 BooleanVarConstraint::ComparisonExpression(
1894 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1895 ComparisonOp::Eq,
1896 Box::new(IntegerExpression::Const(1)),
1897 ),
1898 )
1899 .unwrap();
1900
1901 let dis = DisjunctionTargetConfig {
1902 property_name: "test".into(),
1903 targets: vec![config1, config2],
1904 };
1905
1906 assert!(dis.contains_reachability_constraint());
1907
1908 let disj = DisjunctionTargetConfig::new_empty("test".into());
1909
1910 assert!(!disj.contains_reachability_constraint())
1911 }
1912
1913 #[test]
1914 fn test_disj_target_config_encode_to_smt_empty() {
1915 let solver_builder = SMTSolverBuilder::default();
1916
1917 let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1918
1919 let solver = ctx.get_smt_solver();
1920
1921 let disj = DisjunctionTargetConfig::new_empty("test".into());
1922
1923 let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1924
1925 let expected_expr = solver.false_();
1926
1927 assert_eq!(
1928 got_expr,
1929 expected_expr,
1930 "got: {}, expected: {}",
1931 solver.display(got_expr),
1932 solver.display(expected_expr)
1933 )
1934 }
1935
1936 #[test]
1937 fn test_disj_target_config_encode_smt() {
1938 let solver_builder = SMTSolverBuilder::default();
1939
1940 let ctx = StaticSMTContext::new(
1941 solver_builder,
1942 [],
1943 [
1944 Location::new("l1"),
1945 Location::new("l2"),
1946 Location::new("l3"),
1947 Location::new("l4"),
1948 ],
1949 [Variable::new("v")],
1950 )
1951 .unwrap();
1952
1953 let solver = ctx.get_smt_solver();
1954
1955 let config1 = TargetConfig::new_reach_with_var_constr(
1956 [(Location::new("l1"), 42)],
1957 [Location::new("l2")],
1958 BooleanVarConstraint::ComparisonExpression(
1959 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1960 ComparisonOp::Eq,
1961 Box::new(IntegerExpression::Const(1)),
1962 ),
1963 )
1964 .unwrap();
1965
1966 let config2 = TargetConfig::new_reach_with_var_constr(
1967 [(Location::new("l3"), 42)],
1968 [Location::new("l4")],
1969 BooleanVarConstraint::ComparisonExpression(
1970 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1971 ComparisonOp::Eq,
1972 Box::new(IntegerExpression::Const(1)),
1973 ),
1974 )
1975 .unwrap();
1976
1977 let disj = DisjunctionTargetConfig {
1978 property_name: "test".into(),
1979 targets: vec![config1, config2],
1980 };
1981
1982 let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1983
1984 let expected_expr = solver.or_many([
1985 solver.and_many([
1986 solver.gte(
1987 ctx.get_expr_for(&Location::new("l1")).unwrap(),
1988 solver.numeral(42),
1989 ),
1990 solver.eq(
1991 ctx.get_expr_for(&Location::new("l2")).unwrap(),
1992 solver.numeral(0),
1993 ),
1994 solver.and(
1995 solver.lt(
1996 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1997 solver.numeral(2),
1998 ),
1999 solver.gte(
2000 ctx.get_expr_for(&Variable::new("v")).unwrap(),
2001 solver.numeral(1),
2002 ),
2003 ),
2004 ]),
2005 solver.and_many([
2006 solver.gte(
2007 ctx.get_expr_for(&Location::new("l3")).unwrap(),
2008 solver.numeral(42),
2009 ),
2010 solver.eq(
2011 ctx.get_expr_for(&Location::new("l4")).unwrap(),
2012 solver.numeral(0),
2013 ),
2014 solver.and(
2015 solver.lt(
2016 ctx.get_expr_for(&Variable::new("v")).unwrap(),
2017 solver.numeral(2),
2018 ),
2019 solver.gte(
2020 ctx.get_expr_for(&Variable::new("v")).unwrap(),
2021 solver.numeral(1),
2022 ),
2023 ),
2024 ]),
2025 ]);
2026
2027 assert_eq!(
2028 got_expr,
2029 expected_expr,
2030 "got: {}, expected: {}",
2031 solver.display(got_expr),
2032 solver.display(expected_expr)
2033 )
2034 }
2035
2036 #[test]
2037 fn test_reachability_property_get_name() {
2038 let reach_property = ReachabilityProperty {
2039 name: "test".to_string(),
2040 disj: vec![],
2041 };
2042
2043 assert_eq!(reach_property.name(), "test")
2044 }
2045
2046 #[test]
2047 fn test_reachability_property_contains_reachability_constraint() {
2048 let reach_property = ReachabilityProperty {
2049 name: "test".to_string(),
2050 disj: vec![],
2051 };
2052 assert!(!reach_property.contains_reachability_constraint());
2053
2054 let reach_property = ReachabilityProperty {
2055 name: "test".to_string(),
2056 disj: vec![Reachability {
2057 precondition_par: vec![],
2058 precondition_loc: vec![],
2059 precondition_var: vec![],
2060 target: DisjunctionTargetConfig {
2061 property_name: "test".into(),
2062 targets: vec![TargetConfig::new_loc_not_covered(Location::new("l"))],
2063 },
2064 }],
2065 };
2066 assert!(reach_property.contains_reachability_constraint());
2067 }
2068
2069 #[test]
2070 fn test_reachability_property_create_tas() {
2071 let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
2072 .with_parameter(Parameter::new("n"))
2073 .unwrap()
2074 .with_location(Location::new("l"))
2075 .unwrap()
2076 .with_variable(Variable::new("v"))
2077 .unwrap()
2078 .initialize()
2079 .build();
2080
2081 let reach_property = ReachabilityProperty {
2082 name: "test".to_string(),
2083 disj: vec![],
2084 };
2085 assert_eq!(reach_property.create_tas_to_check(&ta).count(), 0);
2086
2087 let reach_property = ReachabilityProperty {
2088 name: "test".to_string(),
2089 disj: vec![Reachability {
2090 precondition_par: vec![BooleanExpression::ComparisonExpression(
2091 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2092 ComparisonOp::Gt,
2093 Box::new(IntegerExpression::Const(42)),
2094 )],
2095 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2096 Box::new(IntegerExpression::Atom(Location::new("l"))),
2097 ComparisonOp::Eq,
2098 Box::new(IntegerExpression::Const(0)),
2099 )],
2100 precondition_var: vec![],
2101 target: DisjunctionTargetConfig {
2102 property_name: "test".into(),
2103 targets: vec![],
2104 },
2105 }],
2106 };
2107 let mut it = reach_property.create_tas_to_check(&ta);
2108 let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2109 .with_parameter(Parameter::new("n"))
2110 .unwrap()
2111 .with_location(Location::new("l"))
2112 .unwrap()
2113 .with_variable(Variable::new("v"))
2114 .unwrap()
2115 .initialize()
2116 .with_resilience_condition(BooleanExpression::ComparisonExpression(
2117 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2118 ComparisonOp::Gt,
2119 Box::new(IntegerExpression::Const(42)),
2120 ))
2121 .unwrap()
2122 .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2123 Box::new(IntegerExpression::Atom(Location::new("l"))),
2124 ComparisonOp::Eq,
2125 Box::new(IntegerExpression::Const(0)),
2126 ))
2127 .unwrap()
2128 .build();
2129 let expected_disj = DisjunctionTargetConfig {
2130 property_name: "test".into(),
2131 targets: vec![],
2132 };
2133 assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2134 assert_eq!(it.next(), None);
2135
2136 let reach_property = ReachabilityProperty {
2137 name: "test".to_string(),
2138 disj: vec![Reachability {
2139 precondition_par: vec![],
2140 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2141 Box::new(IntegerExpression::Atom(Location::new("l"))),
2142 ComparisonOp::Gt,
2143 Box::new(IntegerExpression::Const(42)),
2144 )],
2145 precondition_var: vec![],
2146 target: DisjunctionTargetConfig {
2147 property_name: "test".into(),
2148 targets: vec![],
2149 },
2150 }],
2151 };
2152 let mut it = reach_property.create_tas_to_check(&ta);
2153 let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2154 .with_parameter(Parameter::new("n"))
2155 .unwrap()
2156 .with_location(Location::new("l"))
2157 .unwrap()
2158 .with_variable(Variable::new("v"))
2159 .unwrap()
2160 .initialize()
2161 .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2162 Box::new(IntegerExpression::Atom(Location::new("l"))),
2163 ComparisonOp::Gt,
2164 Box::new(IntegerExpression::Const(42)),
2165 ))
2166 .unwrap()
2167 .build();
2168 let expected_disj = DisjunctionTargetConfig {
2169 property_name: "test".into(),
2170 targets: vec![],
2171 };
2172 assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2173 assert_eq!(it.next(), None);
2174
2175 let reach_property = ReachabilityProperty {
2176 name: "test".to_string(),
2177 disj: vec![Reachability {
2178 precondition_par: vec![],
2179 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2180 Box::new(IntegerExpression::Atom(Location::new("l"))),
2181 ComparisonOp::Gt,
2182 Box::new(IntegerExpression::Const(42)),
2183 )],
2184 precondition_var: vec![],
2185 target: DisjunctionTargetConfig {
2186 property_name: "test".into(),
2187 targets: vec![
2188 TargetConfig::new_unconstrained(),
2189 TargetConfig::new_cover([Location::new("l")]).unwrap(),
2190 ],
2191 },
2192 }],
2193 };
2194 let mut it = reach_property.create_tas_to_check(&ta);
2195 let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2196 .with_parameter(Parameter::new("n"))
2197 .unwrap()
2198 .with_location(Location::new("l"))
2199 .unwrap()
2200 .with_variable(Variable::new("v"))
2201 .unwrap()
2202 .initialize()
2203 .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2204 Box::new(IntegerExpression::Atom(Location::new("l"))),
2205 ComparisonOp::Gt,
2206 Box::new(IntegerExpression::Const(42)),
2207 ))
2208 .unwrap()
2209 .build();
2210 let expected_disj = DisjunctionTargetConfig {
2211 property_name: "test".into(),
2212 targets: vec![
2213 TargetConfig::new_unconstrained(),
2214 TargetConfig::new_cover([Location::new("l")]).unwrap(),
2215 ],
2216 };
2217 assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2218 assert_eq!(it.next(), None);
2219
2220 let reach_property = ReachabilityProperty {
2221 name: "test".to_string(),
2222 disj: vec![
2223 Reachability {
2224 precondition_par: vec![],
2225 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2226 Box::new(IntegerExpression::Atom(Location::new("l"))),
2227 ComparisonOp::Gt,
2228 Box::new(IntegerExpression::Const(42)),
2229 )],
2230 precondition_var: vec![],
2231 target: DisjunctionTargetConfig {
2232 targets: vec![],
2233 property_name: "test".into(),
2234 },
2235 },
2236 Reachability {
2237 precondition_par: vec![],
2238 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2239 Box::new(IntegerExpression::Atom(Location::new("l"))),
2240 ComparisonOp::Lt,
2241 Box::new(IntegerExpression::Const(42)),
2242 )],
2243 precondition_var: vec![],
2244 target: DisjunctionTargetConfig {
2245 property_name: "test".into(),
2246 targets: vec![],
2247 },
2248 },
2249 ],
2250 };
2251 let mut it = reach_property.create_tas_to_check(&ta);
2252 let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2253 .with_parameter(Parameter::new("n"))
2254 .unwrap()
2255 .with_location(Location::new("l"))
2256 .unwrap()
2257 .with_variable(Variable::new("v"))
2258 .unwrap()
2259 .initialize()
2260 .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2261 Box::new(IntegerExpression::Atom(Location::new("l"))),
2262 ComparisonOp::Gt,
2263 Box::new(IntegerExpression::Const(42)),
2264 ))
2265 .unwrap()
2266 .build();
2267 let expected_disj = DisjunctionTargetConfig {
2268 property_name: "test".into(),
2269 targets: vec![],
2270 };
2271 assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2272 let expected_disj = DisjunctionTargetConfig {
2273 property_name: "test".into(),
2274 targets: vec![],
2275 };
2276 let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2277 .with_parameter(Parameter::new("n"))
2278 .unwrap()
2279 .with_location(Location::new("l"))
2280 .unwrap()
2281 .with_variable(Variable::new("v"))
2282 .unwrap()
2283 .initialize()
2284 .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2285 Box::new(IntegerExpression::Atom(Location::new("l"))),
2286 ComparisonOp::Lt,
2287 Box::new(IntegerExpression::Const(42)),
2288 ))
2289 .unwrap()
2290 .build();
2291 assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2292 assert_eq!(it.next(), None);
2293 }
2294
2295 #[test]
2296 fn test_reachability_property_from_eltl() {
2297 let eltl = ELTLExpression::True;
2299
2300 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2301
2302 let expected_reach = ReachabilityProperty {
2303 name: "test".to_string(),
2304 disj: vec![],
2305 };
2306
2307 assert_eq!(got_reach, expected_reach);
2308
2309 let eltl = ELTLExpression::False;
2311
2312 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2313
2314 let expected_reach = ReachabilityProperty {
2315 name: "test".to_string(),
2316 disj: vec![Reachability::new_unconstrained("test".into())],
2317 };
2318
2319 assert_eq!(got_reach, expected_reach);
2320
2321 let eltl = ELTLExpression::ParameterExpr(
2323 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2324 ComparisonOp::Eq,
2325 Box::new(IntegerExpression::Const(3)),
2326 );
2327
2328 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2329
2330 let expected_reach = ReachabilityProperty {
2331 name: "test".to_string(),
2332 disj: vec![Reachability {
2333 precondition_par: vec![BooleanExpression::ComparisonExpression(
2334 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2335 ComparisonOp::Neq,
2336 Box::new(IntegerExpression::Const(3)),
2337 )],
2338 precondition_loc: vec![],
2339 precondition_var: vec![],
2340 target: DisjunctionTargetConfig::new_empty("test".into()),
2341 }],
2342 };
2343
2344 assert_eq!(got_reach, expected_reach);
2345
2346 let eltl = ELTLExpression::VariableExpr(
2348 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2349 ComparisonOp::Lt,
2350 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2351 );
2352
2353 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2354
2355 let expected_reach = ReachabilityProperty {
2356 name: "test".to_string(),
2357 disj: vec![Reachability {
2358 precondition_par: vec![],
2359 precondition_loc: vec![],
2360 precondition_var: vec![BooleanExpression::ComparisonExpression(
2361 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2362 ComparisonOp::Geq,
2363 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2364 )],
2365 target: DisjunctionTargetConfig::new_empty("test".into()),
2366 }],
2367 };
2368
2369 assert_eq!(got_reach, expected_reach);
2370
2371 let eltl = ELTLExpression::LocationExpr(
2373 Box::new(IntegerExpression::Atom(Location::new("l"))),
2374 ComparisonOp::Lt,
2375 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2376 );
2377
2378 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2379
2380 let expected_reach = ReachabilityProperty {
2381 name: "test".to_string(),
2382 disj: vec![Reachability {
2383 precondition_par: vec![],
2384 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2385 Box::new(IntegerExpression::Atom(Location::new("l"))),
2386 ComparisonOp::Geq,
2387 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2388 )],
2389 precondition_var: vec![],
2390 target: DisjunctionTargetConfig::new_empty("test".into()),
2391 }],
2392 };
2393
2394 assert_eq!(got_reach, expected_reach);
2395
2396 let eltl = ELTLExpression::And(
2398 Box::new(ELTLExpression::LocationExpr(
2399 Box::new(IntegerExpression::Atom(Location::new("l"))),
2400 ComparisonOp::Lt,
2401 Box::new(IntegerExpression::Const(1)),
2402 )),
2403 Box::new(ELTLExpression::VariableExpr(
2404 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2405 ComparisonOp::Lt,
2406 Box::new(IntegerExpression::Const(3)),
2407 )),
2408 );
2409
2410 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2411
2412 let expected_reach = ReachabilityProperty {
2413 name: "test".to_string(),
2414 disj: vec![
2415 Reachability {
2416 precondition_par: vec![],
2417 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2418 Box::new(IntegerExpression::Atom(Location::new("l"))),
2419 ComparisonOp::Geq,
2420 Box::new(IntegerExpression::Const(1)),
2421 )],
2422 precondition_var: vec![],
2423 target: DisjunctionTargetConfig::new_empty("test".into()),
2424 },
2425 Reachability {
2426 precondition_par: vec![],
2427 precondition_loc: vec![],
2428 precondition_var: vec![BooleanExpression::ComparisonExpression(
2429 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2430 ComparisonOp::Geq,
2431 Box::new(IntegerExpression::Const(3)),
2432 )],
2433 target: DisjunctionTargetConfig::new_empty("test".into()),
2434 },
2435 ],
2436 };
2437
2438 assert_eq!(got_reach, expected_reach);
2439
2440 let eltl = ELTLExpression::And(
2442 Box::new(ELTLExpression::LocationExpr(
2443 Box::new(IntegerExpression::Atom(Location::new("l"))),
2444 ComparisonOp::Lt,
2445 Box::new(IntegerExpression::Const(1)),
2446 )),
2447 Box::new(ELTLExpression::VariableExpr(
2448 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2449 ComparisonOp::Lt,
2450 Box::new(IntegerExpression::Const(3)),
2451 )),
2452 );
2453
2454 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2455
2456 let expected_reach = ReachabilityProperty {
2457 name: "test".to_string(),
2458 disj: vec![
2459 Reachability {
2460 precondition_par: vec![],
2461 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2462 Box::new(IntegerExpression::Atom(Location::new("l"))),
2463 ComparisonOp::Geq,
2464 Box::new(IntegerExpression::Const(1)),
2465 )],
2466 precondition_var: vec![],
2467 target: DisjunctionTargetConfig::new_empty("test".into()),
2468 },
2469 Reachability {
2470 precondition_par: vec![],
2471 precondition_loc: vec![],
2472 precondition_var: vec![BooleanExpression::ComparisonExpression(
2473 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2474 ComparisonOp::Geq,
2475 Box::new(IntegerExpression::Const(3)),
2476 )],
2477 target: DisjunctionTargetConfig::new_empty("test".into()),
2478 },
2479 ],
2480 };
2481
2482 assert_eq!(got_reach, expected_reach);
2483
2484 let eltl = ELTLExpression::Not(Box::new(ELTLExpression::And(
2486 Box::new(ELTLExpression::LocationExpr(
2487 Box::new(IntegerExpression::Atom(Location::new("l"))),
2488 ComparisonOp::Lt,
2489 Box::new(IntegerExpression::Const(1)),
2490 )),
2491 Box::new(ELTLExpression::VariableExpr(
2492 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2493 ComparisonOp::Lt,
2494 Box::new(IntegerExpression::Const(3)),
2495 )),
2496 )));
2497
2498 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2499
2500 let expected_reach = ReachabilityProperty {
2501 name: "test".to_string(),
2502 disj: vec![Reachability {
2503 precondition_par: vec![],
2504 precondition_loc: vec![BooleanExpression::ComparisonExpression(
2505 Box::new(IntegerExpression::Atom(Location::new("l"))),
2506 ComparisonOp::Lt,
2507 Box::new(IntegerExpression::Const(1)),
2508 )],
2509 precondition_var: vec![BooleanExpression::ComparisonExpression(
2510 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2511 ComparisonOp::Lt,
2512 Box::new(IntegerExpression::Const(3)),
2513 )],
2514 target: DisjunctionTargetConfig::new_empty("test".into()),
2515 }],
2516 };
2517
2518 assert_eq!(got_reach, expected_reach);
2519
2520 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::True));
2522
2523 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2524
2525 let expected_reach = ReachabilityProperty {
2526 name: "test".to_string(),
2527 disj: vec![Reachability {
2528 precondition_par: vec![],
2529 precondition_loc: vec![],
2530 precondition_var: vec![],
2531 target: DisjunctionTargetConfig {
2532 property_name: "test".into(),
2533 targets: vec![],
2534 },
2535 }],
2536 };
2537
2538 assert_eq!(got_reach, expected_reach);
2539
2540 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::False));
2542
2543 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2544
2545 let expected_reach = ReachabilityProperty {
2546 name: "test".to_string(),
2547 disj: vec![Reachability {
2548 precondition_par: vec![],
2549 precondition_loc: vec![],
2550 precondition_var: vec![],
2551 target: DisjunctionTargetConfig {
2552 property_name: "test".into(),
2553 targets: vec![TargetConfig::new_unconstrained()],
2554 },
2555 }],
2556 };
2557
2558 assert_eq!(got_reach, expected_reach);
2559
2560 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::VariableExpr(
2562 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2563 ComparisonOp::Lt,
2564 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2565 )));
2566
2567 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2568
2569 let expected_reach = ReachabilityProperty {
2570 name: "test".to_string(),
2571 disj: vec![Reachability {
2572 precondition_par: vec![],
2573 precondition_loc: vec![],
2574 precondition_var: vec![],
2575 target: DisjunctionTargetConfig {
2576 property_name: "test".into(),
2577 targets: vec![
2578 TargetConfig::new_with_var_constr(BooleanExpression::ComparisonExpression(
2579 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2580 ComparisonOp::Geq,
2581 Box::new(IntegerExpression::Param(Parameter::new("n"))),
2582 ))
2583 .unwrap(),
2584 ],
2585 },
2586 }],
2587 };
2588
2589 assert_eq!(got_reach, expected_reach);
2590
2591 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::LocationExpr(
2593 Box::new(IntegerExpression::Atom(Location::new("l"))),
2594 ComparisonOp::Eq,
2595 Box::new(IntegerExpression::Const(0)),
2596 )));
2597
2598 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2599
2600 let expected_reach = ReachabilityProperty {
2601 name: "test".to_string(),
2602 disj: vec![Reachability {
2603 precondition_par: vec![],
2604 precondition_loc: vec![],
2605 precondition_var: vec![],
2606 target: DisjunctionTargetConfig {
2607 property_name: "test".into(),
2608 targets: vec![TargetConfig::new_cover([Location::new("l")]).unwrap()],
2609 },
2610 }],
2611 };
2612
2613 assert_eq!(got_reach, expected_reach);
2614
2615 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2617 Box::new(ELTLExpression::LocationExpr(
2618 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2619 ComparisonOp::Eq,
2620 Box::new(IntegerExpression::Const(0)),
2621 )),
2622 Box::new(ELTLExpression::LocationExpr(
2623 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2624 ComparisonOp::Neq,
2625 Box::new(IntegerExpression::Const(0)),
2626 )),
2627 )));
2628
2629 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2630
2631 let expected_reach = ReachabilityProperty {
2632 name: "test".to_string(),
2633 disj: vec![Reachability {
2634 precondition_par: vec![],
2635 precondition_loc: vec![],
2636 precondition_var: vec![],
2637 target: DisjunctionTargetConfig {
2638 property_name: "test".into(),
2639 targets: vec![
2640 TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2641 .unwrap(),
2642 ],
2643 },
2644 }],
2645 };
2646
2647 assert_eq!(got_reach, expected_reach);
2648
2649 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::And(
2651 Box::new(ELTLExpression::LocationExpr(
2652 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2653 ComparisonOp::Eq,
2654 Box::new(IntegerExpression::Const(0)),
2655 )),
2656 Box::new(ELTLExpression::LocationExpr(
2657 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2658 ComparisonOp::Neq,
2659 Box::new(IntegerExpression::Const(0)),
2660 )),
2661 )));
2662
2663 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2664
2665 let expected_reach = ReachabilityProperty {
2666 name: "test".to_string(),
2667 disj: vec![Reachability {
2668 precondition_par: vec![],
2669 precondition_loc: vec![],
2670 precondition_var: vec![],
2671 target: DisjunctionTargetConfig {
2672 property_name: "test".into(),
2673 targets: vec![
2674 TargetConfig::new_cover([Location::new("l1")]).unwrap(),
2675 TargetConfig::new_loc_not_covered(Location::new("l2")),
2676 ],
2677 },
2678 }],
2679 };
2680
2681 assert_eq!(got_reach, expected_reach);
2682
2683 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Globally(Box::new(
2685 ELTLExpression::Or(
2686 Box::new(ELTLExpression::LocationExpr(
2687 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2688 ComparisonOp::Eq,
2689 Box::new(IntegerExpression::Const(0)),
2690 )),
2691 Box::new(ELTLExpression::LocationExpr(
2692 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2693 ComparisonOp::Neq,
2694 Box::new(IntegerExpression::Const(0)),
2695 )),
2696 ),
2697 ))));
2698
2699 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2700
2701 let expected_reach = ReachabilityProperty {
2702 name: "test".to_string(),
2703 disj: vec![Reachability {
2704 precondition_par: vec![],
2705 precondition_loc: vec![],
2706 precondition_var: vec![],
2707 target: DisjunctionTargetConfig {
2708 property_name: "test".into(),
2709 targets: vec![
2710 TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2711 .unwrap(),
2712 ],
2713 },
2714 }],
2715 };
2716
2717 assert_eq!(got_reach, expected_reach);
2718
2719 let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2721 Box::new(ELTLExpression::LocationExpr(
2722 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2723 ComparisonOp::Eq,
2724 Box::new(IntegerExpression::Const(0)),
2725 )),
2726 Box::new(ELTLExpression::Globally(Box::new(
2727 ELTLExpression::LocationExpr(
2728 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2729 ComparisonOp::Neq,
2730 Box::new(IntegerExpression::Const(0)),
2731 ),
2732 ))),
2733 )));
2734
2735 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2736
2737 let expected_reach = ReachabilityProperty {
2738 name: "test".to_string(),
2739 disj: vec![Reachability {
2740 precondition_par: vec![],
2741 precondition_loc: vec![],
2742 precondition_var: vec![],
2743 target: DisjunctionTargetConfig {
2744 property_name: "test".into(),
2745 targets: vec![
2746 TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2747 .unwrap(),
2748 ],
2749 },
2750 }],
2751 };
2752
2753 assert_eq!(got_reach, expected_reach);
2754
2755 let eltl = ELTLExpression::Implies(
2757 Box::new(ELTLExpression::VariableExpr(
2758 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2759 ComparisonOp::Eq,
2760 Box::new(IntegerExpression::Const(0)),
2761 )),
2762 Box::new(ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2763 Box::new(ELTLExpression::LocationExpr(
2764 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2765 ComparisonOp::Eq,
2766 Box::new(IntegerExpression::Const(0)),
2767 )),
2768 Box::new(ELTLExpression::Globally(Box::new(
2769 ELTLExpression::LocationExpr(
2770 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2771 ComparisonOp::Neq,
2772 Box::new(IntegerExpression::Const(0)),
2773 ),
2774 ))),
2775 )))),
2776 );
2777
2778 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2779
2780 let expected_reach = ReachabilityProperty {
2781 name: "test".to_string(),
2782 disj: vec![Reachability {
2783 precondition_par: vec![],
2784 precondition_loc: vec![],
2785 precondition_var: vec![BooleanExpression::ComparisonExpression(
2786 Box::new(IntegerExpression::Atom(Variable::new("v"))),
2787 ComparisonOp::Eq,
2788 Box::new(IntegerExpression::Const(0)),
2789 )],
2790 target: DisjunctionTargetConfig {
2791 property_name: "test".into(),
2792 targets: vec![
2793 TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2794 .unwrap(),
2795 ],
2796 },
2797 }],
2798 };
2799
2800 assert_eq!(got_reach, expected_reach);
2801
2802 let eltl = ELTLExpression::Or(
2804 Box::new(ELTLExpression::Not(Box::new(ELTLExpression::And(
2805 Box::new(ELTLExpression::LocationExpr(
2806 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2807 ComparisonOp::Eq,
2808 Box::new(IntegerExpression::Const(0)),
2809 )),
2810 Box::new(ELTLExpression::LocationExpr(
2811 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2812 ComparisonOp::Eq,
2813 Box::new(IntegerExpression::Const(0)),
2814 )),
2815 )))),
2816 Box::new(ELTLExpression::Globally(Box::new(
2817 ELTLExpression::LocationExpr(
2818 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2819 ComparisonOp::Eq,
2820 Box::new(IntegerExpression::Const(0)),
2821 ),
2822 ))),
2823 );
2824
2825 let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2826
2827 let expected_reach = ReachabilityProperty {
2828 name: "test".to_string(),
2829 disj: vec![Reachability {
2830 precondition_par: vec![],
2831 precondition_loc: vec![
2832 BooleanExpression::ComparisonExpression(
2833 Box::new(IntegerExpression::Atom(Location::new("l1"))),
2834 ComparisonOp::Eq,
2835 Box::new(IntegerExpression::Const(0)),
2836 ),
2837 BooleanExpression::ComparisonExpression(
2838 Box::new(IntegerExpression::Atom(Location::new("l2"))),
2839 ComparisonOp::Eq,
2840 Box::new(IntegerExpression::Const(0)),
2841 ),
2842 ],
2843 precondition_var: vec![],
2844 target: DisjunctionTargetConfig {
2845 property_name: "test".into(),
2846 targets: vec![TargetConfig::new_reach([Location::new("l2")], []).unwrap()],
2847 },
2848 }],
2849 };
2850
2851 assert_eq!(got_reach, expected_reach);
2852 }
2853}