1use 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#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct GeneralThresholdAutomatonBuilder {
78 ta: GeneralThresholdAutomaton,
79}
80
81impl GeneralThresholdAutomatonBuilder {
82 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 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 pub fn with_parameter(mut self, param: Parameter) -> Result<Self, BuilderError> {
122 if self.ta.parameters.contains(¶m) {
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 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 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 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 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 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 pub fn initialize(self) -> InitializedGeneralThresholdAutomatonBuilder {
298 InitializedGeneralThresholdAutomatonBuilder { ta: self.ta }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
336pub struct InitializedGeneralThresholdAutomatonBuilder {
337 ta: GeneralThresholdAutomaton,
338}
339
340impl InitializedGeneralThresholdAutomatonBuilder {
341 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[inline(always)]
867 pub fn has_parameter(&self, param: &Parameter) -> bool {
868 self.ta.parameters.contains(param)
869 }
870
871 #[inline(always)]
886 pub fn has_variable(&self, var: &Variable) -> bool {
887 self.ta.variables.contains(var)
888 }
889
890 #[inline(always)]
908 pub fn has_location(&self, loc: &Location) -> bool {
909 self.ta.locations.contains(loc)
910 }
911
912 pub fn locations(&self) -> impl Iterator<Item = &Location> {
933 self.ta.locations()
934 }
935
936 pub fn variables(&self) -> impl Iterator<Item = &Variable> {
957 self.ta.variables()
958 }
959
960 pub fn parameters(&self) -> impl Iterator<Item = &Parameter> {
981 self.ta.parameters()
982 }
983
984 #[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#[derive(Debug, PartialEq, Eq)]
1011pub struct RuleBuilder {
1012 rule: Rule,
1013}
1014
1015impl RuleBuilder {
1016 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 #[inline(always)]
1031 pub fn with_guard(mut self, guard: BooleanExpression<Variable>) -> Self {
1032 self.rule.guard = guard;
1033 self
1034 }
1035
1036 pub fn with_action(mut self, action: Action) -> Self {
1038 self.rule.actions.push(action);
1039 self
1040 }
1041
1042 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 pub fn build(self) -> Rule {
1052 self.rule
1053 }
1054}
1055
1056impl Action {
1057 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 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 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 if n_var.unwrap() > 1 {
1115 return Err(ActionBuilderError::UpdateExpressionMalformed(
1116 var.clone(),
1117 update_expr.clone(),
1118 InvalidUpdateError::MultipleOccurrences,
1119 ));
1120 }
1121
1122 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 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 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 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#[derive(Debug, Clone)]
1211pub enum BuilderError {
1212 DuplicateParameter(Parameter),
1214 DuplicateVariable(Variable),
1216 DuplicateLocation(Location),
1218 DuplicateRuleId(Box<Rule>, Box<Rule>),
1220 MalformedRule(Box<Rule>, String),
1222 MalformedAction(Box<Action>, String),
1224 MalformedResilienceCondition(Box<ParameterConstraint>, String),
1226 MalformedInitialLocationConstraint(Box<LocationConstraint>, String),
1228 MalformedInitialVariableConstraint(Box<BooleanVarConstraint>, String),
1230 NameClash(String),
1232 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#[derive(Debug, Clone)]
1276pub enum ActionBuilderError {
1277 UpdateExpressionMalformed(Variable, IntegerExpression<Variable>, InvalidUpdateError),
1279}
1280
1281#[derive(Debug, Clone)]
1283pub enum InvalidUpdateError {
1284 ConstantUpdateNonZero(i64),
1286 AdditionalVariable(Variable),
1288 ParameterFound(Parameter),
1290 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(¶m));
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}