taco_zcs_model_checker/zcs/
builder.rs

1//! Factory method to construct a symbolic 01-CS (ZCS) for a given IntervalThresholdAutomaton
2
3use std::fmt::Debug;
4
5use taco_bdd::{BDD, BDDManager, BddManager};
6use taco_interval_ta::IntervalActionEffect;
7use taco_interval_ta::{IntervalConstraint, IntervalTARule, IntervalThresholdAutomaton};
8use taco_threshold_automaton::ActionDefinition;
9use taco_threshold_automaton::RuleDefinition;
10use taco_threshold_automaton::expressions::Variable;
11
12use taco_threshold_automaton::ThresholdAutomaton;
13
14use crate::zcs::bdd_var_manager::BddVarManager;
15
16use super::{Bdd, SymbolicTransition, ZCS, ZCSStates};
17
18/// Builder for constructing a symbolic 01-CS
19///
20/// This builder takes a BDDManager and an IntervalThresholdAutomaton
21/// and derives the corresponding symbolic 01-counter system (ZCS).
22///
23/// The builder encodes the initial states, the symbolic transitions for each rule,
24/// and the symbolic transition relation as a single BDD.
25#[derive(Debug)]
26pub struct ZCSBuilder<'a> {
27    /// 01-CS (ZCS) that is constructed by the builder
28    cs: ZCS<'a>,
29}
30impl<'a> ZCSBuilder<'a> {
31    /// create a new symbolic 01-CS builder
32    pub fn new(mgr: &BDDManager, aut: &'a IntervalThresholdAutomaton) -> Self {
33        ZCSBuilder {
34            cs: ZCS {
35                bdd_mgr: BddVarManager::new(mgr.clone(), aut),
36                ata: aut,
37                initial_states: ZCSStates {
38                    set_of_states: mgr.get_bdd_false(),
39                },
40                transitions: Vec::new(),
41                transition_relation: mgr.get_bdd_false(),
42            },
43        }
44    }
45
46    /// 1. encode initial states
47    /// 2. encode each rule as a transition bdd
48    /// 3. encode entire transition relation
49    pub fn initialize(&mut self) {
50        self.cs.initial_states = self.build_initial_states();
51        self.cs.transitions = self.build_symbolic_transitions();
52        self.cs.transition_relation = self.build_symbolic_transition_relation();
53    }
54
55    /// Build the bdd that represents the set of initial states
56    fn build_initial_states(&self) -> ZCSStates {
57        // add initial locations
58        // collect bdds of all states that are not initial and negate them
59        let initial_states_bdd = self
60            .cs
61            .ata
62            .locations()
63            .filter(|loc| !self.cs.ata.can_be_initial_location(loc))
64            .map(|loc| {
65                !self
66                    .cs
67                    .bdd_mgr
68                    .get_location_var(loc)
69                    .unwrap_or_else(|_| panic!("No bdd variable for location {loc} created"))
70            })
71            .fold(self.cs.bdd_mgr.get_bdd_true(), |acc, bdd| acc & bdd);
72
73        // add initial intervals
74        let initial_states_bdd =
75            self.cs
76                .ata
77                .variables()
78                .fold(initial_states_bdd, |acc, var| {
79                    let initial_intervals = self.cs.ata.get_initial_interval(var);
80                    acc.and(
81                        &initial_intervals.into_iter().map(|initial_interval|{
82                            self
83                            .cs
84                            .bdd_mgr
85                            .get_shared_interval_bdd(var, initial_interval)
86                            .unwrap_or_else(|_| panic!("No shared interval bdd for shared variable {var} and interval {initial_interval} created"))
87                        }).fold(self.cs.bdd_mgr.get_bdd_false(), |acc, bdd| {
88                            acc.or(bdd)
89                        }
90                    ))
91                });
92
93        ZCSStates::new(initial_states_bdd)
94    }
95
96    /// builds all symbolic transitions for all rules of the TA
97    fn build_symbolic_transitions(&self) -> Vec<SymbolicTransition> {
98        self.cs
99            .ata
100            .rules()
101            .map(|rule| self.build_symbolic_transition(rule))
102            .collect()
103    }
104
105    /// Build a symbolic transition for a given rule of the threshold automaton
106    ///
107    /// # Steps
108    ///   1. constraints for locations
109    ///   2. add encoded rule_id to symbolic_transition
110    ///   3. check if abstract guard is satisfied
111    ///   4. check if effects of rule are applied. i.e., effects of update vector and resets
112    fn build_symbolic_transition(&self, rule: &IntervalTARule) -> SymbolicTransition {
113        //   1. constraints for locations
114        let mut symbolic_transition = self.build_location_constraints_for_rule(rule);
115
116        // 2. add encoded rule_id to symbolic_transition
117        symbolic_transition &= self
118            .cs
119            .bdd_mgr
120            .get_rule_bdd(rule)
121            .unwrap_or_else(|_| panic!("No bdd variable for rule {rule} created"))
122            .clone();
123
124        // 3. check if abstract guard is satisfied (single and multi var guard)
125        symbolic_transition &= self.construct_bdd_guard(rule.get_guard());
126
127        // 4. check if effects of rule are applied. i.e., effects of update vector and resets
128        symbolic_transition &= self.build_effect_constraints_for_rule(rule);
129
130        // return symbolic transition
131        SymbolicTransition {
132            abstract_rule: rule.clone(),
133            transition_bdd: symbolic_transition,
134        }
135    }
136
137    /// builds location constraints for a given `rule` from location `from` to location `to`
138    ///
139    /// 1. `from` and `to_prime` have to be set
140    /// 2. `from_prime` and `to` can either be either empty or occupied, the value for all other locations has to be equal
141    fn build_location_constraints_for_rule(&self, rule: &IntervalTARule) -> BDD {
142        // 1. `from` and `to_prime` have to be set
143        let loc_constraint = self
144            .cs
145            .bdd_mgr
146            .get_location_var(rule.source())
147            .unwrap_or_else(|_| panic!("No bdd variable for location {} created", rule.source()))
148            .and(
149                self.cs
150                    .bdd_mgr
151                    .get_primed_location_var(rule.target())
152                    .unwrap_or_else(|_| {
153                        panic!(
154                            "No primed bdd variable for location {} created",
155                            rule.target()
156                        )
157                    }),
158            );
159
160        // 2. 'from_prime' and 'to' can either be either empty or occupied, the value for all other locations has to be equal
161        self.cs
162            .ata
163            .locations()
164            .filter(|loc| *loc != rule.source() && *loc != rule.target())
165            .fold(loc_constraint, |acc, loc| {
166                acc & self
167                    .cs
168                    .bdd_mgr
169                    .get_location_var(loc)
170                    .unwrap_or_else(|_| panic!("No bdd variable for location {loc} created"))
171                    .equiv(
172                        self.cs
173                            .bdd_mgr
174                            .get_primed_location_var(loc)
175                            .unwrap_or_else(|_| {
176                                panic!("No primed bdd variable for location {loc} created")
177                            }),
178                    )
179            })
180    }
181
182    /// builds the guard constraints for a given rule
183    ///
184    /// 1. reset -> primed interval is the initial interval, i.e., I_0 = [0,1)
185    /// 2. increment/decrement -> primed interval is the next/previous interval or stays in the same interval
186    /// 3. no effect -> primed interval stays in the same interval
187    fn build_effect_constraints_for_rule(&self, rule: &IntervalTARule) -> BDD {
188        let effect_constraints = rule
189            .actions()
190            .fold(self.cs.bdd_mgr.get_bdd_true(), |acc, act| {
191                let var = act.variable();
192                let effect = act.effect();
193
194                // 1. reset -> primed interval is the initial interval, i.e., I_0 = [0,1)
195                if matches!(effect, IntervalActionEffect::Reset) {
196                    let primed_initial_interval_bdd =
197                        self.build_reset_constraints_for_shared_var(var);
198
199                    acc.and(primed_initial_interval_bdd)
200                } else {
201                    // 2. increment/decrement -> primed interval is the next/previous interval or stays in the same interval
202                    let update_constraints =
203                        self.build_update_constraints_for_shared_var(var, effect);
204                    acc.and(&update_constraints)
205                }
206            });
207
208        // 3. no effect -> primed interval stays in the same interval
209        self.cs
210            .ata
211            .variables()
212            .filter(|var| !rule.updates_variable(var))
213            .fold(effect_constraints, |acc, var| {
214                acc & self
215                    .cs
216                    .bdd_mgr
217                    .get_unchanged_interval_bdd(var)
218                    .unwrap_or_else(|_| {
219                        panic!(
220                            "No unchanged interval bdd for shared variable {var} could be created"
221                        )
222                    })
223            })
224    }
225
226    /// builds the reset constraint for a given shared variable
227    /// i.e., the primed interval is the initial interval I_0 = [0,1)
228    fn build_reset_constraints_for_shared_var(&self, shared: &Variable) -> &BDD {
229        let initial_interval = &self.cs.ata.get_zero_interval(shared);
230
231        self
232            .cs
233            .bdd_mgr
234            .get_primed_shared_interval_bdd(shared, initial_interval)
235            .unwrap_or_else(|_| panic!("No primed interval bdd for shared variable {shared} and interval {initial_interval} created"))
236    }
237
238    /// builds the update constraint for a given shared variable for `effect` which is either a increment or decrement
239    /// i.e., the primed interval is the next/previous interval or stays in the same interval
240    fn build_update_constraints_for_shared_var(
241        &self,
242        shared: &Variable,
243        effect: &IntervalActionEffect,
244    ) -> BDD {
245        self.cs.ata.get_intervals(shared).iter().fold(
246            self.cs.bdd_mgr.get_bdd_true(),
247            |acc, interval| {
248                let unprimed_interval_bdd = self
249                    .cs
250                    .bdd_mgr
251                    .get_shared_interval_bdd(shared, interval)
252                    .unwrap_or_else(|_| panic!("No interval bdd for shared variable {shared} and interval {interval} created"));
253
254                let unchanged_primed_interval_bdd = self
255                    .cs
256                    .bdd_mgr
257                    .get_primed_shared_interval_bdd(shared, interval)
258                    .unwrap_or_else(|_| panic!("No primed interval bdd for shared variable {shared} and interval {interval} created"));
259
260                let succ_primed_interval = match effect {
261                    IntervalActionEffect::Inc(_) => self.cs.ata.get_next_interval(shared, interval),
262                    IntervalActionEffect::Dec(_) => {
263                        self.cs.ata.get_previous_interval(shared, interval)
264                    }
265                    IntervalActionEffect::Reset => unreachable!(),
266                };
267
268                // there is no next interval -> only possible to stay in the current interval
269                let mut possible_intervals = unchanged_primed_interval_bdd.clone();
270                if let Some(next) = succ_primed_interval {
271                    //there is a next interval -> either stay in interval or move to next/previous one
272                    let next_bdd = self
273                        .cs
274                        .bdd_mgr
275                        .get_primed_shared_interval_bdd(shared, next)
276                        .unwrap_or_else(|_| panic!("No primed interval bdd for shared variable {shared} and interval {next} created"));
277                    // interval is a constant of the form [n,n+1) -> then there is no unchanged interval
278                    // i.e., applying the rule one time always leads to the next interval
279                    if interval.check_add_always_out_of_interval(1) {
280                        possible_intervals = next_bdd.clone();
281                    } else {
282                        possible_intervals |= next_bdd.clone();
283                    }
284                }
285                acc.and(&unprimed_interval_bdd.implies(&possible_intervals))
286            },
287        )
288    }
289
290    /// builds the symbolic transition relation as a single bdd
291    /// assume that all symbolic transitions are already stored in the cs
292    fn build_symbolic_transition_relation(&self) -> BDD {
293        self.cs.transitions.iter().fold(
294            self.cs.bdd_mgr.get_bdd_false(),
295            |acc, symbolic_transition| acc.or(&symbolic_transition.transition_bdd),
296        )
297    }
298
299    /// Given an abstract guard as an IntervalGuard,
300    /// Construct a BDD that replaces every abstract Interval with its respective BDD
301    fn construct_bdd_guard(&self, guard: &IntervalConstraint) -> BDD {
302        match guard {
303            IntervalConstraint::True => self.cs.bdd_mgr.get_bdd_true(),
304            IntervalConstraint::Conj(lhs, rhs) => {
305                let lhs_bdd = self.construct_bdd_guard(lhs);
306                let rhs_bdd = self.construct_bdd_guard(rhs);
307                lhs_bdd.and(&rhs_bdd)
308            }
309            IntervalConstraint::Disj(lhs, rhs) => {
310                let lhs_bdd = self.construct_bdd_guard(lhs);
311                let rhs_bdd = self.construct_bdd_guard(rhs);
312                lhs_bdd.or(&rhs_bdd)
313            }
314            IntervalConstraint::SingleVarIntervalConstr(variable, hash_set) => {
315                let mut possible_intervals = self.cs.bdd_mgr.get_bdd_false();
316                for interval in hash_set {
317                    possible_intervals = possible_intervals.or(self
318                        .cs
319                        .bdd_mgr
320                        .get_shared_interval_bdd(variable, interval)
321                        .unwrap_or_else(|_| panic!("No interval bdd for shared variable {variable} and interval {interval} created")));
322                }
323                possible_intervals
324            }
325            IntervalConstraint::MultiVarIntervalConstr(_, _) => {
326                unimplemented!("No support for multivar variables yet")
327            }
328            IntervalConstraint::False => self.cs.bdd_mgr.get_bdd_false(),
329        }
330    }
331
332    /// build the symbolic 01-CS
333    pub fn build(mut self) -> ZCS<'a> {
334        self.initialize();
335        self.cs
336    }
337}
338
339#[cfg(test)]
340mod tests {
341
342    use super::*;
343    use taco_interval_ta::builder::IntervalTABuilder;
344
345    use taco_interval_ta::interval::Interval;
346    use taco_interval_ta::interval::IntervalBoundary;
347
348    use taco_smt_encoder::SMTSolverBuilder;
349    use taco_threshold_automaton::expressions::Location;
350    use taco_threshold_automaton::expressions::Variable;
351    use taco_threshold_automaton::expressions::fraction::Fraction;
352    use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::Threshold;
353    use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::WeightedSum;
354    use taco_threshold_automaton::{
355        BooleanVarConstraint, LocationConstraint, ParameterConstraint,
356        expressions::{ComparisonOp, IntegerExpression, Parameter},
357        general_threshold_automaton::{Action, builder::RuleBuilder},
358    };
359
360    /// This function creates the canonical threshold automaton
361    /// for a simple voting algorithm,
362    /// presented in Figure 1 from the paper
363    /// "Parameterized Verification of Round-based Distributed Algorithms
364    /// via Extended Threshold Automata" (FM24)
365    ///
366    /// i.e.,
367    /// Locations: { v0, v1, wait, d0, d1 }
368    /// Variables: { x0, x1 }
369    /// Parameters: { n, t, f }
370    /// Initial location constraints: v0 + v1 = n - t & wait = 0 & d0 = 0 & d1 = 0
371    /// Resilience Condition: n > 3 * t & t >= f
372    /// Rules:
373    ///     r0: v0 -> wait, guard: true, action: x0 = x0 + 1
374    ///     r1: v1 -> wait, guard: true, action: x1 = x1 + 1
375    ///     r2: wait -> d0, guard: x0 >= n - t, action: none
376    ///     r3: wait -> d1, guard: x1 >= n - t, action: none
377    ///
378    /// Abstract Threshold Automaton:
379    /// Intervals for x0 and x1: I_0 = [0,1), I_1 = [1,n-t), I_2 = [n-t, infty)
380    /// Interval Order: I_0 < I_1 < I_2
381    /// Abstract Rules:
382    ///     r0: v0 -> wait, guard: true, action: x0 = x0 + 1
383    ///     r1: v1 -> wait, guard: true, action: x1 = x1 + 1
384    ///     r2: wait -> d0, guard: x0 = I_2, action: none
385    ///     r3: wait -> d1, guard: x1 = I_2, action: none
386    fn test_automaton() -> IntervalThresholdAutomaton {
387        let ta_builder =
388            taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
389                .with_locations(vec![
390                    Location::new("v0"),
391                    Location::new("v1"),
392                    Location::new("wait"),
393                    Location::new("d0"),
394                    Location::new("d1"),
395                ])
396                .unwrap()
397                .with_variables(vec![Variable::new("x0"), Variable::new("x1")])
398                .unwrap()
399                .with_parameters(vec![
400                    Parameter::new("n"),
401                    Parameter::new("t"),
402                    Parameter::new("f"),
403                ])
404                .unwrap()
405                .initialize()
406                .with_initial_location_constraints(vec![
407                    LocationConstraint::ComparisonExpression(
408                        Box::new(
409                            IntegerExpression::Atom(Location::new("v0"))
410                                + IntegerExpression::Atom(Location::new("v1")),
411                        ),
412                        ComparisonOp::Eq,
413                        Box::new(
414                            IntegerExpression::Param(Parameter::new("n"))
415                                - IntegerExpression::Param(Parameter::new("t")),
416                        ),
417                    ),
418                    LocationConstraint::ComparisonExpression(
419                        Box::new(IntegerExpression::Atom(Location::new("wait"))),
420                        ComparisonOp::Eq,
421                        Box::new(IntegerExpression::Const(0)),
422                    ),
423                    LocationConstraint::ComparisonExpression(
424                        Box::new(IntegerExpression::Atom(Location::new("d0"))),
425                        ComparisonOp::Eq,
426                        Box::new(IntegerExpression::Const(0)),
427                    ),
428                    LocationConstraint::ComparisonExpression(
429                        Box::new(IntegerExpression::Atom(Location::new("d1"))),
430                        ComparisonOp::Eq,
431                        Box::new(IntegerExpression::Const(0)),
432                    ),
433                ])
434                .unwrap()
435                .with_resilience_conditions(vec![
436                    ParameterConstraint::ComparisonExpression(
437                        Box::new(IntegerExpression::Atom(Parameter::new("n"))),
438                        ComparisonOp::Gt,
439                        Box::new(
440                            IntegerExpression::Const(3)
441                                * IntegerExpression::Atom(Parameter::new("t")),
442                        ),
443                    ),
444                    ParameterConstraint::ComparisonExpression(
445                        Box::new(IntegerExpression::Atom(Parameter::new("t"))),
446                        ComparisonOp::Geq,
447                        Box::new(IntegerExpression::Atom(Parameter::new("f"))),
448                    ),
449                    ParameterConstraint::ComparisonExpression(
450                        Box::new(IntegerExpression::Atom(Parameter::new("n")) - IntegerExpression::Atom(Parameter::new("t"))),
451                        ComparisonOp::Gt,
452                        Box::new(IntegerExpression::Const(1)),
453                    ),
454
455                ])
456                .unwrap()
457                .with_rules(vec![
458                    RuleBuilder::new(0, Location::new("v0"), Location::new("wait"))
459                        .with_action(
460                            Action::new(
461                                Variable::new("x0"),
462                                IntegerExpression::Atom(Variable::new("x0"))
463                                    + IntegerExpression::Const(1),
464                            )
465                            .unwrap(),
466                        )
467                        .build(),
468                    RuleBuilder::new(1, Location::new("v1"), Location::new("wait"))
469                        .with_action(
470                            Action::new(
471                                Variable::new("x1"),
472                                IntegerExpression::Atom(Variable::new("x1"))
473                                    + IntegerExpression::Const(1),
474                            )
475                            .unwrap(),
476                        )
477                        .build(),
478                    RuleBuilder::new(2, Location::new("wait"), Location::new("d0"))
479                        .with_guard(BooleanVarConstraint::ComparisonExpression(
480                            Box::new(IntegerExpression::Atom(Variable::new("x0"))),
481                            ComparisonOp::Geq,
482                            Box::new(
483                                IntegerExpression::Param(Parameter::new("n"))
484                                    - IntegerExpression::Param(Parameter::new("t")),
485                            ),
486                        ))
487                        .build(),
488                    RuleBuilder::new(3, Location::new("wait"), Location::new("d1"))
489                        .with_guard(BooleanVarConstraint::ComparisonExpression(
490                            Box::new(IntegerExpression::Atom(Variable::new("x1"))),
491                            ComparisonOp::Geq,
492                            Box::new(
493                                IntegerExpression::Param(Parameter::new("n"))
494                                    - IntegerExpression::Param(Parameter::new("t")),
495                            ),
496                        ))
497                        .build(),
498                ])
499                .unwrap();
500        let ta = ta_builder.build();
501        let lia =
502            taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
503                ta.clone(),
504            )
505            .unwrap();
506
507        let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
508            .build()
509            .unwrap();
510        let interval_threshold_automaton = interval_tas.next().unwrap();
511
512        let nxt = interval_tas.next();
513        assert!(nxt.is_none(), "Got additional ta {}", nxt.unwrap());
514
515        interval_threshold_automaton
516    }
517
518    /// This function creates a very basic threshold automaton with one decrement rule and one reset rule
519    ///
520    /// Intervals for shared variable: I_0 = [0,1), I_1 = [1,n-t), I_2 = [n-t,infty)
521    /// r0: l0 -> l1: x = x - 1
522    /// r1: l0 -> l1: x = 0
523    fn test_dec_reset_automaton() -> IntervalThresholdAutomaton {
524        let ta_builder =
525            taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
526                .with_locations(vec![Location::new("l0"), Location::new("l1")])
527                .unwrap()
528                .with_variable(Variable::new("x"))
529                .unwrap()
530                .with_parameters(vec![Parameter::new("n"), Parameter::new("t")])
531                .unwrap()
532                .initialize().with_resilience_condition(ParameterConstraint::ComparisonExpression(
533                    Box::new(IntegerExpression::Atom(Parameter::new("n")) - IntegerExpression::Atom(Parameter::new("t"))),
534                    ComparisonOp::Gt,
535                    Box::new(IntegerExpression::Const(1)),
536                )).unwrap().with_initial_variable_constraint(BooleanVarConstraint::ComparisonExpression(
537                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
538                    ComparisonOp::Geq,
539                    Box::new(
540                        IntegerExpression::Param(Parameter::new("n"))
541                            - IntegerExpression::Param(Parameter::new("t")),
542                    ),
543                )).unwrap()
544                .with_rules(vec![
545                    RuleBuilder::new(0, Location::new("l0"), Location::new("l1"))
546                        .with_action(
547                            Action::new(
548                                Variable::new("x"),
549                                IntegerExpression::Atom(Variable::new("x"))
550                                    - IntegerExpression::Const(1),
551                            )
552                            .unwrap(),
553                        )
554                        .build(),
555                    RuleBuilder::new(1, Location::new("l0"), Location::new("l1"))
556                        .with_action(Action::new_reset(Variable::new("x")))
557                        .build(),
558                ])
559                .unwrap();
560        let ta = ta_builder.build();
561        let lia =
562            taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
563                ta.clone(),
564            )
565            .unwrap();
566
567        let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
568            .build()
569            .unwrap();
570        let interval_threshold_automaton = interval_tas.next().unwrap();
571        let nxt = interval_tas.next();
572        assert!(nxt.is_none(), "Got additional ta {}", nxt.unwrap());
573
574        interval_threshold_automaton
575    }
576
577    #[test]
578    fn test_location_variables() {
579        let mgr = taco_bdd::BDDManager::default();
580        let ata = test_automaton();
581        let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
582        let var_mgr = builder.cs.bdd_mgr;
583        assert!(var_mgr.get_location_var(&Location::new("v0")).is_ok());
584        assert!(
585            var_mgr
586                .get_primed_location_var(&Location::new("v0"))
587                .is_ok()
588        );
589        assert!(var_mgr.get_location_var(&Location::new("unknown")).is_err());
590        assert!(
591            !var_mgr
592                .get_location_var(&Location::new("v0"))
593                .unwrap()
594                .eq(var_mgr
595                    .get_primed_location_var(&Location::new("v0"))
596                    .unwrap())
597        );
598    }
599
600    #[test]
601    fn test_create_shared_variables() {
602        let mgr = taco_bdd::BDDManager::default();
603        let ata = test_automaton();
604        let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
605
606        // I_0 = [0,1)
607        let interval_0 = Interval::zero();
608        // I_1 = [1,n-t)
609        let interval_1 = Interval::new(
610            IntervalBoundary::new_bound(WeightedSum::new_empty(), Fraction::new(1, 1, false)),
611            false,
612            IntervalBoundary::Bound(Threshold::new(
613                [
614                    (Parameter::new("n"), 1.into()),
615                    (Parameter::new("t"), Fraction::new(1, 1, true)),
616                ],
617                0,
618            )),
619            true,
620        );
621        // I_2 = [n-t,infty)
622        let interval_2 = Interval::new(
623            IntervalBoundary::Bound(Threshold::new(
624                [
625                    (Parameter::new("n"), 1.into()),
626                    (Parameter::new("t"), Fraction::new(1, 1, true)),
627                ],
628                0,
629            )),
630            false,
631            IntervalBoundary::new_infty(),
632            true,
633        );
634
635        let interval_0_x0_bdd = builder
636            .cs
637            .bdd_mgr
638            .get_shared_interval_bdd(&Variable::new("x0"), &interval_0);
639        let interval_1_x0_bdd = builder
640            .cs
641            .bdd_mgr
642            .get_shared_interval_bdd(&Variable::new("x0"), &interval_1);
643        let interval_2_x0_bdd = builder
644            .cs
645            .bdd_mgr
646            .get_shared_interval_bdd(&Variable::new("x0"), &interval_2);
647
648        assert!(interval_0_x0_bdd.is_ok());
649        assert!(interval_1_x0_bdd.is_ok());
650        assert!(interval_2_x0_bdd.is_ok());
651
652        // x0_0 = !(i_0 | i_2)
653        let x0_0 = (interval_0_x0_bdd
654            .clone()
655            .unwrap()
656            .or(interval_2_x0_bdd.clone().unwrap()))
657        .not();
658        // x0_1 = !(i_0 | i_1)
659        let x0_1 = (interval_0_x0_bdd
660            .clone()
661            .unwrap()
662            .or(interval_1_x0_bdd.clone().unwrap()))
663        .not();
664
665        assert!(
666            interval_0_x0_bdd
667                .unwrap()
668                .eq(&(x0_0.not().and(&x0_1.not())))
669        );
670        assert!(
671            interval_1_x0_bdd
672                .clone()
673                .unwrap()
674                .eq(&(x0_0.and(&x0_1.not())))
675        );
676        assert!(interval_2_x0_bdd.unwrap().eq(&(x0_0.not().and(&x0_1))));
677
678        let interval_1_x0_primed_bdd = builder
679            .cs
680            .bdd_mgr
681            .get_primed_shared_interval_bdd(&Variable::new("x0"), &interval_1);
682        assert!(interval_1_x0_primed_bdd.is_ok());
683        assert!(
684            !interval_1_x0_primed_bdd
685                .unwrap()
686                .eq(interval_1_x0_bdd.unwrap())
687        );
688
689        assert!(
690            builder
691                .cs
692                .bdd_mgr
693                .get_shared_interval_bdd(&Variable::new("x1"), &interval_2)
694                .is_ok()
695        );
696        assert!(
697            builder
698                .cs
699                .bdd_mgr
700                .get_primed_shared_interval_bdd(&Variable::new("x1"), &interval_0)
701                .is_ok()
702        );
703    }
704
705    #[test]
706    fn test_build_initial_states() {
707        let mgr = taco_bdd::BDDManager::default();
708        let ata = test_automaton();
709        let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
710        let initial_states_bdd = builder.build_initial_states().set_of_states;
711
712        //locations
713        let wait = builder.cs.bdd_mgr.get_location_var(&Location::new("wait"));
714        let d_0 = builder.cs.bdd_mgr.get_location_var(&Location::new("d0"));
715        let d_1 = builder.cs.bdd_mgr.get_location_var(&Location::new("d1"));
716        assert!(wait.is_ok());
717        assert!(d_0.is_ok());
718        assert!(d_1.is_ok());
719        let mut correct_initial_states_bdd = wait
720            .unwrap()
721            .not()
722            .and(&(d_0.unwrap().not().and(&d_1.unwrap().not())));
723
724        //intervals
725        // I_0 = [0,1)
726        let interval_0 = Interval::zero();
727        let interval_0_x0_bdd = builder
728            .cs
729            .bdd_mgr
730            .get_shared_interval_bdd(&Variable::new("x0"), &interval_0);
731        let interval_0_x1_bdd = builder
732            .cs
733            .bdd_mgr
734            .get_shared_interval_bdd(&Variable::new("x1"), &interval_0);
735        assert!(interval_0_x0_bdd.is_ok());
736        assert!(interval_0_x1_bdd.is_ok());
737        let interval_0_x0_bdd = interval_0_x0_bdd.unwrap();
738        let interval_0_x1_bdd = interval_0_x1_bdd.unwrap();
739
740        // I_1 = [1,n-t)
741        let interval_1_nt = Interval::new(
742            IntervalBoundary::from_const(1),
743            false,
744            IntervalBoundary::new_bound(
745                WeightedSum::new([
746                    (Parameter::new("n"), Fraction::from(1)),
747                    (Parameter::new("t"), -Fraction::from(1)),
748                ]),
749                0,
750            ),
751            true,
752        );
753        let interval_1_nt_x0 = builder
754            .cs
755            .bdd_mgr
756            .get_shared_interval_bdd(&Variable::new("x0"), &interval_1_nt);
757        let interval_1_nt_x1 = builder
758            .cs
759            .bdd_mgr
760            .get_shared_interval_bdd(&Variable::new("x1"), &interval_1_nt);
761        assert!(interval_1_nt_x0.is_ok());
762        assert!(interval_1_nt_x1.is_ok());
763        let interval_1_nt_x0 = interval_1_nt_x0.unwrap();
764        let interval_1_nt_x1 = interval_1_nt_x1.unwrap();
765
766        // I_2 = [n-t, infty)
767        let interval_nt_inf = Interval::new(
768            IntervalBoundary::new_bound(
769                WeightedSum::new([
770                    (Parameter::new("n"), Fraction::from(1)),
771                    (Parameter::new("t"), -Fraction::from(1)),
772                ]),
773                0,
774            ),
775            false,
776            IntervalBoundary::new_infty(),
777            true,
778        );
779        let interval_nt_inf_x0 = builder
780            .cs
781            .bdd_mgr
782            .get_shared_interval_bdd(&Variable::new("x0"), &interval_nt_inf);
783        let interval_nt_inf_x1 = builder
784            .cs
785            .bdd_mgr
786            .get_shared_interval_bdd(&Variable::new("x1"), &interval_nt_inf);
787        assert!(interval_nt_inf_x0.is_ok());
788        assert!(interval_nt_inf_x1.is_ok());
789        let interval_nt_inf_x0 = interval_nt_inf_x0.unwrap();
790        let interval_nt_inf_x1 = interval_nt_inf_x1.unwrap();
791
792        let init_intervals_x0 = interval_0_x0_bdd
793            .or(interval_1_nt_x0)
794            .or(interval_nt_inf_x0);
795        let init_intervals_x1 = interval_0_x1_bdd
796            .or(interval_1_nt_x1)
797            .or(interval_nt_inf_x1);
798
799        correct_initial_states_bdd =
800            correct_initial_states_bdd.and(&init_intervals_x0.and(&init_intervals_x1));
801
802        assert!(initial_states_bdd.eq(&correct_initial_states_bdd));
803    }
804
805    #[test]
806    fn test_build_symbolic_transitions() {
807        let mgr = taco_bdd::BDDManager::default();
808        let ata = test_automaton();
809        let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
810
811        let transitions = builder.build_symbolic_transitions();
812
813        assert_eq!(transitions.len(), 4);
814
815        let var_mgr = builder.cs.bdd_mgr;
816
817        // r0: v0 -> wait, guard: true, action: x0 = x0 + 1
818
819        // locations
820        // `from`(v0) and `to_prime` (wait')
821        let mut correct_r0 = var_mgr
822            .get_location_var(&Location::new("v0"))
823            .unwrap()
824            .clone();
825        correct_r0 = correct_r0.and(
826            var_mgr
827                .get_primed_location_var(&Location::new("wait"))
828                .unwrap(),
829        );
830        // v1 = v1', d0 = d0', d1 = d1'
831        correct_r0 = correct_r0.and(
832            &(var_mgr
833                .get_location_var(&Location::new("v1"))
834                .unwrap()
835                .equiv(
836                    var_mgr
837                        .get_primed_location_var(&Location::new("v1"))
838                        .unwrap(),
839                )),
840        );
841        correct_r0 = correct_r0.and(
842            &(var_mgr
843                .get_location_var(&Location::new("d0"))
844                .unwrap()
845                .equiv(
846                    var_mgr
847                        .get_primed_location_var(&Location::new("d0"))
848                        .unwrap(),
849                )),
850        );
851        correct_r0 = correct_r0.and(
852            &(var_mgr
853                .get_location_var(&Location::new("d1"))
854                .unwrap()
855                .equiv(
856                    var_mgr
857                        .get_primed_location_var(&Location::new("d1"))
858                        .unwrap(),
859                )),
860        );
861
862        // intervals for update
863        // I_0 = [0,1)
864        let interval_0 = Interval::zero();
865        // I_1 = [1,n-t)
866        let interval_1 = Interval::new(
867            IntervalBoundary::new_bound(WeightedSum::new_empty(), Fraction::new(1, 1, false)),
868            false,
869            IntervalBoundary::Bound(Threshold::new(
870                [
871                    (Parameter::new("n"), 1.into()),
872                    (Parameter::new("t"), Fraction::new(1, 1, true)),
873                ],
874                0,
875            )),
876            true,
877        );
878        // I_2 = [n-t,infty)
879        let interval_2 = Interval::new(
880            IntervalBoundary::Bound(Threshold::new(
881                [
882                    (Parameter::new("n"), 1.into()),
883                    (Parameter::new("t"), Fraction::new(1, 1, true)),
884                ],
885                0,
886            )),
887            false,
888            IntervalBoundary::new_infty(),
889            true,
890        );
891
892        let interval_0_x0_bdd = var_mgr
893            .get_shared_interval_bdd(&Variable::new("x0"), &interval_0)
894            .unwrap();
895        let interval_1_x0_bdd = var_mgr
896            .get_shared_interval_bdd(&Variable::new("x0"), &interval_1)
897            .unwrap();
898        let interval_2_x0_bdd = var_mgr
899            .get_shared_interval_bdd(&Variable::new("x0"), &interval_2)
900            .unwrap();
901        let interval_1_x0_prime_bdd = var_mgr
902            .get_primed_shared_interval_bdd(&Variable::new("x0"), &interval_1)
903            .unwrap();
904        let interval_2_x0_prime_bdd = var_mgr
905            .get_primed_shared_interval_bdd(&Variable::new("x0"), &interval_2)
906            .unwrap();
907
908        // I_0 => I_1'
909        correct_r0 = correct_r0.and(&(interval_0_x0_bdd.implies(interval_1_x0_prime_bdd)));
910        // I_1 => I_1' | I_2'
911        correct_r0 = correct_r0.and(
912            &(interval_1_x0_bdd.implies(&(interval_1_x0_prime_bdd.or(interval_2_x0_prime_bdd)))),
913        );
914        // I_2 => I_2'
915        correct_r0 = correct_r0.and(&(interval_2_x0_bdd.implies(interval_2_x0_prime_bdd)));
916
917        // for x_1 intervals unchanged
918        correct_r0 = correct_r0.and(
919            &var_mgr
920                .get_unchanged_interval_bdd(&Variable::new("x1"))
921                .unwrap(),
922        );
923
924        // rule_id
925        for t in &transitions {
926            if t.abstract_rule().id() == 0 {
927                correct_r0 = correct_r0.and(var_mgr.get_rule_bdd(t.abstract_rule()).unwrap());
928            }
929        }
930
931        // r3: wait -> d1, guard: x1 >= n - t, action: none
932
933        // locations
934        // `from`(wait) and `to_prime` (d1')
935        let mut correct_r3 = var_mgr
936            .get_location_var(&Location::new("wait"))
937            .unwrap()
938            .clone();
939        correct_r3 = correct_r3.and(
940            var_mgr
941                .get_primed_location_var(&Location::new("d1"))
942                .unwrap(),
943        );
944        // v0 = v0', v1 = v1', d0 = d0'
945        correct_r3 = correct_r3.and(
946            &(var_mgr
947                .get_location_var(&Location::new("v0"))
948                .unwrap()
949                .equiv(
950                    var_mgr
951                        .get_primed_location_var(&Location::new("v0"))
952                        .unwrap(),
953                )),
954        );
955        correct_r3 = correct_r3.and(
956            &(var_mgr
957                .get_location_var(&Location::new("v1"))
958                .unwrap()
959                .equiv(
960                    var_mgr
961                        .get_primed_location_var(&Location::new("v1"))
962                        .unwrap(),
963                )),
964        );
965        correct_r3 = correct_r3.and(
966            &(var_mgr
967                .get_location_var(&Location::new("d0"))
968                .unwrap()
969                .equiv(
970                    var_mgr
971                        .get_primed_location_var(&Location::new("d0"))
972                        .unwrap(),
973                )),
974        );
975
976        // for x_0 intervals unchanged
977        correct_r3 = correct_r3.and(
978            &var_mgr
979                .get_unchanged_interval_bdd(&Variable::new("x0"))
980                .unwrap(),
981        );
982
983        // for x_1 intervals unchanged
984        correct_r3 = correct_r3.and(
985            &var_mgr
986                .get_unchanged_interval_bdd(&Variable::new("x1"))
987                .unwrap(),
988        );
989
990        // rule_id
991        for t in &transitions {
992            if t.abstract_rule().id() == 3 {
993                correct_r3 = correct_r3.and(var_mgr.get_rule_bdd(t.abstract_rule()).unwrap());
994            }
995        }
996
997        // guard: x1 has to be in I_2
998        correct_r3 = correct_r3.and(
999            var_mgr
1000                .get_shared_interval_bdd(&Variable::new("x1"), &interval_2)
1001                .unwrap(),
1002        );
1003
1004        for t in transitions {
1005            assert!(t.abstract_rule().id() < 4);
1006            if t.abstract_rule().id() == 0 {
1007                assert!(correct_r0.eq(&t.transition_bdd));
1008            }
1009            if t.abstract_rule().id() == 3 {
1010                assert!(correct_r3.eq(&t.transition_bdd));
1011            }
1012        }
1013    }
1014
1015    #[test]
1016    fn test_build_symbolic_transition_relation() {
1017        let mgr = taco_bdd::BDDManager::default();
1018        let ata = test_automaton();
1019        let mut builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
1020        builder.initialize();
1021
1022        assert!(
1023            builder
1024                .cs
1025                .transition_relation
1026                .and(&builder.cs.transitions[1].transition_bdd)
1027                .satisfiable()
1028        );
1029        assert!(
1030            builder
1031                .cs
1032                .transition_relation
1033                .and(
1034                    builder
1035                        .cs
1036                        .bdd_mgr
1037                        .get_rule_bdd(builder.cs.transitions[1].abstract_rule())
1038                        .unwrap()
1039                )
1040                .satisfiable()
1041        );
1042        assert!(
1043            builder
1044                .cs
1045                .transition_relation
1046                .and(&builder.cs.transitions[2].transition_bdd)
1047                .satisfiable()
1048        );
1049        assert!(
1050            builder
1051                .cs
1052                .transition_relation
1053                .and(
1054                    builder
1055                        .cs
1056                        .bdd_mgr
1057                        .get_rule_bdd(builder.cs.transitions[2].abstract_rule())
1058                        .unwrap()
1059                )
1060                .satisfiable()
1061        );
1062    }
1063
1064    #[test]
1065    fn test_decrement_transition() {
1066        let mgr = taco_bdd::BDDManager::default();
1067        let ata = test_dec_reset_automaton();
1068
1069        let mut builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
1070        builder.initialize();
1071
1072        assert_eq!(builder.cs.transitions.len(), 2);
1073
1074        let var_mgr = builder.cs.bdd_mgr;
1075
1076        // r0: l0 -> l1, guard: true, action: x = x - 1
1077
1078        // locations
1079        // `from`(l0) and `to_prime` (l1')
1080        let mut correct_r0 = var_mgr
1081            .get_location_var(&Location::new("l0"))
1082            .unwrap()
1083            .clone();
1084        correct_r0 = correct_r0.and(
1085            var_mgr
1086                .get_primed_location_var(&Location::new("l1"))
1087                .unwrap(),
1088        );
1089
1090        // intervals for update
1091        // I_0 = [0,1)
1092        let interval_0 = Interval::zero();
1093        // I_1 = [1,n-t)
1094        let interval_1 = Interval::new(
1095            IntervalBoundary::new_bound(WeightedSum::new_empty(), Fraction::new(1, 1, false)),
1096            false,
1097            IntervalBoundary::Bound(Threshold::new(
1098                [
1099                    (Parameter::new("n"), 1.into()),
1100                    (Parameter::new("t"), Fraction::new(1, 1, true)),
1101                ],
1102                0,
1103            )),
1104            true,
1105        );
1106        // I_2 = [n-t,infty)
1107        let interval_2 = Interval::new(
1108            IntervalBoundary::Bound(Threshold::new(
1109                [
1110                    (Parameter::new("n"), 1.into()),
1111                    (Parameter::new("t"), Fraction::new(1, 1, true)),
1112                ],
1113                0,
1114            )),
1115            false,
1116            IntervalBoundary::new_infty(),
1117            true,
1118        );
1119
1120        let interval_0_bdd = var_mgr
1121            .get_shared_interval_bdd(&Variable::new("x"), &interval_0)
1122            .unwrap();
1123        let interval_1_bdd = var_mgr
1124            .get_shared_interval_bdd(&Variable::new("x"), &interval_1)
1125            .unwrap();
1126        let interval_2_bdd = var_mgr
1127            .get_shared_interval_bdd(&Variable::new("x"), &interval_2)
1128            .unwrap();
1129        let interval_0_prime_bdd = var_mgr
1130            .get_primed_shared_interval_bdd(&Variable::new("x"), &interval_0)
1131            .unwrap();
1132        let interval_1_prime_bdd = var_mgr
1133            .get_primed_shared_interval_bdd(&Variable::new("x"), &interval_1)
1134            .unwrap();
1135        let interval_2_prime_bdd = var_mgr
1136            .get_primed_shared_interval_bdd(&Variable::new("x"), &interval_2)
1137            .unwrap();
1138
1139        // I_0 => I_0'
1140        correct_r0 = correct_r0.and(&(interval_0_bdd.implies(interval_0_prime_bdd)));
1141        // I_1 => I_1' | I_0'
1142        correct_r0 = correct_r0
1143            .and(&(interval_1_bdd.implies(&(interval_1_prime_bdd.or(interval_0_prime_bdd)))));
1144        // I_2 => I_2' | I_1'
1145        correct_r0 = correct_r0
1146            .and(&(interval_2_bdd.implies(&(interval_2_prime_bdd.or(interval_1_prime_bdd)))));
1147
1148        // rule_id
1149        for t in &builder.cs.transitions {
1150            if t.abstract_rule.id() == 0 {
1151                correct_r0 = correct_r0.and(var_mgr.get_rule_bdd(t.abstract_rule()).unwrap());
1152                assert!(correct_r0.eq(&t.transition_bdd));
1153            }
1154        }
1155    }
1156
1157    #[test]
1158    fn test_reset_transition() {
1159        let mgr = taco_bdd::BDDManager::default();
1160        let ata = test_dec_reset_automaton();
1161
1162        let mut builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
1163        builder.initialize();
1164
1165        assert_eq!(builder.cs.transitions.len(), 2);
1166
1167        let var_mgr = builder.cs.bdd_mgr;
1168
1169        // r1: l0 -> l1, guard: true, action: x = 0
1170
1171        // locations
1172        // `from`(l0) and `to_prime` (l1')
1173        let mut correct_r1 = var_mgr
1174            .get_location_var(&Location::new("l0"))
1175            .unwrap()
1176            .clone();
1177        correct_r1 = correct_r1.and(
1178            var_mgr
1179                .get_primed_location_var(&Location::new("l1"))
1180                .unwrap(),
1181        );
1182
1183        // intervals for update
1184        // I_0 = [0,1)
1185        let interval_0 = Interval::zero();
1186
1187        let interval_0_prime_bdd = var_mgr
1188            .get_primed_shared_interval_bdd(&Variable::new("x"), &interval_0)
1189            .unwrap();
1190
1191        // I_0'
1192        correct_r1 = correct_r1.and(interval_0_prime_bdd);
1193
1194        // rule_id
1195        for t in &builder.cs.transitions {
1196            if t.abstract_rule().id() == 1 {
1197                correct_r1 = correct_r1.and(var_mgr.get_rule_bdd(t.abstract_rule()).unwrap());
1198                assert!(correct_r1.eq(&t.transition_bdd));
1199            }
1200        }
1201    }
1202}