1use std::{collections::HashMap, error::Error, fmt::Display};
41
42pub(crate) mod classify_into_lia;
43mod remove_boolean_neg;
44mod remove_div;
45mod remove_minus;
46mod split_pair;
47
48use crate::{
49 ActionDefinition, RuleDefinition,
50 expressions::Location,
51 general_threshold_automaton::{GeneralThresholdAutomaton, Rule},
52};
53
54use super::{
55 ConstraintRewriteError, LIARule, LIAThresholdAutomaton, LIATransformationError,
56 LIAVariableConstraint,
57};
58
59impl Error for ConstraintRewriteError {}
60
61impl Display for ConstraintRewriteError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 ConstraintRewriteError::NotLinearArithmetic => write!(
65 f,
66 "Non linear integer arithmetic expression detected. Threshold automata only allow for linear arithmetic formula in their guards.",
67 ),
68 ConstraintRewriteError::ParameterConstraint(expr) => write!(
69 f,
70 "Found a constraint over the parameter evaluation instead of the variable valuation ! The constraint {expr} restricts the parameter values, which is only allowed in the resilience condition."
71 ),
72 }
73 }
74}
75
76impl TryFrom<GeneralThresholdAutomaton> for LIAThresholdAutomaton {
77 type Error = LIATransformationError;
78
79 fn try_from(ta: GeneralThresholdAutomaton) -> Result<Self, Self::Error> {
80 let init_variable_constr = ta
81 .initial_variable_conditions()
82 .map(|c| {
83 let c: LIAVariableConstraint = c.try_into().map_err(|err| {
84 LIATransformationError::InitialConstraintError(
85 Box::new(c.clone()),
86 Box::new(err),
87 )
88 })?;
89
90 Ok::<_, Self::Error>(c)
91 })
92 .collect::<Result<Vec<LIAVariableConstraint>, Self::Error>>()?;
93
94 let rules: HashMap<Location, Vec<_>> = ta
95 .get_rule_map()
96 .into_iter()
97 .map(|(loc, rules)| {
98 let rules: Vec<LIARule> = rules
99 .into_iter()
100 .map(|r| LIARule::try_from(&r))
101 .collect::<Result<Vec<LIARule>, LIATransformationError>>()?;
102
103 Ok::<(Location, Vec<LIARule>), LIATransformationError>((loc, rules))
104 })
105 .collect::<Result<HashMap<Location, Vec<_>>, LIATransformationError>>()?;
106
107 Ok(Self {
108 ta,
109 init_variable_constr,
110 rules,
111 additional_vars_for_sums: HashMap::new(),
112 })
113 }
114}
115
116impl TryFrom<&Rule> for LIARule {
117 type Error = LIATransformationError;
118
119 fn try_from(rule: &Rule) -> Result<Self, Self::Error> {
128 let guard = rule.guard().try_into().map_err(|err| {
129 LIATransformationError::GuardError(
130 Box::new(rule.clone()),
131 Box::new(rule.guard().clone()),
132 Box::new(err),
133 )
134 })?;
135 let actions = rule
136 .actions()
137 .filter(|ac| !ac.is_unchanged())
138 .cloned()
139 .collect();
140
141 Ok(Self {
142 id: rule.id(),
143 source: rule.source().clone(),
144 target: rule.target().clone(),
145 guard,
146 actions,
147 })
148 }
149}
150
151impl TryFrom<Rule> for LIARule {
152 type Error = LIATransformationError;
153
154 fn try_from(value: Rule) -> Result<Self, Self::Error> {
155 (&value).try_into()
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use std::collections::HashMap;
162
163 use crate::{
164 BooleanVarConstraint, LocationConstraint, ParameterConstraint,
165 expressions::{
166 BooleanConnective, ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter,
167 Variable, fraction::Fraction,
168 },
169 general_threshold_automaton::{
170 Action,
171 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
172 },
173 lia_threshold_automaton::{
174 LIARule, LIAThresholdAutomaton, LIATransformationError, LIAVariableConstraint,
175 SingleAtomConstraint,
176 integer_thresholds::{ThresholdCompOp, ThresholdConstraint},
177 },
178 };
179
180 #[test]
181 fn test_full_ta_lia_ta() {
182 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
183 .with_parameters(vec![
184 Parameter::new("n"),
185 Parameter::new("t"),
186 Parameter::new("f"),
187 ])
188 .unwrap()
189 .with_variables(vec![
190 Variable::new("var1"),
191 Variable::new("var2"),
192 Variable::new("var3"),
193 ])
194 .unwrap()
195 .with_locations(vec![
196 Location::new("loc1"),
197 Location::new("loc2"),
198 Location::new("loc3"),
199 ])
200 .unwrap()
201 .initialize()
202 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
203 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
204 ComparisonOp::Eq,
205 Box::new(IntegerExpression::Const(1)),
206 )])
207 .unwrap()
208 .with_initial_location_constraints(vec![
209 LocationConstraint::ComparisonExpression(
210 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
211 ComparisonOp::Eq,
212 Box::new(
213 IntegerExpression::Param(Parameter::new("n"))
214 - IntegerExpression::Param(Parameter::new("f")),
215 ),
216 ) | LocationConstraint::ComparisonExpression(
217 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
218 ComparisonOp::Eq,
219 Box::new(IntegerExpression::Const(0)),
220 ),
221 ])
222 .unwrap()
223 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
224 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
225 ComparisonOp::Gt,
226 Box::new(IntegerExpression::BinaryExpr(
227 Box::new(IntegerExpression::Const(3)),
228 IntegerOp::Mul,
229 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
230 )),
231 )])
232 .unwrap()
233 .with_rules(vec![
234 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
235 .with_actions(vec![
236 Action::new(
237 Variable::new("var1"),
238 IntegerExpression::Atom(Variable::new("var1")),
239 )
240 .unwrap(),
241 ])
242 .build(),
243 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
244 .with_guard(
245 BooleanVarConstraint::ComparisonExpression(
246 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
247 ComparisonOp::Eq,
248 Box::new(IntegerExpression::Const(1)),
249 ) & BooleanVarConstraint::ComparisonExpression(
250 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
251 ComparisonOp::Eq,
252 Box::new(IntegerExpression::Param(Parameter::new("n"))),
253 ),
254 )
255 .with_actions(vec![
256 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
257 Action::new(
258 Variable::new("var1"),
259 IntegerExpression::BinaryExpr(
260 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
261 IntegerOp::Add,
262 Box::new(IntegerExpression::Const(1)),
263 ),
264 )
265 .unwrap(),
266 ])
267 .build(),
268 ])
269 .unwrap()
270 .build();
271
272 let lia_ta = LIAThresholdAutomaton::try_from(ta.clone()).unwrap();
273
274 let expected_lia = LIAThresholdAutomaton {
275 ta: ta.clone(),
276 rules: HashMap::from([
277 (
278 Location::new("loc1"),
279 vec![LIARule {
280 id: 0,
281 source: Location::new("loc1"),
282 target: Location::new("loc2"),
283 guard: LIAVariableConstraint::True,
284 actions: vec![],
285 }],
286 ),
287 (
288 Location::new("loc2"),
289 vec![LIARule {
290 id: 1,
291 source: Location::new("loc2"),
292 target: Location::new("loc3"),
293 guard: LIAVariableConstraint::BinaryGuard(
294 Box::new(LIAVariableConstraint::BinaryGuard(
295 Box::new(LIAVariableConstraint::SingleVarConstraint(
296 SingleAtomConstraint::new(
297 Variable::new("var1"),
298 ThresholdConstraint::new(
299 ThresholdCompOp::Lt,
300 Vec::<(Parameter, Fraction)>::new(),
301 2,
302 ),
303 ),
304 )),
305 BooleanConnective::And,
306 Box::new(LIAVariableConstraint::SingleVarConstraint(
307 SingleAtomConstraint::new(
308 Variable::new("var1"),
309 ThresholdConstraint::new(
310 ThresholdCompOp::Geq,
311 Vec::<(Parameter, Fraction)>::new(),
312 1,
313 ),
314 ),
315 )),
316 )),
317 BooleanConnective::And,
318 Box::new(LIAVariableConstraint::BinaryGuard(
319 Box::new(LIAVariableConstraint::SingleVarConstraint(
320 SingleAtomConstraint::new(
321 Variable::new("var2"),
322 ThresholdConstraint::new(
323 ThresholdCompOp::Lt,
324 [(Parameter::new("n"), 1)],
325 1,
326 ),
327 ),
328 )),
329 BooleanConnective::And,
330 Box::new(LIAVariableConstraint::SingleVarConstraint(
331 SingleAtomConstraint::new(
332 Variable::new("var2"),
333 ThresholdConstraint::new(
334 ThresholdCompOp::Geq,
335 [(Parameter::new("n"), 1)],
336 0,
337 ),
338 ),
339 )),
340 )),
341 ),
342 actions: vec![
343 Action::new(Variable::new("var3"), IntegerExpression::Const(0))
344 .unwrap(),
345 Action::new(
346 Variable::new("var1"),
347 IntegerExpression::BinaryExpr(
348 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
349 IntegerOp::Add,
350 Box::new(IntegerExpression::Const(1)),
351 ),
352 )
353 .unwrap(),
354 ],
355 }],
356 ),
357 ]),
358 init_variable_constr: vec![LIAVariableConstraint::BinaryGuard(
359 Box::new(LIAVariableConstraint::SingleVarConstraint(
360 SingleAtomConstraint::new(
361 Variable::new("var1"),
362 ThresholdConstraint::new(
363 ThresholdCompOp::Lt,
364 Vec::<(Parameter, Fraction)>::new(),
365 2,
366 ),
367 ),
368 )),
369 BooleanConnective::And,
370 Box::new(LIAVariableConstraint::SingleVarConstraint(
371 SingleAtomConstraint::new(
372 Variable::new("var1"),
373 ThresholdConstraint::new(
374 ThresholdCompOp::Geq,
375 Vec::<(Parameter, Fraction)>::new(),
376 1,
377 ),
378 ),
379 )),
380 )],
381 additional_vars_for_sums: HashMap::new(),
382 };
383
384 assert_eq!(lia_ta, expected_lia);
385 }
386
387 #[test]
388 fn test_error_on_non_lia() {
389 let violating_rule = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
390 .with_guard(
391 BooleanVarConstraint::ComparisonExpression(
392 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
393 ComparisonOp::Eq,
394 Box::new(IntegerExpression::Const(1)),
395 ) & BooleanVarConstraint::ComparisonExpression(
396 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
397 ComparisonOp::Eq,
398 Box::new(
399 IntegerExpression::Param(Parameter::new("n"))
400 * IntegerExpression::Atom(Variable::new("var2")),
401 ),
402 ),
403 )
404 .with_actions(vec![
405 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
406 Action::new(
407 Variable::new("var1"),
408 IntegerExpression::BinaryExpr(
409 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
410 IntegerOp::Add,
411 Box::new(IntegerExpression::Const(1)),
412 ),
413 )
414 .unwrap(),
415 ])
416 .build();
417
418 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
419 .with_parameters(vec![
420 Parameter::new("n"),
421 Parameter::new("t"),
422 Parameter::new("f"),
423 ])
424 .unwrap()
425 .with_variables(vec![
426 Variable::new("var1"),
427 Variable::new("var2"),
428 Variable::new("var3"),
429 ])
430 .unwrap()
431 .with_locations(vec![
432 Location::new("loc1"),
433 Location::new("loc2"),
434 Location::new("loc3"),
435 ])
436 .unwrap()
437 .initialize()
438 .with_initial_location_constraints(vec![
439 LocationConstraint::ComparisonExpression(
440 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
441 ComparisonOp::Eq,
442 Box::new(
443 IntegerExpression::Param(Parameter::new("n"))
444 - IntegerExpression::Param(Parameter::new("f")),
445 ),
446 ) | LocationConstraint::ComparisonExpression(
447 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
448 ComparisonOp::Eq,
449 Box::new(IntegerExpression::Const(0)),
450 ),
451 ])
452 .unwrap()
453 .with_rules(vec![
454 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
455 .with_actions(vec![
456 Action::new(
457 Variable::new("var1"),
458 IntegerExpression::Atom(Variable::new("var1")),
459 )
460 .unwrap(),
461 ])
462 .build(),
463 violating_rule.clone(),
464 ])
465 .unwrap()
466 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
467 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
468 ComparisonOp::Gt,
469 Box::new(IntegerExpression::BinaryExpr(
470 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
471 IntegerOp::Mul,
472 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
473 )),
474 )])
475 .unwrap()
476 .build();
477 let lia_ta = LIAThresholdAutomaton::try_from(ta.clone());
478 assert!(lia_ta.is_err());
479
480 let err = lia_ta.unwrap_err();
481 assert!(matches!(
482 err.clone(),
483 LIATransformationError::GuardError(_, _, _)
484 ));
485 assert!(err.to_string().contains(&violating_rule.to_string()))
486 }
487}