taco_threshold_automaton/general_threshold_automaton/
builder.rs

1//! Factory methods for building a valid [`GeneralThresholdAutomaton`]
2//!
3//! This module contains the builder [`GeneralThresholdAutomatonBuilder`] for a
4//! [`GeneralThresholdAutomaton`]. The builder will ensure that the threshold
5//! automaton is valid, e.g., ensure that all variables in expressions are
6//! declared.
7
8use std::{
9    cmp::Ordering,
10    collections::{HashMap, HashSet},
11    fmt::{Debug, Display},
12};
13
14use super::{
15    Action, BooleanVarConstraint, GeneralThresholdAutomaton, LocationConstraint,
16    ParameterConstraint, Rule, UpdateExpression,
17};
18use crate::{
19    ThresholdAutomaton,
20    expressions::{
21        Atomic, BooleanExpression, IntegerExpression, IsDeclared, Location, Parameter, Variable,
22        properties::EvaluationError,
23    },
24};
25
26/// Builder for constructing a [`GeneralThresholdAutomaton`]
27///
28/// A builder can be used to construct a [`GeneralThresholdAutomaton`], ensuring
29/// that the threshold automaton is valid. The builder has two stages: In the
30/// first stage, parameters, variables, and locations can be added to the
31/// threshold automaton. This stage is completed by calling
32/// [`GeneralThresholdAutomatonBuilder::initialize`],
33/// transforming the builder into an
34/// [`InitializedGeneralThresholdAutomatonBuilder`].
35///
36/// In the second stage, rules, resilience conditions, and initial location
37/// constraints are added. The threshold automaton is then constructed by calling
38/// [`InitializedGeneralThresholdAutomatonBuilder::build`].
39///
40/// # Example
41///
42/// ```
43/// use taco_threshold_automaton::expressions::*;
44/// use taco_threshold_automaton::general_threshold_automaton::builder::*;
45///
46/// // Building a threshold automaton
47/// let _ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
48///            .with_parameter(Parameter::new("n")).unwrap()
49///            .with_variable(Variable::new("var1")).unwrap()
50///            .with_locations(vec![
51///                Location::new("loc1"),
52///                Location::new("loc2"),
53///            ]).unwrap()
54///            .initialize()
55///            .with_rules(vec![
56///                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
57///            ]).unwrap()
58///            .build();
59///
60///
61/// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
62///            .with_parameter(Parameter::new("n")).unwrap()
63///            .with_variable(Variable::new("var1")).unwrap()
64///            .with_locations(vec![
65///                Location::new("loc1"),
66///                Location::new("loc2"),
67///                Location::new("loc3"),
68///            ]).unwrap()
69///            .initialize();
70///
71/// assert!(builder.has_parameter(&Parameter::new("n")));
72/// assert!(builder.has_variable(&Variable::new("var1")));
73/// assert!(builder.has_location(&Location::new("loc1")));
74/// assert!(builder.has_location(&Location::new("loc2")));
75/// ```
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct GeneralThresholdAutomatonBuilder {
78    ta: GeneralThresholdAutomaton,
79}
80
81impl GeneralThresholdAutomatonBuilder {
82    /// Create a new threshold automaton builder
83    pub fn new(name: impl ToString) -> Self {
84        GeneralThresholdAutomatonBuilder {
85            ta: GeneralThresholdAutomaton {
86                name: name.to_string(),
87                parameters: HashSet::new(),
88                variables: HashSet::new(),
89                locations: HashSet::new(),
90                outgoing_rules: HashMap::new(),
91                initial_location_constraint: vec![],
92                initial_variable_constraint: vec![],
93                resilience_condition: vec![],
94            },
95        }
96    }
97
98    /// Checks whether a name is already present in the threshold automaton
99    fn check_for_name_clash(&self, name: &str) -> bool {
100        self.ta.parameters.contains(&Parameter::new(name))
101            || self.ta.variables.contains(&Variable::new(name))
102            || self.ta.locations.contains(&Location::new(name))
103    }
104
105    /// Adds a parameter to the threshold automaton
106    ///
107    /// Adds a parameter to the threshold automaton. If the parameter is already
108    /// present or its name is already taken an error is returned.
109    ///
110    ///  # Example
111    ///
112    /// ```
113    /// use taco_threshold_automaton::expressions::*;
114    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
115    ///
116    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
117    ///            .with_parameter(Parameter::new("n")).unwrap()
118    ///            .initialize();
119    /// assert!(builder.has_parameter(&Parameter::new("n")));
120    /// ```
121    pub fn with_parameter(mut self, param: Parameter) -> Result<Self, BuilderError> {
122        if self.ta.parameters.contains(&param) {
123            return Err(BuilderError::DuplicateParameter(param));
124        }
125
126        if self.check_for_name_clash(param.name()) {
127            return Err(BuilderError::NameClash(param.name().to_string()));
128        }
129
130        self.ta.parameters.insert(param);
131        Ok(self)
132    }
133
134    /// Adds multiple parameters to the threshold automaton
135    ///
136    /// Adds multiple parameters to the threshold automaton. If any of the
137    /// parameters are duplicates or a name is already taken an error is
138    /// returned.
139    ///
140    ///  # Example
141    ///
142    /// ```
143    /// use taco_threshold_automaton::expressions::*;
144    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
145    ///
146    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
147    ///            .with_parameters(vec![
148    ///                 Parameter::new("n"),
149    ///                 Parameter::new("t")]
150    ///            ).unwrap()
151    ///            .initialize();
152    ///
153    /// assert!(builder.has_parameter(&Parameter::new("n")));
154    /// assert!(builder.has_parameter(&Parameter::new("t")));
155    /// ```
156    pub fn with_parameters(
157        self,
158        params: impl IntoIterator<Item = Parameter>,
159    ) -> Result<Self, BuilderError> {
160        let mut res = self;
161        for param in params {
162            res = res.with_parameter(param)?;
163        }
164
165        Ok(res)
166    }
167
168    /// Adds a variable to the threshold automaton
169    ///
170    /// Adds a variable to the threshold automaton. If the variable is already
171    /// present or its name is already taken an error is returned.
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// use taco_threshold_automaton::expressions::*;
177    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
178    ///
179    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
180    ///           .with_variable(Variable::new("var1")).unwrap()
181    ///           .initialize();
182    /// assert!(builder.has_variable(&Variable::new("var1")));
183    /// ```
184    pub fn with_variable(mut self, var: Variable) -> Result<Self, BuilderError> {
185        if self.ta.variables.contains(&var) {
186            return Err(BuilderError::DuplicateVariable(var));
187        }
188
189        if self.check_for_name_clash(var.name()) {
190            return Err(BuilderError::NameClash(var.name().to_string()));
191        }
192
193        self.ta.variables.insert(var);
194        Ok(self)
195    }
196
197    /// Adds multiple variables to the threshold automaton
198    ///
199    /// Adds multiple variables to the threshold automaton. If any of the
200    /// variables are duplicates or a name is already taken an error is
201    /// returned.
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use taco_threshold_automaton::expressions::*;
207    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
208    ///
209    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
210    ///         .with_variables(vec![
211    ///             Variable::new("var1"),
212    ///             Variable::new("var2"),
213    ///         ]).unwrap()
214    ///         .initialize();
215    ///
216    /// assert!(builder.has_variable(&Variable::new("var1")));
217    /// assert!(builder.has_variable(&Variable::new("var2")));
218    /// ```
219    pub fn with_variables(
220        self,
221        vars: impl IntoIterator<Item = Variable>,
222    ) -> Result<Self, BuilderError> {
223        let mut res = self;
224        for var in vars {
225            res = res.with_variable(var)?;
226        }
227
228        Ok(res)
229    }
230
231    /// Adds a location to the threshold automaton
232    ///
233    /// Adds a location to the threshold automaton. If the location is already
234    /// present or its name is already taken an error is returned.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// use taco_threshold_automaton::expressions::Location;
240    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
241    ///
242    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
243    ///        .with_location(Location::new("loc1")).unwrap()
244    ///        .initialize();
245    ///
246    /// assert!(builder.has_location(&Location::new("loc1")));
247    /// ```
248    pub fn with_location(mut self, loc: Location) -> Result<Self, BuilderError> {
249        if self.ta.locations.contains(&loc) {
250            return Err(BuilderError::DuplicateLocation(loc));
251        }
252
253        if self.check_for_name_clash(loc.name()) {
254            return Err(BuilderError::NameClash(loc.name().to_string()));
255        }
256
257        self.ta.locations.insert(loc);
258        Ok(self)
259    }
260
261    /// Adds multiple locations to the threshold automaton
262    ///
263    /// Adds multiple locations to the threshold automaton. If any of the
264    /// locations are duplicates or a name is already taken an error is
265    /// returned.
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// use taco_threshold_automaton::expressions::*;
271    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
272    ///
273    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
274    ///         .with_locations(vec![
275    ///             Location::new("loc1"),
276    ///             Location::new("loc2"),
277    ///         ]).unwrap()
278    ///        .initialize();
279    ///
280    /// assert!(builder.has_location(&Location::new("loc1")));
281    /// ```
282    pub fn with_locations(
283        self,
284        locs: impl IntoIterator<Item = Location>,
285    ) -> Result<Self, BuilderError> {
286        let mut res = self;
287        for loc in locs {
288            res = res.with_location(loc)?;
289        }
290
291        Ok(res)
292    }
293
294    /// Completes the first stage of the builder, transforming it into an `InitializedGeneralThresholdAutomatonBuilder`.
295    ///
296    /// This method should be called after all parameters, variables, and locations have been added.
297    pub fn initialize(self) -> InitializedGeneralThresholdAutomatonBuilder {
298        InitializedGeneralThresholdAutomatonBuilder { ta: self.ta }
299    }
300}
301
302/// A builder for a threshold automaton where parameters, variables, and
303/// locations have already been added and are now fixed.
304///
305/// In this stage, rules, resilience conditions, and initial constraints
306/// can be added to the threshold automaton.
307///
308/// # Example
309///
310/// ```
311/// use taco_threshold_automaton::expressions::*;
312/// use taco_threshold_automaton::general_threshold_automaton::builder::*;
313/// use taco_threshold_automaton::LocationConstraint;
314///
315/// // Building a threshold automaton
316/// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
317///     .with_parameter(Parameter::new("n")).unwrap()
318///     .with_variable(Variable::new("var1")).unwrap()
319///     .with_locations(vec![
320///          Location::new("loc1"),
321///          Location::new("loc2"),
322///      ]).unwrap()
323///      .initialize()
324///      .with_rules(vec![
325///         RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
326///       ]).unwrap()
327///      .with_initial_location_constraint(
328///             LocationConstraint::ComparisonExpression(
329///                Box::new(IntegerExpression::Atom(Location::new("loc1"))),
330///                ComparisonOp::Eq,
331///                Box::new(IntegerExpression::Param(Parameter::new("n"))),
332///       )).unwrap()   
333///     .build();
334/// ```
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub struct InitializedGeneralThresholdAutomatonBuilder {
337    ta: GeneralThresholdAutomaton,
338}
339
340impl InitializedGeneralThresholdAutomatonBuilder {
341    /// Checks whether the rule or rule id is already present in the threshold automaton
342    ///
343    /// Returns an error if the rule or rule id is already present, otherwise returns None
344    fn contains_rule_or_rule_id(&self, rule: &Rule) -> Option<BuilderError> {
345        for rule_vec in self.ta.outgoing_rules.values() {
346            for ta_rule in rule_vec {
347                if ta_rule.id == rule.id {
348                    return Some(BuilderError::DuplicateRuleId(
349                        Box::new(rule.clone()),
350                        Box::new(ta_rule.clone()),
351                    ));
352                }
353            }
354        }
355
356        None
357    }
358
359    /// Check whether a integer expression `int_expr` only uses components appearing in `known_atoms`
360    ///
361    /// Returns an error if the constraint does contain unknown components, otherwise returns None
362    fn validate_integer_expr<T: Atomic>(
363        &self,
364        int_expr: &IntegerExpression<T>,
365        known_atoms: &HashSet<T>,
366    ) -> Option<BuilderError> {
367        match int_expr {
368            IntegerExpression::Atom(a) => {
369                if known_atoms.contains(a) {
370                    return None;
371                }
372
373                Some(BuilderError::UnknownComponent(a.to_string()))
374            }
375            IntegerExpression::Const(_) => None,
376            IntegerExpression::Param(p) => {
377                if !self.ta.parameters.contains(p) {
378                    return Some(BuilderError::UnknownComponent(format!("Parameter {p}")));
379                }
380
381                None
382            }
383            IntegerExpression::BinaryExpr(lhs, _, rhs) => self
384                .validate_integer_expr(lhs, known_atoms)
385                .or_else(|| self.validate_integer_expr(rhs, known_atoms)),
386            IntegerExpression::Neg(ex) => self.validate_integer_expr(ex, known_atoms),
387        }
388    }
389
390    /// Check whether a constraint `constraint` only uses components appearing in `known_atoms`
391    ///
392    /// Returns an error if the constraint does contain unknown components, otherwise returns None
393    fn validate_constraint<T: Atomic>(
394        &self,
395        constraint: &BooleanExpression<T>,
396        known_atoms: &HashSet<T>,
397    ) -> Option<BuilderError> {
398        match constraint {
399            BooleanExpression::ComparisonExpression(lhs, _, rhs) => self
400                .validate_integer_expr(lhs, known_atoms)
401                .or_else(|| self.validate_integer_expr(rhs, known_atoms)),
402            BooleanExpression::BinaryExpression(lhs, _, rhs) => self
403                .validate_constraint(lhs, known_atoms)
404                .or_else(|| self.validate_constraint(rhs, known_atoms)),
405            BooleanExpression::True => None,
406            BooleanExpression::False => None,
407            BooleanExpression::Not(b) => self.validate_constraint(b, known_atoms),
408        }
409    }
410
411    /// Check whether an action is valid
412    ///
413    /// Returns an error if the action is invalid, otherwise returns None
414    fn validate_action(&self, action: &Action) -> Option<BuilderError> {
415        if !self.ta.variables.contains(&action.variable_to_update) {
416            return Some(BuilderError::MalformedAction(
417                Box::new(action.clone()),
418                format!(
419                    "Unknown Variable to update: {}, Action: {}",
420                    action.variable_to_update, action
421                ),
422            ));
423        }
424
425        None
426    }
427
428    /// Check whether components of a rule are valid
429    ///
430    /// Returns an error if the rule is malformed, otherwise returns None
431    fn validate_rule(&self, rule: &Rule) -> Option<BuilderError> {
432        if let Some(err) = self.contains_rule_or_rule_id(rule) {
433            return Some(err);
434        }
435
436        if !self.ta.locations.contains(&rule.source) {
437            return Some(BuilderError::MalformedRule(
438                Box::new(rule.clone()),
439                format!("Source location {} not found", rule.source),
440            ));
441        }
442
443        if !self.ta.locations.contains(&rule.target) {
444            return Some(BuilderError::MalformedRule(
445                Box::new(rule.clone()),
446                format!("Target location {} not found", rule.target),
447            ));
448        }
449
450        if let Some(err) = self.validate_constraint(&rule.guard, &self.ta.variables) {
451            return Some(BuilderError::MalformedRule(
452                Box::new(rule.clone()),
453                format!("Guard constraint {} is malformed: {}", &rule.guard, err),
454            ));
455        }
456
457        let mut updated_variables = HashSet::new();
458        for action in &rule.actions {
459            // Check that variable is actually defined
460            if let Some(err) = self.validate_action(action) {
461                return Some(BuilderError::MalformedRule(
462                    Box::new(rule.clone()),
463                    format!("Action {action} is malformed: {err}"),
464                ));
465            }
466
467            // Check that a single variable is not updated multiple times
468            if !updated_variables.insert(action.variable_to_update.clone()) {
469                return Some(BuilderError::MalformedRule(
470                    Box::new(rule.clone()),
471                    format!(
472                        "Variable {} is updated multiple times in the same rule",
473                        action.variable_to_update
474                    ),
475                ));
476            }
477        }
478
479        None
480    }
481
482    /// Add a rule to the threshold automaton
483    ///
484    /// Adds a rule to the threshold automaton. It returns an error if a rule is
485    /// added twice, the rule id is already taken or one of the rules
486    /// expressions is invalid.
487    ///
488    /// # Example
489    ///
490    /// ```
491    /// use taco_threshold_automaton::expressions::*;
492    /// use taco_threshold_automaton::general_threshold_automaton::*;
493    /// use taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder;
494    /// use taco_threshold_automaton::general_threshold_automaton::builder::RuleBuilder;
495    ///
496    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
497    ///     .with_locations(vec![
498    ///          Location::new("loc1"),
499    ///          Location::new("loc2"),
500    ///      ]).unwrap()
501    ///      .initialize()
502    ///      .with_rule(
503    ///         RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
504    ///      ).unwrap()
505    ///      .build();
506    /// ```
507    pub fn with_rule(mut self, rule: Rule) -> Result<Self, BuilderError> {
508        if let Some(err) = self.validate_rule(&rule) {
509            return Err(err);
510        }
511
512        self.ta
513            .outgoing_rules
514            .entry(rule.source.clone())
515            .or_default()
516            .push(rule.clone());
517
518        Ok(self)
519    }
520
521    /// Add multiple rules to the threshold automaton
522    ///
523    /// Adds multiple rules to the threshold automaton. It returns an error if a
524    /// rule is added twice, the rule id is already taken or one of the rules
525    /// expressions is invalid.
526    ///
527    /// # Example
528    ///
529    /// ```
530    /// use taco_threshold_automaton::expressions::*;
531    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
532    /// use taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder;
533    ///
534    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
535    ///     .with_locations(vec![
536    ///          Location::new("loc1"),
537    ///          Location::new("loc2"),
538    ///      ]).unwrap()
539    ///      .initialize()
540    ///      .with_rules(vec![
541    ///         RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
542    ///         RuleBuilder::new(1, Location::new("loc2"), Location::new("loc1")).build(),
543    ///       ]).unwrap()
544    ///     .build();
545    /// ```
546    pub fn with_rules(self, rules: impl IntoIterator<Item = Rule>) -> Result<Self, BuilderError> {
547        let mut res = self;
548        for rule in rules {
549            res = res.with_rule(rule)?;
550        }
551        Ok(res)
552    }
553
554    /// The representation of a resilience condition is currently not unique
555    /// because in this case parameters are also atoms. This function converts
556    /// all atoms into parameters.
557    fn canonicalize_parameter_integer_expr(
558        rc: IntegerExpression<Parameter>,
559    ) -> IntegerExpression<Parameter> {
560        match rc {
561            IntegerExpression::Atom(p) => IntegerExpression::Param(p),
562            IntegerExpression::BinaryExpr(lhs, op, rhs) => IntegerExpression::BinaryExpr(
563                Box::new(Self::canonicalize_parameter_integer_expr(*lhs)),
564                op,
565                Box::new(Self::canonicalize_parameter_integer_expr(*rhs)),
566            ),
567            IntegerExpression::Neg(ex) => {
568                IntegerExpression::Neg(Box::new(Self::canonicalize_parameter_integer_expr(*ex)))
569            }
570            IntegerExpression::Const(c) => IntegerExpression::Const(c),
571            IntegerExpression::Param(p) => IntegerExpression::Param(p),
572        }
573    }
574
575    /// The representation of a resilience condition is currently not unique
576    /// because in this case parameters are also atoms. This function converts
577    /// all atoms into parameters.
578    fn canonicalize_resilience_condition(rc: ParameterConstraint) -> ParameterConstraint {
579        match rc {
580            BooleanExpression::ComparisonExpression(lhs, op, rhs) => {
581                BooleanExpression::ComparisonExpression(
582                    Box::new(Self::canonicalize_parameter_integer_expr(*lhs)),
583                    op,
584                    Box::new(Self::canonicalize_parameter_integer_expr(*rhs)),
585                )
586            }
587            BooleanExpression::BinaryExpression(lhs, op, rhs) => {
588                BooleanExpression::BinaryExpression(
589                    Box::new(Self::canonicalize_resilience_condition(*lhs)),
590                    op,
591                    Box::new(Self::canonicalize_resilience_condition(*rhs)),
592                )
593            }
594            BooleanExpression::Not(ex) => {
595                BooleanExpression::Not(Box::new(Self::canonicalize_resilience_condition(*ex)))
596            }
597            BooleanExpression::True => BooleanExpression::True,
598            BooleanExpression::False => BooleanExpression::False,
599        }
600    }
601
602    /// Add a resilience condition to the threshold automaton
603    ///
604    /// Adds a resilience condition to the threshold automaton. It returns an
605    /// error if the resilience condition already exists or contains unknown
606    /// parameters.
607    ///
608    /// # Example
609    ///
610    /// ```
611    /// use taco_threshold_automaton::expressions::*;
612    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
613    /// use taco_threshold_automaton::ParameterConstraint;
614    ///
615    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
616    ///     .with_parameter(Parameter::new("n")).unwrap()
617    ///     .initialize()
618    ///     .with_resilience_condition(
619    ///         ParameterConstraint::ComparisonExpression(
620    ///             Box::new(IntegerExpression::Atom(Parameter::new("n"))),
621    ///             ComparisonOp::Eq,
622    ///             Box::new(IntegerExpression::Const(0)),
623    ///     )).unwrap()
624    ///     .build();
625    /// ```
626    pub fn with_resilience_condition(
627        mut self,
628        rc: ParameterConstraint,
629    ) -> Result<Self, BuilderError> {
630        if let Some(err) = self.validate_constraint(&rc, &self.ta.parameters) {
631            return Err(BuilderError::MalformedResilienceCondition(
632                Box::new(rc.clone()),
633                err.to_string(),
634            ));
635        }
636
637        self.ta
638            .resilience_condition
639            .push(Self::canonicalize_resilience_condition(rc));
640        Ok(self)
641    }
642
643    /// Add multiple resilience conditions to the threshold automaton
644    ///
645    /// Adds multiple resilience conditions to the threshold automaton. It
646    /// returns an error if a resilience condition already exists or contains
647    /// unknown parameters.
648    ///
649    /// # Example
650    ///
651    /// ```
652    /// use taco_threshold_automaton::expressions::*;
653    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
654    /// use taco_threshold_automaton::ParameterConstraint;
655    ///
656    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
657    ///    .with_parameters(vec![
658    ///         Parameter::new("n"),
659    ///         Parameter::new("m"),
660    ///     ]).unwrap()
661    ///     .initialize()
662    ///     .with_resilience_conditions(vec![
663    ///         ParameterConstraint::ComparisonExpression(
664    ///         Box::new(IntegerExpression::Atom(Parameter::new("n"))),
665    ///         ComparisonOp::Eq,
666    ///         Box::new(IntegerExpression::Const(0)),
667    ///     ),
668    ///     ParameterConstraint::ComparisonExpression(
669    ///         Box::new(IntegerExpression::Atom(Parameter::new("m"))),
670    ///         ComparisonOp::Eq,
671    ///         Box::new(IntegerExpression::Const(0)),
672    ///     )]).unwrap()
673    ///     .build();
674    /// ```
675    pub fn with_resilience_conditions(
676        self,
677        rcs: impl IntoIterator<Item = ParameterConstraint>,
678    ) -> Result<Self, BuilderError> {
679        let mut res = self;
680        for rc in rcs {
681            res = res.with_resilience_condition(rc)?;
682        }
683        Ok(res)
684    }
685
686    /// Add an initial location constraint to the threshold automaton
687    ///
688    /// Adds an initial location constraint to the threshold automaton. It
689    /// returns an error if the constraint already exists or contains unknown
690    /// locations.
691    ///
692    /// # Example
693    ///
694    /// ```
695    /// use taco_threshold_automaton::expressions::*;
696    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
697    /// use taco_threshold_automaton::LocationConstraint;
698    ///
699    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
700    ///     .with_location(Location::new("loc1")).unwrap()
701    ///     .initialize()
702    ///     .with_initial_location_constraint(
703    ///         LocationConstraint::ComparisonExpression(
704    ///             Box::new(IntegerExpression::Atom(Location::new("loc1"))),
705    ///             ComparisonOp::Eq,
706    ///             Box::new(IntegerExpression::Const(0)),
707    ///     )).unwrap()
708    ///     .build();
709    /// ```
710    pub fn with_initial_location_constraint(
711        mut self,
712        constraint: LocationConstraint,
713    ) -> Result<Self, BuilderError> {
714        if let Some(err) = self.validate_constraint(&constraint, &self.ta.locations) {
715            return Err(BuilderError::MalformedInitialLocationConstraint(
716                Box::new(constraint.clone()),
717                err.to_string(),
718            ));
719        }
720
721        self.ta.initial_location_constraint.push(constraint);
722        Ok(self)
723    }
724
725    /// Add multiple initial location constraints to the threshold automaton
726    ///
727    /// Adds multiple initial location constraints to the threshold automaton.
728    /// It returns an error if a constraint already exists or contains unknown
729    /// locations.
730    ///
731    /// # Example
732    ///
733    /// ```
734    /// use taco_threshold_automaton::ParameterConstraint;
735    /// use taco_threshold_automaton::LocationConstraint;
736    /// use taco_threshold_automaton::expressions::*;
737    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
738    ///
739    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
740    ///     .with_locations(vec![
741    ///         Location::new("loc1"),
742    ///         Location::new("loc2"),
743    ///     ]).unwrap()
744    ///     .initialize()
745    ///     .with_initial_location_constraints(vec![
746    ///         LocationConstraint::ComparisonExpression(
747    ///             Box::new(IntegerExpression::Atom(Location::new("loc1"))),
748    ///             ComparisonOp::Eq,
749    ///             Box::new(IntegerExpression::Const(0)),
750    ///         ),
751    ///         LocationConstraint::ComparisonExpression(
752    ///             Box::new(IntegerExpression::Atom(Location::new("loc2"))),
753    ///             ComparisonOp::Eq,
754    ///             Box::new(IntegerExpression::Const(0)),
755    ///         ),
756    ///     ]).unwrap()
757    ///     .build();
758    /// ```
759    pub fn with_initial_location_constraints(
760        self,
761        constraints: impl IntoIterator<Item = LocationConstraint>,
762    ) -> Result<Self, BuilderError> {
763        let mut res = self;
764        for constraint in constraints {
765            res = res.with_initial_location_constraint(constraint)?;
766        }
767        Ok(res)
768    }
769
770    /// Add initial variable constraint to the threshold automaton
771    ///
772    /// Adds an initial variable constraint to the threshold automaton. It
773    /// returns an error if the constraint already exists or contains unknown
774    /// variables.
775    ///
776    /// # Example
777    ///
778    /// ```
779    /// use taco_threshold_automaton::BooleanVarConstraint;
780    /// use taco_threshold_automaton::expressions::*;
781    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
782    ///
783    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
784    ///     .with_variable(Variable::new("var1")).unwrap()
785    ///     .initialize()
786    ///     .with_initial_variable_constraint(
787    ///         BooleanVarConstraint::ComparisonExpression(
788    ///             Box::new(IntegerExpression::Atom(Variable::new("var1"))),
789    ///             ComparisonOp::Eq,
790    ///             Box::new(IntegerExpression::Const(0)),
791    ///     )).unwrap()
792    ///     .build();
793    /// ```
794    pub fn with_initial_variable_constraint(
795        mut self,
796        constraint: BooleanVarConstraint,
797    ) -> Result<Self, BuilderError> {
798        if let Some(err) = self.validate_constraint(&constraint, &self.ta.variables) {
799            return Err(BuilderError::MalformedInitialVariableConstraint(
800                Box::new(constraint.clone()),
801                err.to_string(),
802            ));
803        }
804
805        self.ta.initial_variable_constraint.push(constraint);
806        Ok(self)
807    }
808
809    /// Add multiple initial variable constraints to the threshold automaton
810    ///
811    /// Adds multiple initial variable constraints to the threshold automaton.
812    /// It returns an error if a constraint already exists or contains unknown
813    /// variables.
814    ///
815    /// # Example
816    ///
817    /// ```
818    /// use taco_threshold_automaton::expressions::*;
819    /// use taco_threshold_automaton::BooleanVarConstraint;
820    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
821    ///
822    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
823    ///     .with_variables(vec![
824    ///         Variable::new("var1"),
825    ///         Variable::new("var2"),
826    ///     ]).unwrap()
827    ///     .initialize()
828    ///     .with_initial_variable_constraints(vec![
829    ///         BooleanVarConstraint::ComparisonExpression(
830    ///             Box::new(IntegerExpression::Atom(Variable::new("var1"))),
831    ///             ComparisonOp::Eq,
832    ///             Box::new(IntegerExpression::Const(0)),
833    ///         ),
834    ///         BooleanVarConstraint::ComparisonExpression(
835    ///             Box::new(IntegerExpression::Atom(Variable::new("var2"))),
836    ///             ComparisonOp::Eq,
837    ///             Box::new(IntegerExpression::Const(0)),
838    ///      )]).unwrap()
839    ///     .build();
840    /// ```
841    pub fn with_initial_variable_constraints(
842        self,
843        constraints: impl IntoIterator<Item = BooleanVarConstraint>,
844    ) -> Result<Self, BuilderError> {
845        let mut res = self;
846        for constraint in constraints {
847            res = res.with_initial_variable_constraint(constraint)?;
848        }
849        Ok(res)
850    }
851
852    /// Check whether the threshold automaton has a specific parameter
853    ///
854    /// # Example
855    ///
856    /// ```
857    /// use taco_threshold_automaton::expressions::*;
858    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
859    ///
860    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
861    ///     .with_parameter(Parameter::new("n")).unwrap()
862    ///     .initialize();
863    ///
864    /// assert!(builder.has_parameter(&Parameter::new("n")));
865    /// ```
866    #[inline(always)]
867    pub fn has_parameter(&self, param: &Parameter) -> bool {
868        self.ta.parameters.contains(param)
869    }
870
871    /// Check whether the threshold automaton has a specific variable
872    ///
873    /// # Example
874    ///
875    /// ```
876    /// use taco_threshold_automaton::expressions::*;
877    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
878    ///
879    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
880    ///     .with_variable(Variable::new("var1")).unwrap()
881    ///     .initialize();
882    ///
883    /// assert!(builder.has_variable(&Variable::new("var1")));
884    /// ```
885    #[inline(always)]
886    pub fn has_variable(&self, var: &Variable) -> bool {
887        self.ta.variables.contains(var)
888    }
889
890    /// Check whether the threshold automaton has a specific location
891    ///
892    /// Returns true if the location is present.
893    ///
894    /// # Example
895    ///
896    /// ```
897    ///
898    /// use taco_threshold_automaton::expressions::*;
899    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
900    ///
901    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
902    ///     .with_location(Location::new("loc1")).unwrap()
903    ///     .initialize();
904    ///
905    /// assert!(builder.has_location(&Location::new("loc1")));
906    /// ```
907    #[inline(always)]
908    pub fn has_location(&self, loc: &Location) -> bool {
909        self.ta.locations.contains(loc)
910    }
911
912    /// Get an iterator over all locations known to the builder
913    ///
914    /// # Example
915    ///
916    /// ```
917    ///
918    /// use taco_threshold_automaton::expressions::*;
919    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
920    ///
921    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
922    ///     .with_location(Location::new("loc1")).unwrap()
923    ///     .initialize();
924    ///
925    /// let locations: Vec<&Location> = builder
926    ///     .locations()
927    ///     .into_iter()
928    ///     .collect();
929    ///
930    /// assert_eq!(locations, vec![&Location::new("loc1")]);
931    /// ```
932    pub fn locations(&self) -> impl Iterator<Item = &Location> {
933        self.ta.locations()
934    }
935
936    /// Get an iterator over all variables known to the builder
937    ///
938    /// # Example
939    ///
940    /// ```
941    ///
942    /// use taco_threshold_automaton::expressions::*;
943    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
944    ///
945    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
946    ///     .with_variable(Variable::new("var1")).unwrap()
947    ///     .initialize();
948    ///
949    /// let variables: Vec<&Variable> = builder
950    ///     .variables()
951    ///     .into_iter()
952    ///     .collect();
953    ///
954    /// assert_eq!(variables, vec![&Variable::new("var1")]);
955    /// ```
956    pub fn variables(&self) -> impl Iterator<Item = &Variable> {
957        self.ta.variables()
958    }
959
960    /// Get an iterator over all parameters known to the builder
961    ///
962    /// # Example
963    ///
964    /// ```
965    ///
966    /// use taco_threshold_automaton::expressions::*;
967    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
968    ///
969    /// let builder = GeneralThresholdAutomatonBuilder::new("test_ta1")
970    ///     .with_parameter(Parameter::new("var1")).unwrap()
971    ///     .initialize();
972    ///
973    /// let parameters: Vec<&Parameter> = builder
974    ///     .parameters()
975    ///     .into_iter()
976    ///     .collect();
977    ///
978    /// assert_eq!(parameters, vec![&Parameter::new("var1")]);
979    /// ```
980    pub fn parameters(&self) -> impl Iterator<Item = &Parameter> {
981        self.ta.parameters()
982    }
983
984    /// Complete the build step and construct the threshold automaton
985    #[inline(always)]
986    pub fn build(self) -> GeneralThresholdAutomaton {
987        self.ta
988    }
989}
990
991impl IsDeclared<Variable> for InitializedGeneralThresholdAutomatonBuilder {
992    fn is_declared(&self, var: &Variable) -> bool {
993        self.has_variable(var)
994    }
995}
996
997impl IsDeclared<Location> for InitializedGeneralThresholdAutomatonBuilder {
998    fn is_declared(&self, loc: &Location) -> bool {
999        self.has_location(loc)
1000    }
1001}
1002
1003impl IsDeclared<Parameter> for InitializedGeneralThresholdAutomatonBuilder {
1004    fn is_declared(&self, param: &Parameter) -> bool {
1005        self.has_parameter(param)
1006    }
1007}
1008
1009/// Builder to construct rules for threshold automata
1010#[derive(Debug, PartialEq, Eq)]
1011pub struct RuleBuilder {
1012    rule: Rule,
1013}
1014
1015impl RuleBuilder {
1016    /// Create a new rule builder
1017    pub fn new(id: u32, source: Location, target: Location) -> Self {
1018        RuleBuilder {
1019            rule: Rule {
1020                id,
1021                source,
1022                target,
1023                guard: BooleanExpression::True,
1024                actions: vec![],
1025            },
1026        }
1027    }
1028
1029    /// Add a guard to the rule
1030    #[inline(always)]
1031    pub fn with_guard(mut self, guard: BooleanExpression<Variable>) -> Self {
1032        self.rule.guard = guard;
1033        self
1034    }
1035
1036    /// Add an action to the rule
1037    pub fn with_action(mut self, action: Action) -> Self {
1038        self.rule.actions.push(action);
1039        self
1040    }
1041
1042    /// Add multiple actions to the rule
1043    pub fn with_actions(mut self, actions: impl IntoIterator<Item = Action>) -> Self {
1044        for action in actions {
1045            self.rule.actions.push(action);
1046        }
1047        self
1048    }
1049
1050    /// Complete the build step and construct the rule
1051    pub fn build(self) -> Rule {
1052        self.rule
1053    }
1054}
1055
1056impl Action {
1057    /// This function counts the number of occurrences of variable `var` in the
1058    /// expression and checks that no parameters appear
1059    ///
1060    /// If a parameter or a variable that is not `var` is found in the
1061    /// expression, an error is returned
1062    fn count_number_of_var_occurrences_and_validate(
1063        var: &Variable,
1064        update_expr: &IntegerExpression<Variable>,
1065    ) -> Result<u32, InvalidUpdateError> {
1066        match update_expr {
1067            IntegerExpression::Atom(v) => {
1068                if v != var {
1069                    return Err(InvalidUpdateError::AdditionalVariable(v.clone()));
1070                }
1071                Result::Ok(1)
1072            }
1073            IntegerExpression::Const(_) => Result::Ok(0),
1074            IntegerExpression::Param(p) => Err(InvalidUpdateError::ParameterFound(p.clone())),
1075            IntegerExpression::BinaryExpr(lhs, _, rhs) => {
1076                let lhs = Self::count_number_of_var_occurrences_and_validate(var, lhs)?;
1077                let rhs = Self::count_number_of_var_occurrences_and_validate(var, rhs)?;
1078
1079                Result::Ok(lhs + rhs)
1080            }
1081            IntegerExpression::Neg(expr) => {
1082                Self::count_number_of_var_occurrences_and_validate(var, expr)
1083            }
1084        }
1085    }
1086
1087    /// Try to parse a general integer expression into an update expression
1088    fn parse_to_update_expr(
1089        var: &Variable,
1090        update_expr: IntegerExpression<Variable>,
1091    ) -> Result<UpdateExpression, ActionBuilderError> {
1092        if let Some(c) = update_expr.try_to_evaluate_to_const() {
1093            if c != 0 {
1094                return Err(ActionBuilderError::UpdateExpressionMalformed(
1095                    var.clone(),
1096                    update_expr,
1097                    InvalidUpdateError::ConstantUpdateNonZero(c),
1098                ));
1099            }
1100            return Result::Ok(UpdateExpression::Reset);
1101        }
1102
1103        // check for parameters, other variables etc.
1104        let n_var = Self::count_number_of_var_occurrences_and_validate(var, &update_expr);
1105        if let Err(err) = n_var {
1106            return Err(ActionBuilderError::UpdateExpressionMalformed(
1107                var.clone(),
1108                update_expr.clone(),
1109                err,
1110            ));
1111        }
1112
1113        // confirm that var is only referenced once
1114        if n_var.unwrap() > 1 {
1115            return Err(ActionBuilderError::UpdateExpressionMalformed(
1116                var.clone(),
1117                update_expr.clone(),
1118                InvalidUpdateError::MultipleOccurrences,
1119            ));
1120        }
1121
1122        // get the overall result of the update (which must now be possible)
1123        let update = update_expr
1124            .try_to_evaluate_to_const_with_env(&HashMap::from([(var.clone(), 0)]), &HashMap::new())
1125            .expect("Failed to evaluate update expression, even though validation passed");
1126
1127        match update.cmp(&0) {
1128            Ordering::Equal => Result::Ok(UpdateExpression::Unchanged),
1129            Ordering::Greater => Result::Ok(UpdateExpression::Inc(update as u32)),
1130            Ordering::Less => Result::Ok(UpdateExpression::Dec((-update) as u32)),
1131        }
1132    }
1133
1134    /// Create a new reset action which a reset of `var_to_update`.
1135    ///
1136    /// # Example
1137    ///
1138    /// ```
1139    /// use taco_threshold_automaton::general_threshold_automaton::Action;
1140    /// use taco_threshold_automaton::expressions::*;
1141    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
1142    ///
1143    /// // action resetting var1
1144    /// Action::new_reset(Variable::new("var1"));
1145    /// ```
1146    pub fn new_reset(to_update: Variable) -> Self {
1147        Action {
1148            variable_to_update: to_update,
1149            update_expr: UpdateExpression::Reset,
1150        }
1151    }
1152
1153    /// Create a new action that updates variable `var_to_update` with the
1154    /// result of `update_expr`
1155    ///
1156    /// Returns an error if the update expression is malformed, e.g. if the
1157    /// update refers to a different variable than the one to update.
1158    ///
1159    /// # Example
1160    ///
1161    /// ```
1162    /// use taco_threshold_automaton::general_threshold_automaton::Action;
1163    /// use taco_threshold_automaton::expressions::*;
1164    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
1165    ///
1166    /// // action incrementing var1
1167    /// Action::new(
1168    ///     Variable::new("var1"),
1169    ///     IntegerExpression::Const(1)
1170    ///         + IntegerExpression::Atom(Variable::new("var1")),
1171    /// ).unwrap();
1172    /// ```
1173    pub fn new(
1174        to_update: Variable,
1175        update_expr: IntegerExpression<Variable>,
1176    ) -> Result<Self, ActionBuilderError> {
1177        let update_expr = Self::parse_to_update_expr(&to_update, update_expr)?;
1178
1179        Result::Ok(Action {
1180            variable_to_update: to_update,
1181            update_expr,
1182        })
1183    }
1184
1185    /// Create a new action that updates variable `var` according to `update`
1186    ///
1187    /// # Example
1188    ///
1189    /// ```
1190    /// use taco_threshold_automaton::general_threshold_automaton::Action;
1191    /// use taco_threshold_automaton::general_threshold_automaton::UpdateExpression;
1192    /// use taco_threshold_automaton::expressions::Variable;
1193    /// use taco_threshold_automaton::general_threshold_automaton::builder::*;
1194    ///
1195    /// // action incrementing var1
1196    /// Action::new_with_update(
1197    ///     Variable::new("var1"),
1198    ///     UpdateExpression::Inc(1),
1199    /// );
1200    /// ```
1201    pub fn new_with_update(var: Variable, update: UpdateExpression) -> Self {
1202        Self {
1203            variable_to_update: var,
1204            update_expr: update,
1205        }
1206    }
1207}
1208
1209/// Errors that can occur during the construction of a threshold automaton
1210#[derive(Debug, Clone)]
1211pub enum BuilderError {
1212    /// A parameter with the same name was added multiple times
1213    DuplicateParameter(Parameter),
1214    /// A variable with the same name was added multiple times
1215    DuplicateVariable(Variable),
1216    /// A location with the same name was added multiple times
1217    DuplicateLocation(Location),
1218    /// A rule with the same id was added multiple times
1219    DuplicateRuleId(Box<Rule>, Box<Rule>),
1220    /// A rule is malformed
1221    MalformedRule(Box<Rule>, String),
1222    /// An action is malformed
1223    MalformedAction(Box<Action>, String),
1224    /// A resilience condition is malformed
1225    MalformedResilienceCondition(Box<ParameterConstraint>, String),
1226    /// An initial location constraint is malformed
1227    MalformedInitialLocationConstraint(Box<LocationConstraint>, String),
1228    /// An initial variable constraint is malformed
1229    MalformedInitialVariableConstraint(Box<BooleanVarConstraint>, String),
1230    /// The same name was used multiple times for different components
1231    NameClash(String),
1232    /// An unknown component was used in a constraint
1233    UnknownComponent(String),
1234}
1235
1236impl std::error::Error for BuilderError {}
1237
1238impl std::fmt::Display for BuilderError {
1239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1240        match self {
1241            BuilderError::DuplicateParameter(param) => {
1242                write!(f, "Duplicate parameter: {param}")
1243            }
1244            BuilderError::DuplicateVariable(var) => write!(f, "Duplicate variable: {var}"),
1245            BuilderError::DuplicateLocation(loc) => write!(f, "Duplicate location: {loc}"),
1246            BuilderError::DuplicateRuleId(rule1, rule2) => {
1247                write!(
1248                    f,
1249                    "Duplicate rule id {} appearing in rule {} and {}",
1250                    rule1.id, rule1, rule2
1251                )
1252            }
1253            BuilderError::MalformedRule(rule, msg) => {
1254                write!(f, "Malformed rule {}: {} Rule: {}", rule.id, msg, rule)
1255            }
1256            BuilderError::UnknownComponent(c) => write!(f, "Unknown component: {c}"),
1257            BuilderError::MalformedAction(a, msg) => {
1258                write!(f, "Malformed action: {msg}: {a}")
1259            }
1260            BuilderError::MalformedResilienceCondition(rc, msg) => {
1261                write!(f, "Malformed resilience condition: {msg}: {rc}")
1262            }
1263            BuilderError::MalformedInitialLocationConstraint(lc, msg) => {
1264                write!(f, "Malformed initial location constraint: {msg}: {lc}")
1265            }
1266            BuilderError::MalformedInitialVariableConstraint(vc, msg) => {
1267                write!(f, "Malformed initial variable constraint: {msg}: {vc}")
1268            }
1269            BuilderError::NameClash(name) => write!(f, "Name {name} already taken"),
1270        }
1271    }
1272}
1273
1274/// Custom Error type for a failed construction of an action
1275#[derive(Debug, Clone)]
1276pub enum ActionBuilderError {
1277    /// Error indicating that the supplied update expression was invalid
1278    UpdateExpressionMalformed(Variable, IntegerExpression<Variable>, InvalidUpdateError),
1279}
1280
1281/// Custom Error type to indicate a malformed update expression
1282#[derive(Debug, Clone)]
1283pub enum InvalidUpdateError {
1284    /// Error indicating that the update set the variable to a constant value that is not 0
1285    ConstantUpdateNonZero(i64),
1286    /// Error indicating that the update referenced an additional variable
1287    AdditionalVariable(Variable),
1288    /// Error indicating that the update referenced a parameter
1289    ParameterFound(Parameter),
1290    /// Error indicating that the variable to update occurred multiple times in the update expression
1291    MultipleOccurrences,
1292}
1293
1294impl std::error::Error for ActionBuilderError {}
1295impl std::error::Error for InvalidUpdateError {}
1296
1297impl Display for ActionBuilderError {
1298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1299        match self {
1300            ActionBuilderError::UpdateExpressionMalformed(var, upd_expr, err) => {
1301                write!(
1302                    f,
1303                    "Update expression to variable {var} malformed. Err: {err}; Expression: {upd_expr}"
1304                )
1305            }
1306        }
1307    }
1308}
1309
1310impl From<EvaluationError<Variable>> for InvalidUpdateError {
1311    fn from(value: EvaluationError<Variable>) -> Self {
1312        match value {
1313            EvaluationError::AtomicNotFound(v) => InvalidUpdateError::AdditionalVariable(v),
1314            EvaluationError::ParameterNotFound(p) => InvalidUpdateError::ParameterFound(p),
1315        }
1316    }
1317}
1318
1319impl Display for InvalidUpdateError {
1320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1321        match self {
1322            InvalidUpdateError::ConstantUpdateNonZero(c) => write!(
1323                f,
1324                "Update expression assigns variable to constant value {c} which is not 0. Such updates are currently unsupported"
1325            ),
1326            InvalidUpdateError::AdditionalVariable(var) => write!(
1327                f,
1328                "Update expressions references additional variable {var}. Such updates are not allowed in threshold automata."
1329            ),
1330            InvalidUpdateError::ParameterFound(p) => write!(
1331                f,
1332                "Update expression references parameter {p}. Such updates are not allowed in threshold automata."
1333            ),
1334            InvalidUpdateError::MultipleOccurrences => write!(
1335                f,
1336                "Update expression references the variable to update more than once. Such expressions are unsupported by threshold automata in general, but might be transformable by hand."
1337            ),
1338        }
1339    }
1340}
1341
1342#[cfg(test)]
1343mod tests {
1344    use crate::{
1345        BooleanVarConstraint,
1346        expressions::{BooleanConnective, ComparisonOp},
1347    };
1348
1349    use super::*;
1350
1351    #[test]
1352    fn test_with_parameter() {
1353        let builder = GeneralThresholdAutomatonBuilder::new("test");
1354        let param = Parameter::new("n");
1355
1356        let ta = builder.with_parameter(param.clone()).unwrap().initialize();
1357
1358        assert_eq!(
1359            ta.parameters().collect::<Vec<_>>(),
1360            vec![&Parameter::new("n")]
1361        );
1362
1363        let ta = ta.build();
1364
1365        assert_eq!(ta.parameters.len(), 1);
1366        assert!(ta.parameters.contains(&param));
1367    }
1368
1369    #[test]
1370    fn test_with_parameter_duplicate() {
1371        let builder = GeneralThresholdAutomatonBuilder::new("test");
1372        let param = Parameter::new("n");
1373
1374        let error = builder
1375            .with_parameter(param.clone())
1376            .unwrap()
1377            .with_parameter(param.clone());
1378
1379        assert!(error.is_err());
1380        assert!(matches!(
1381            error.clone().unwrap_err(),
1382            BuilderError::DuplicateParameter(_)
1383        ));
1384        assert!(error.unwrap_err().to_string().contains("n"));
1385    }
1386
1387    #[test]
1388    fn test_with_parameters() {
1389        let builder = GeneralThresholdAutomatonBuilder::new("test");
1390        let params = HashSet::from([Parameter::new("n"), Parameter::new("m")]);
1391
1392        let ta = builder
1393            .with_parameters(params.clone())
1394            .unwrap()
1395            .initialize()
1396            .build();
1397
1398        assert_eq!(ta.parameters.len(), 2);
1399        assert_eq!(ta.parameters, params);
1400    }
1401
1402    #[test]
1403    fn test_with_parameter_duplicates_single_add() {
1404        let builder = GeneralThresholdAutomatonBuilder::new("test");
1405        let params = vec![Parameter::new("n"), Parameter::new("n")];
1406
1407        let error = builder.with_parameters(params.clone());
1408
1409        assert!(error.is_err());
1410        assert!(matches!(
1411            error.clone().unwrap_err(),
1412            BuilderError::DuplicateParameter(_)
1413        ));
1414        assert!(error.unwrap_err().to_string().contains("n"))
1415    }
1416
1417    #[test]
1418    fn test_with_parameter_duplicates_separate_add() {
1419        let builder = GeneralThresholdAutomatonBuilder::new("test");
1420        let params = vec![Parameter::new("n"), Parameter::new("m")];
1421
1422        let error = builder
1423            .with_parameters(params.clone())
1424            .unwrap()
1425            .with_parameter(params[1].clone());
1426
1427        assert!(error.is_err());
1428        assert!(matches!(
1429            error.clone().unwrap_err(),
1430            BuilderError::DuplicateParameter(_)
1431        ));
1432        assert!(error.unwrap_err().to_string().contains("m"))
1433    }
1434
1435    #[test]
1436    fn test_with_parameter_name_clash() {
1437        let builder = GeneralThresholdAutomatonBuilder::new("test");
1438        let param = Parameter::new("n");
1439
1440        let error = builder
1441            .with_variable(Variable::new("n"))
1442            .unwrap()
1443            .with_parameter(param.clone());
1444
1445        assert!(error.is_err());
1446        assert!(matches!(
1447            error.clone().unwrap_err(),
1448            BuilderError::NameClash(_)
1449        ));
1450        assert!(error.unwrap_err().to_string().contains("n"))
1451    }
1452
1453    #[test]
1454    fn test_with_location() {
1455        let builder = GeneralThresholdAutomatonBuilder::new("test");
1456        let loc = Location::new("A");
1457
1458        let ta = builder
1459            .with_location(loc.clone())
1460            .unwrap()
1461            .initialize()
1462            .build();
1463
1464        assert_eq!(ta.locations.len(), 1);
1465        assert!(ta.locations.contains(&loc));
1466    }
1467
1468    #[test]
1469    fn test_with_location_duplicate() {
1470        let builder = GeneralThresholdAutomatonBuilder::new("test");
1471        let loc = Location::new("A");
1472
1473        let error = builder
1474            .with_location(loc.clone())
1475            .unwrap()
1476            .with_location(loc.clone());
1477
1478        assert!(error.is_err());
1479        assert!(matches!(
1480            error.clone().unwrap_err(),
1481            BuilderError::DuplicateLocation(_)
1482        ));
1483        assert!(error.unwrap_err().to_string().contains("A"))
1484    }
1485
1486    #[test]
1487    fn test_with_location_name_clash() {
1488        let builder = GeneralThresholdAutomatonBuilder::new("test");
1489        let loc = Location::new("n");
1490
1491        let error = builder
1492            .with_variable(Variable::new("n"))
1493            .unwrap()
1494            .with_location(loc.clone());
1495
1496        assert!(error.is_err());
1497        assert!(matches!(
1498            error.clone().unwrap_err(),
1499            BuilderError::NameClash(_)
1500        ));
1501        assert!(error.unwrap_err().to_string().contains("n"))
1502    }
1503
1504    #[test]
1505    fn test_with_locations() {
1506        let builder = GeneralThresholdAutomatonBuilder::new("test");
1507        let locs = HashSet::from([Location::new("A"), Location::new("B")]);
1508
1509        let ta = builder
1510            .with_locations(locs.clone())
1511            .unwrap()
1512            .initialize()
1513            .build();
1514
1515        assert_eq!(ta.locations.len(), 2);
1516        assert_eq!(ta.locations, locs);
1517    }
1518
1519    #[test]
1520    fn test_with_location_duplicates_single_add() {
1521        let builder = GeneralThresholdAutomatonBuilder::new("test");
1522        let locs = vec![Location::new("A"), Location::new("A")];
1523
1524        let error = builder.with_locations(locs.clone());
1525
1526        assert!(error.is_err());
1527        assert!(matches!(
1528            error.clone().unwrap_err(),
1529            BuilderError::DuplicateLocation(_)
1530        ));
1531        assert!(error.unwrap_err().to_string().contains("A"))
1532    }
1533
1534    #[test]
1535    fn test_with_location_duplicates_separate_add() {
1536        let builder = GeneralThresholdAutomatonBuilder::new("test");
1537        let locs = vec![Location::new("A"), Location::new("B")];
1538
1539        let error = builder
1540            .with_locations(locs.clone())
1541            .unwrap()
1542            .with_location(locs[1].clone());
1543
1544        assert!(error.is_err());
1545        assert!(matches!(
1546            error.clone().unwrap_err(),
1547            BuilderError::DuplicateLocation(_)
1548        ));
1549        assert!(error.unwrap_err().to_string().contains("B"))
1550    }
1551
1552    #[test]
1553    fn test_with_variable() {
1554        let builder = GeneralThresholdAutomatonBuilder::new("test");
1555        let var = Variable::new("x");
1556
1557        let ta = builder
1558            .with_variable(var.clone())
1559            .unwrap()
1560            .initialize()
1561            .build();
1562
1563        assert_eq!(ta.variables.len(), 1);
1564        assert!(ta.variables.contains(&var));
1565    }
1566
1567    #[test]
1568    fn test_with_variable_duplicate() {
1569        let builder = GeneralThresholdAutomatonBuilder::new("test");
1570        let var = Variable::new("x");
1571
1572        let error = builder
1573            .with_variable(var.clone())
1574            .unwrap()
1575            .with_variable(var.clone());
1576
1577        assert!(error.is_err());
1578        assert!(matches!(
1579            error.clone().unwrap_err(),
1580            BuilderError::DuplicateVariable(_)
1581        ));
1582        assert!(error.unwrap_err().to_string().contains("x"))
1583    }
1584
1585    #[test]
1586    fn with_variable_name_clash() {
1587        let builder = GeneralThresholdAutomatonBuilder::new("test");
1588        let var = Variable::new("z");
1589
1590        let error = builder
1591            .with_parameter(Parameter::new("z"))
1592            .unwrap()
1593            .with_variable(var.clone());
1594
1595        assert!(error.is_err());
1596        assert!(matches!(
1597            error.clone().unwrap_err(),
1598            BuilderError::NameClash(_)
1599        ));
1600        assert!(error.unwrap_err().to_string().contains("z"))
1601    }
1602
1603    #[test]
1604    fn test_with_variables() {
1605        let builder = GeneralThresholdAutomatonBuilder::new("test");
1606        let vars = HashSet::from([Variable::new("x"), Variable::new("y")]);
1607
1608        let ta = builder
1609            .with_variables(vars.clone())
1610            .unwrap()
1611            .initialize()
1612            .build();
1613
1614        assert_eq!(ta.variables.len(), 2);
1615        assert_eq!(ta.variables, vars);
1616    }
1617
1618    #[test]
1619    fn test_with_variable_duplicates_single_add() {
1620        let builder = GeneralThresholdAutomatonBuilder::new("test");
1621        let vars = vec![Variable::new("x"), Variable::new("x")];
1622
1623        let error = builder.with_variables(vars.clone());
1624
1625        assert!(error.is_err());
1626        assert!(matches!(
1627            error.clone().unwrap_err(),
1628            BuilderError::DuplicateVariable(_)
1629        ));
1630        assert!(error.unwrap_err().to_string().contains("x"))
1631    }
1632
1633    #[test]
1634    fn test_with_variable_duplicates_separate_add() {
1635        let builder = GeneralThresholdAutomatonBuilder::new("test");
1636        let vars = vec![Variable::new("x"), Variable::new("y")];
1637
1638        let error = builder
1639            .with_variables(vars.clone())
1640            .unwrap()
1641            .with_variable(vars[1].clone());
1642
1643        assert!(error.is_err());
1644        assert!(matches!(
1645            error.clone().unwrap_err(),
1646            BuilderError::DuplicateVariable(_)
1647        ));
1648        assert!(error.unwrap_err().to_string().contains("y"))
1649    }
1650
1651    #[test]
1652    fn test_with_rule() {
1653        let builder = GeneralThresholdAutomatonBuilder::new("test");
1654
1655        let rule = Rule {
1656            id: 0,
1657            source: Location::new("A"),
1658            target: Location::new("B"),
1659            guard: BooleanVarConstraint::ComparisonExpression(
1660                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1661                ComparisonOp::Eq,
1662                Box::new(IntegerExpression::Const(1)),
1663            ),
1664            actions: vec![Action {
1665                variable_to_update: Variable::new("var1"),
1666                update_expr: UpdateExpression::Reset,
1667            }],
1668        };
1669
1670        let ta = builder
1671            .with_locations(vec![Location::new("A"), Location::new("B")])
1672            .unwrap()
1673            .with_variables(vec![Variable::new("var1")])
1674            .unwrap()
1675            .initialize();
1676
1677        let ta = ta.with_rule(rule.clone()).unwrap().build();
1678
1679        assert_eq!(ta.outgoing_rules.len(), 1);
1680        assert_eq!(ta.outgoing_rules.get(&Location::new("A")).unwrap()[0], rule);
1681    }
1682
1683    #[test]
1684    fn test_with_rule_duplicate_rule() {
1685        let builder = GeneralThresholdAutomatonBuilder::new("test");
1686
1687        let rule = Rule {
1688            id: 0,
1689            source: Location::new("A"),
1690            target: Location::new("B"),
1691            guard: BooleanVarConstraint::ComparisonExpression(
1692                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1693                ComparisonOp::Eq,
1694                Box::new(IntegerExpression::Const(1)),
1695            ),
1696            actions: vec![Action {
1697                variable_to_update: Variable::new("var1"),
1698                update_expr: UpdateExpression::Reset,
1699            }],
1700        };
1701
1702        let ta = builder
1703            .with_locations(vec![Location::new("A"), Location::new("B")])
1704            .unwrap()
1705            .with_variables(vec![Variable::new("var1")])
1706            .unwrap()
1707            .initialize();
1708
1709        let ta = ta.with_rule(rule.clone()).unwrap().with_rule(rule.clone());
1710
1711        assert!(ta.is_err());
1712        assert!(matches!(
1713            ta.unwrap_err(),
1714            BuilderError::DuplicateRuleId(_, _)
1715        ));
1716    }
1717
1718    #[test]
1719    fn test_with_rule_double_update() {
1720        let builder = GeneralThresholdAutomatonBuilder::new("test");
1721
1722        let rule = Rule {
1723            id: 0,
1724            source: Location::new("A"),
1725            target: Location::new("B"),
1726            guard: BooleanVarConstraint::ComparisonExpression(
1727                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1728                ComparisonOp::Eq,
1729                Box::new(IntegerExpression::Const(1)),
1730            ),
1731            actions: vec![
1732                Action {
1733                    variable_to_update: Variable::new("var1"),
1734                    update_expr: UpdateExpression::Reset,
1735                },
1736                Action {
1737                    variable_to_update: Variable::new("var1"),
1738                    update_expr: UpdateExpression::Reset,
1739                },
1740            ],
1741        };
1742
1743        let ta = builder
1744            .with_locations(vec![Location::new("A"), Location::new("B")])
1745            .unwrap()
1746            .with_variables(vec![Variable::new("var1")])
1747            .unwrap()
1748            .initialize();
1749
1750        let ta = ta.with_rule(rule.clone());
1751
1752        assert!(ta.is_err());
1753        assert!(matches!(ta.unwrap_err(), BuilderError::MalformedRule(_, _)));
1754    }
1755
1756    #[test]
1757    fn test_with_rule_duplicate_id() {
1758        let builder = GeneralThresholdAutomatonBuilder::new("test");
1759
1760        let rule1 = Rule {
1761            id: 0,
1762            source: Location::new("A"),
1763            target: Location::new("B"),
1764            guard: BooleanVarConstraint::ComparisonExpression(
1765                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1766                ComparisonOp::Eq,
1767                Box::new(IntegerExpression::Const(1)),
1768            ),
1769            actions: vec![Action {
1770                variable_to_update: Variable::new("var1"),
1771                update_expr: UpdateExpression::Reset,
1772            }],
1773        };
1774
1775        let rule2 = Rule {
1776            id: 0,
1777            source: Location::new("B"),
1778            target: Location::new("A"),
1779            guard: BooleanVarConstraint::ComparisonExpression(
1780                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1781                ComparisonOp::Eq,
1782                Box::new(IntegerExpression::Const(1)),
1783            ),
1784            actions: vec![Action {
1785                variable_to_update: Variable::new("var1"),
1786                update_expr: UpdateExpression::Reset,
1787            }],
1788        };
1789
1790        let ta = builder
1791            .with_locations(vec![Location::new("A"), Location::new("B")])
1792            .unwrap()
1793            .with_variables(vec![Variable::new("var1")])
1794            .unwrap()
1795            .initialize();
1796
1797        let ta = ta.with_rule(rule1).unwrap().with_rule(rule2);
1798
1799        assert!(ta.is_err());
1800        assert!(matches!(
1801            ta.clone().unwrap_err(),
1802            BuilderError::DuplicateRuleId(_, _)
1803        ));
1804        assert!(ta.unwrap_err().to_string().contains("0"));
1805    }
1806
1807    #[test]
1808    fn test_with_rule_unknown_guard_var() {
1809        let builder = GeneralThresholdAutomatonBuilder::new("test");
1810
1811        let rule = Rule {
1812            id: 0,
1813            source: Location::new("A"),
1814            target: Location::new("B"),
1815            guard: BooleanVarConstraint::ComparisonExpression(
1816                Box::new(IntegerExpression::Atom(Variable::new("var-uk"))),
1817                ComparisonOp::Eq,
1818                Box::new(IntegerExpression::Const(1)),
1819            ),
1820            actions: vec![Action {
1821                variable_to_update: Variable::new("var1"),
1822                update_expr: UpdateExpression::Reset,
1823            }],
1824        };
1825
1826        let ta = builder
1827            .with_locations(vec![Location::new("A"), Location::new("B")])
1828            .unwrap()
1829            .with_variables(vec![Variable::new("var1")])
1830            .unwrap()
1831            .initialize();
1832
1833        let ta = ta.with_rule(rule.clone());
1834
1835        assert!(ta.is_err());
1836        assert!(matches!(
1837            ta.clone().unwrap_err(),
1838            BuilderError::MalformedRule(_, _)
1839        ));
1840        assert!(ta.unwrap_err().to_string().contains("var-uk"));
1841    }
1842
1843    #[test]
1844    fn test_with_rule_unknown_guard_param() {
1845        let builder = GeneralThresholdAutomatonBuilder::new("test");
1846
1847        let rule = Rule {
1848            id: 0,
1849            source: Location::new("A"),
1850            target: Location::new("B"),
1851            guard: BooleanVarConstraint::ComparisonExpression(
1852                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1853                ComparisonOp::Eq,
1854                Box::new(IntegerExpression::Param(Parameter::new("uk"))),
1855            ),
1856            actions: vec![Action {
1857                variable_to_update: Variable::new("var1"),
1858                update_expr: UpdateExpression::Reset,
1859            }],
1860        };
1861
1862        let ta = builder
1863            .with_locations(vec![Location::new("A"), Location::new("B")])
1864            .unwrap()
1865            .with_variables(vec![Variable::new("var1")])
1866            .unwrap()
1867            .initialize();
1868
1869        let ta = ta.with_rule(rule.clone());
1870
1871        assert!(ta.is_err());
1872        assert!(matches!(
1873            ta.clone().unwrap_err(),
1874            BuilderError::MalformedRule(_, _)
1875        ));
1876        assert!(ta.unwrap_err().to_string().contains("uk"));
1877    }
1878
1879    #[test]
1880    fn test_with_rule_unknown_action_var() {
1881        let builder = GeneralThresholdAutomatonBuilder::new("test");
1882
1883        let rule = Rule {
1884            id: 0,
1885            source: Location::new("A"),
1886            target: Location::new("B"),
1887            guard: BooleanVarConstraint::ComparisonExpression(
1888                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1889                ComparisonOp::Eq,
1890                Box::new(IntegerExpression::Const(1)),
1891            ),
1892            actions: vec![Action {
1893                variable_to_update: Variable::new("var-uk"),
1894                update_expr: UpdateExpression::Reset,
1895            }],
1896        };
1897
1898        let ta = builder
1899            .with_locations(vec![Location::new("A"), Location::new("B")])
1900            .unwrap()
1901            .with_variables(vec![Variable::new("var1")])
1902            .unwrap()
1903            .initialize();
1904
1905        let ta = ta.with_rule(rule.clone());
1906
1907        assert!(ta.is_err());
1908        assert!(matches!(
1909            ta.clone().unwrap_err(),
1910            BuilderError::MalformedRule(_, _)
1911        ));
1912        assert!(ta.unwrap_err().to_string().contains("var-uk"));
1913    }
1914
1915    #[test]
1916    fn test_with_rule_unknown_source() {
1917        let builder = GeneralThresholdAutomatonBuilder::new("test");
1918
1919        let rule = Rule {
1920            id: 0,
1921            source: Location::new("uk"),
1922            target: Location::new("B"),
1923            guard: BooleanVarConstraint::ComparisonExpression(
1924                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1925                ComparisonOp::Eq,
1926                Box::new(IntegerExpression::Const(1)),
1927            ),
1928            actions: vec![Action {
1929                variable_to_update: Variable::new("var1"),
1930                update_expr: UpdateExpression::Reset,
1931            }],
1932        };
1933
1934        let ta = builder
1935            .with_locations(vec![Location::new("A"), Location::new("B")])
1936            .unwrap()
1937            .with_variables(vec![Variable::new("var1")])
1938            .unwrap()
1939            .initialize();
1940
1941        let ta = ta.with_rule(rule.clone());
1942
1943        assert!(ta.is_err());
1944        assert!(matches!(
1945            ta.clone().unwrap_err(),
1946            BuilderError::MalformedRule(_, _)
1947        ));
1948        assert!(ta.unwrap_err().to_string().contains("uk"));
1949    }
1950
1951    #[test]
1952    fn test_with_rule_unknown_target() {
1953        let builder = GeneralThresholdAutomatonBuilder::new("test");
1954
1955        let rule = Rule {
1956            id: 0,
1957            source: Location::new("A"),
1958            target: Location::new("uk"),
1959            guard: BooleanVarConstraint::ComparisonExpression(
1960                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1961                ComparisonOp::Eq,
1962                Box::new(IntegerExpression::Const(1)),
1963            ),
1964            actions: vec![Action {
1965                variable_to_update: Variable::new("var1"),
1966                update_expr: UpdateExpression::Reset,
1967            }],
1968        };
1969
1970        let ta = builder
1971            .with_locations(vec![Location::new("A"), Location::new("B")])
1972            .unwrap()
1973            .with_variables(vec![Variable::new("var1")])
1974            .unwrap()
1975            .initialize();
1976
1977        let ta = ta.with_rule(rule.clone());
1978
1979        assert!(ta.is_err());
1980        assert!(matches!(
1981            ta.clone().unwrap_err(),
1982            BuilderError::MalformedRule(_, _)
1983        ));
1984    }
1985
1986    #[test]
1987    fn test_with_rules() {
1988        let builder = GeneralThresholdAutomatonBuilder::new("test");
1989
1990        let rule1 = Rule {
1991            id: 0,
1992            source: Location::new("A"),
1993            target: Location::new("B"),
1994            guard: BooleanVarConstraint::ComparisonExpression(
1995                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1996                ComparisonOp::Eq,
1997                Box::new(IntegerExpression::Const(1)),
1998            ),
1999            actions: vec![Action {
2000                variable_to_update: Variable::new("var1"),
2001                update_expr: UpdateExpression::Reset,
2002            }],
2003        };
2004
2005        let rule2 = Rule {
2006            id: 1,
2007            source: Location::new("B"),
2008            target: Location::new("C"),
2009            guard: BooleanVarConstraint::Not(Box::new(BooleanVarConstraint::ComparisonExpression(
2010                Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2011                ComparisonOp::Gt,
2012                Box::new(IntegerExpression::Const(0)),
2013            ))),
2014            actions: vec![Action {
2015                variable_to_update: Variable::new("var2"),
2016                update_expr: UpdateExpression::Reset,
2017            }],
2018        };
2019
2020        let ta = builder
2021            .with_locations(vec![
2022                Location::new("A"),
2023                Location::new("B"),
2024                Location::new("C"),
2025            ])
2026            .unwrap()
2027            .with_variables(vec![Variable::new("var1"), Variable::new("var2")])
2028            .unwrap()
2029            .initialize();
2030
2031        let ta = ta
2032            .with_rules(vec![rule1.clone(), rule2.clone()])
2033            .unwrap()
2034            .build();
2035
2036        assert_eq!(ta.outgoing_rules.len(), 2);
2037        assert_eq!(
2038            ta.outgoing_rules.get(&Location::new("A")).unwrap()[0],
2039            rule1
2040        );
2041        assert_eq!(
2042            ta.outgoing_rules.get(&Location::new("B")).unwrap()[0],
2043            rule2
2044        );
2045    }
2046
2047    #[test]
2048    fn test_with_location_constraint() {
2049        let builder = GeneralThresholdAutomatonBuilder::new("test");
2050
2051        let loc_constraint = LocationConstraint::ComparisonExpression(
2052            Box::new(IntegerExpression::Atom(Location::new("A"))),
2053            ComparisonOp::Eq,
2054            Box::new(IntegerExpression::Const(1)),
2055        );
2056
2057        let ta = builder
2058            .with_locations(vec![Location::new("A")])
2059            .unwrap()
2060            .initialize()
2061            .with_initial_location_constraint(loc_constraint.clone())
2062            .unwrap()
2063            .build();
2064
2065        assert_eq!(ta.initial_location_constraint.len(), 1);
2066        assert_eq!(ta.initial_location_constraint[0], loc_constraint);
2067    }
2068
2069    #[test]
2070    fn test_with_location_constraint_unknown_loc() {
2071        let builder = GeneralThresholdAutomatonBuilder::new("test");
2072
2073        let loc_constraint = LocationConstraint::ComparisonExpression(
2074            Box::new(IntegerExpression::Atom(Location::new("uk"))),
2075            ComparisonOp::Eq,
2076            Box::new(IntegerExpression::Const(1)),
2077        );
2078
2079        let ta = builder
2080            .with_locations(vec![Location::new("A")])
2081            .unwrap()
2082            .initialize()
2083            .with_initial_location_constraint(loc_constraint.clone());
2084
2085        assert!(ta.is_err());
2086        assert!(matches!(
2087            ta.clone().unwrap_err(),
2088            BuilderError::MalformedInitialLocationConstraint(_, _)
2089        ));
2090        assert!(ta.unwrap_err().to_string().contains("uk"));
2091    }
2092
2093    #[test]
2094    fn test_with_variable_constraint() {
2095        let builder = GeneralThresholdAutomatonBuilder::new("test");
2096
2097        let var_constraint = BooleanVarConstraint::ComparisonExpression(
2098            Box::new(IntegerExpression::Atom(Variable::new("A"))),
2099            ComparisonOp::Eq,
2100            Box::new(IntegerExpression::Neg(Box::new(IntegerExpression::Const(
2101                1,
2102            )))),
2103        );
2104
2105        let ta = builder
2106            .with_variables(vec![Variable::new("A")])
2107            .unwrap()
2108            .initialize()
2109            .with_initial_variable_constraint(var_constraint.clone())
2110            .unwrap()
2111            .build();
2112
2113        assert_eq!(ta.initial_variable_constraint.len(), 1);
2114        assert_eq!(ta.initial_variable_constraint[0], var_constraint);
2115    }
2116
2117    #[test]
2118    fn test_with_variable_constraint_unknown_var() {
2119        let builder = GeneralThresholdAutomatonBuilder::new("test");
2120
2121        let var_constraint = BooleanVarConstraint::ComparisonExpression(
2122            Box::new(IntegerExpression::Atom(Variable::new("uk"))),
2123            ComparisonOp::Eq,
2124            Box::new(IntegerExpression::Const(1)),
2125        );
2126
2127        let ta = builder
2128            .with_variables(vec![Variable::new("A")])
2129            .unwrap()
2130            .initialize()
2131            .with_initial_variable_constraint(var_constraint.clone());
2132
2133        assert!(ta.is_err());
2134        assert!(matches!(
2135            ta.clone().unwrap_err(),
2136            BuilderError::MalformedInitialVariableConstraint(_, _)
2137        ));
2138        assert!(ta.unwrap_err().to_string().contains("uk"));
2139    }
2140
2141    #[test]
2142    fn test_with_resilience_condition() {
2143        let builder = GeneralThresholdAutomatonBuilder::new("test");
2144
2145        let rc = ParameterConstraint::ComparisonExpression(
2146            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2147            ComparisonOp::Geq,
2148            Box::new(IntegerExpression::Const(0)),
2149        );
2150
2151        let ta = builder
2152            .with_parameters(vec![Parameter::new("n")])
2153            .unwrap()
2154            .initialize()
2155            .with_resilience_condition(rc.clone())
2156            .unwrap()
2157            .build();
2158
2159        assert_eq!(ta.resilience_condition.len(), 1);
2160        assert_eq!(ta.resilience_condition[0], rc);
2161    }
2162
2163    #[test]
2164    fn test_with_resilience_condition_canonicalize1() {
2165        let builder = GeneralThresholdAutomatonBuilder::new("test");
2166
2167        let input_rc = ParameterConstraint::ComparisonExpression(
2168            Box::new(-IntegerExpression::Atom(Parameter::new("n"))),
2169            ComparisonOp::Geq,
2170            Box::new(IntegerExpression::Const(0)),
2171        ) & BooleanExpression::True;
2172
2173        let expected_rc = ParameterConstraint::ComparisonExpression(
2174            Box::new(-IntegerExpression::Param(Parameter::new("n"))),
2175            ComparisonOp::Geq,
2176            Box::new(IntegerExpression::Const(0)),
2177        ) & BooleanExpression::True;
2178
2179        let ta = builder
2180            .with_parameters(vec![Parameter::new("n")])
2181            .unwrap()
2182            .initialize()
2183            .with_resilience_condition(input_rc)
2184            .unwrap()
2185            .build();
2186
2187        assert_eq!(ta.resilience_condition.len(), 1);
2188        assert_eq!(ta.resilience_condition[0], expected_rc);
2189    }
2190
2191    #[test]
2192    fn test_with_resilience_condition_canonicalize2() {
2193        let builder = GeneralThresholdAutomatonBuilder::new("test");
2194
2195        let input_rc = ParameterConstraint::ComparisonExpression(
2196            Box::new(IntegerExpression::Atom(Parameter::new("n"))),
2197            ComparisonOp::Geq,
2198            Box::new(IntegerExpression::Const(0)),
2199        ) | ParameterConstraint::ComparisonExpression(
2200            Box::new(IntegerExpression::Const(0)),
2201            ComparisonOp::Geq,
2202            Box::new(IntegerExpression::Atom(Parameter::new("f"))),
2203        ) & !BooleanExpression::False;
2204
2205        let expected_rc = ParameterConstraint::ComparisonExpression(
2206            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2207            ComparisonOp::Geq,
2208            Box::new(IntegerExpression::Const(0)),
2209        ) | ParameterConstraint::ComparisonExpression(
2210            Box::new(IntegerExpression::Const(0)),
2211            ComparisonOp::Geq,
2212            Box::new(IntegerExpression::Param(Parameter::new("f"))),
2213        ) & !BooleanExpression::False;
2214
2215        let ta = builder
2216            .with_parameters(vec![Parameter::new("n"), Parameter::new("f")])
2217            .unwrap()
2218            .initialize()
2219            .with_resilience_condition(input_rc)
2220            .unwrap()
2221            .build();
2222
2223        assert_eq!(ta.resilience_condition.len(), 1);
2224        assert_eq!(ta.resilience_condition[0], expected_rc);
2225    }
2226
2227    #[test]
2228    fn test_with_resilience_condition_unknown_param() {
2229        let builder = GeneralThresholdAutomatonBuilder::new("test");
2230
2231        let rc = ParameterConstraint::ComparisonExpression(
2232            Box::new(IntegerExpression::Atom(Parameter::new("uk"))),
2233            ComparisonOp::Geq,
2234            Box::new(IntegerExpression::Const(0)),
2235        );
2236
2237        let ta = builder
2238            .with_parameters(vec![Parameter::new("n")])
2239            .unwrap()
2240            .initialize()
2241            .with_resilience_condition(rc.clone());
2242
2243        assert!(ta.is_err());
2244        assert!(matches!(
2245            ta.clone().unwrap_err(),
2246            BuilderError::MalformedResilienceCondition(_, _)
2247        ));
2248        assert!(ta.unwrap_err().to_string().contains("uk"));
2249    }
2250
2251    #[test]
2252    fn test_action_new() {
2253        let action = Action::new(
2254            Variable::new("x"),
2255            IntegerExpression::Atom(Variable::new("x")) + IntegerExpression::Const(1),
2256        )
2257        .unwrap();
2258
2259        assert_eq!(action.variable_to_update, Variable::new("x"));
2260        assert_eq!(action.update_expr, UpdateExpression::Inc(1));
2261
2262        let action = Action::new(
2263            Variable::new("x"),
2264            IntegerExpression::Atom(Variable::new("x")) - IntegerExpression::Const(1),
2265        )
2266        .unwrap();
2267
2268        assert_eq!(action.variable_to_update, Variable::new("x"));
2269        assert_eq!(action.update_expr, UpdateExpression::Dec(1));
2270
2271        let action = Action::new(
2272            Variable::new("x"),
2273            IntegerExpression::Atom(Variable::new("x")),
2274        )
2275        .unwrap();
2276
2277        assert_eq!(action.variable_to_update, Variable::new("x"));
2278        assert_eq!(action.update_expr, UpdateExpression::Unchanged);
2279    }
2280
2281    #[test]
2282    fn test_canonicalize_reset_in_action() {
2283        let action = Action::new(Variable::new("x"), IntegerExpression::Const(0)).unwrap();
2284
2285        assert_eq!(action.variable_to_update, Variable::new("x"));
2286        assert_eq!(action.update_expr, UpdateExpression::Reset);
2287    }
2288
2289    #[test]
2290    fn test_action_const_update_non_zero() {
2291        let action = Action::new(Variable::new("x"), IntegerExpression::Const(1));
2292
2293        assert!(action.is_err());
2294        assert!(matches!(
2295            action.clone().unwrap_err(),
2296            ActionBuilderError::UpdateExpressionMalformed(
2297                _,
2298                _,
2299                InvalidUpdateError::ConstantUpdateNonZero(1)
2300            )
2301        ));
2302        assert!(action.unwrap_err().to_string().contains(&1.to_string()));
2303    }
2304
2305    #[test]
2306    fn test_action_new_with_different_var() {
2307        let action = Action::new(
2308            Variable::new("x"),
2309            IntegerExpression::Atom(Variable::new("y")) + IntegerExpression::Const(1),
2310        );
2311
2312        assert!(action.is_err());
2313        assert!(matches!(
2314            action.clone().unwrap_err(),
2315            ActionBuilderError::UpdateExpressionMalformed(
2316                _,
2317                _,
2318                InvalidUpdateError::AdditionalVariable(_)
2319            )
2320        ));
2321        assert!(
2322            action
2323                .unwrap_err()
2324                .to_string()
2325                .contains(&Variable::new("y").to_string())
2326        );
2327    }
2328
2329    #[test]
2330    fn action_new_with_parameter() {
2331        let action = Action::new(
2332            Variable::new("x"),
2333            IntegerExpression::Param(Parameter::new("n")) + IntegerExpression::Const(1),
2334        );
2335
2336        assert!(action.is_err());
2337        assert!(matches!(
2338            action.clone().unwrap_err(),
2339            ActionBuilderError::UpdateExpressionMalformed(
2340                _,
2341                _,
2342                InvalidUpdateError::ParameterFound(_)
2343            )
2344        ));
2345        assert!(
2346            action
2347                .unwrap_err()
2348                .to_string()
2349                .contains(&Parameter::new("n").to_string())
2350        );
2351    }
2352
2353    #[test]
2354    fn action_new_multiple_var() {
2355        let action = Action::new(
2356            Variable::new("x"),
2357            IntegerExpression::Atom(Variable::new("x"))
2358                * IntegerExpression::Atom(Variable::new("x"))
2359                + IntegerExpression::Const(1),
2360        );
2361
2362        assert!(action.is_err());
2363        assert!(matches!(
2364            action.clone().unwrap_err(),
2365            ActionBuilderError::UpdateExpressionMalformed(
2366                _,
2367                _,
2368                InvalidUpdateError::MultipleOccurrences
2369            )
2370        ));
2371        assert!(
2372            action
2373                .unwrap_err()
2374                .to_string()
2375                .contains(&Variable::new("x").to_string())
2376        );
2377    }
2378
2379    #[test]
2380    fn test_complete_automaton() {
2381        let expected_ta = GeneralThresholdAutomaton {
2382            name: "test_ta1".to_string(),
2383            parameters: HashSet::from([
2384                Parameter::new("n"),
2385                Parameter::new("t"),
2386                Parameter::new("f"),
2387            ]),
2388            variables: HashSet::from([
2389                Variable::new("var1"),
2390                Variable::new("var2"),
2391                Variable::new("var3"),
2392            ]),
2393            locations: HashSet::from([
2394                Location::new("loc1"),
2395                Location::new("loc2"),
2396                Location::new("loc3"),
2397            ]),
2398            initial_variable_constraint: vec![],
2399            outgoing_rules: HashMap::from([
2400                (
2401                    Location::new("loc1"),
2402                    vec![Rule {
2403                        id: 0,
2404                        source: Location::new("loc1"),
2405                        target: Location::new("loc2"),
2406                        guard: BooleanExpression::True,
2407                        actions: vec![],
2408                    }],
2409                ),
2410                (
2411                    Location::new("loc2"),
2412                    vec![Rule {
2413                        id: 1,
2414                        source: Location::new("loc2"),
2415                        target: Location::new("loc3"),
2416                        guard: BooleanExpression::BinaryExpression(
2417                            Box::new(BooleanVarConstraint::ComparisonExpression(
2418                                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2419                                ComparisonOp::Eq,
2420                                Box::new(IntegerExpression::Const(1)),
2421                            )),
2422                            BooleanConnective::And,
2423                            Box::new(BooleanVarConstraint::ComparisonExpression(
2424                                Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2425                                ComparisonOp::Eq,
2426                                Box::new(IntegerExpression::Param(Parameter::new("n"))),
2427                            )),
2428                        ),
2429                        actions: vec![
2430                            Action {
2431                                variable_to_update: Variable::new("var3"),
2432                                update_expr: UpdateExpression::Reset,
2433                            },
2434                            Action {
2435                                variable_to_update: Variable::new("var1"),
2436                                update_expr: UpdateExpression::Inc(1),
2437                            },
2438                        ],
2439                    }],
2440                ),
2441            ]),
2442
2443            initial_location_constraint: vec![LocationConstraint::BinaryExpression(
2444                Box::new(LocationConstraint::ComparisonExpression(
2445                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
2446                    ComparisonOp::Eq,
2447                    Box::new(
2448                        IntegerExpression::Param(Parameter::new("n"))
2449                            - IntegerExpression::Param(Parameter::new("f")),
2450                    ),
2451                )),
2452                BooleanConnective::Or,
2453                Box::new(LocationConstraint::ComparisonExpression(
2454                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
2455                    ComparisonOp::Eq,
2456                    Box::new(IntegerExpression::Const(0)),
2457                )),
2458            )],
2459
2460            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
2461                Box::new(IntegerExpression::Param(Parameter::new("n"))),
2462                ComparisonOp::Gt,
2463                Box::new(
2464                    IntegerExpression::Const(3) * IntegerExpression::Param(Parameter::new("f")),
2465                ),
2466            )],
2467        };
2468
2469        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
2470            .with_parameters(vec![
2471                Parameter::new("n"),
2472                Parameter::new("t"),
2473                Parameter::new("f"),
2474            ])
2475            .unwrap()
2476            .with_variables(vec![
2477                Variable::new("var1"),
2478                Variable::new("var2"),
2479                Variable::new("var3"),
2480            ])
2481            .unwrap()
2482            .with_locations(vec![
2483                Location::new("loc1"),
2484                Location::new("loc2"),
2485                Location::new("loc3"),
2486            ])
2487            .unwrap()
2488            .initialize()
2489            .with_rules(vec![
2490                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
2491                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
2492                    .with_guard(
2493                        BooleanVarConstraint::ComparisonExpression(
2494                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2495                            ComparisonOp::Eq,
2496                            Box::new(IntegerExpression::Const(1)),
2497                        ) & BooleanVarConstraint::ComparisonExpression(
2498                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2499                            ComparisonOp::Eq,
2500                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2501                        ),
2502                    )
2503                    .with_action(Action::new_reset(Variable::new("var3")))
2504                    .with_action(
2505                        Action::new(
2506                            Variable::new("var1"),
2507                            IntegerExpression::Atom(Variable::new("var1"))
2508                                + IntegerExpression::Const(1),
2509                        )
2510                        .unwrap(),
2511                    )
2512                    .build(),
2513            ])
2514            .unwrap()
2515            .with_initial_location_constraint(
2516                LocationConstraint::ComparisonExpression(
2517                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
2518                    ComparisonOp::Eq,
2519                    Box::new(
2520                        IntegerExpression::Param(Parameter::new("n"))
2521                            - IntegerExpression::Param(Parameter::new("f")),
2522                    ),
2523                ) | LocationConstraint::ComparisonExpression(
2524                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
2525                    ComparisonOp::Eq,
2526                    Box::new(IntegerExpression::Const(0)),
2527                ),
2528            )
2529            .unwrap()
2530            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
2531                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
2532                ComparisonOp::Gt,
2533                Box::new(
2534                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
2535                ),
2536            ))
2537            .unwrap()
2538            .build();
2539
2540        assert_eq!(ta, expected_ta)
2541    }
2542
2543    #[test]
2544    fn test_from_evaluation_error_to_update_err() {
2545        let error = EvaluationError::AtomicNotFound(Variable::new("x"));
2546        let update_err = InvalidUpdateError::from(error);
2547        assert!(matches!(
2548            update_err,
2549            InvalidUpdateError::AdditionalVariable(_)
2550        ));
2551
2552        let error = EvaluationError::ParameterNotFound(Parameter::new("n"));
2553        let update_err = InvalidUpdateError::from(error);
2554        assert!(matches!(update_err, InvalidUpdateError::ParameterFound(_)));
2555    }
2556}