1use core::fmt;
6use std::{error, fmt::Display};
7
8use eltl::ELTLExpression;
9use log::trace;
10use taco_bdd::{BDDManager, BDDManagerConfig};
11use taco_smt_encoder::{
12 ProvidesSMTSolverBuilder, SMTSolverBuilder, SMTSolverBuilderCfg, SMTSolverBuilderError,
13};
14use taco_threshold_automaton::{
15 ModifiableThresholdAutomaton, ThresholdAutomaton,
16 expressions::Location,
17 general_threshold_automaton::GeneralThresholdAutomaton,
18 lia_threshold_automaton::{
19 LIAThresholdAutomaton, LIATransformationError, LIAVariableConstraint,
20 },
21 path::Path,
22};
23
24use crate::preprocessing::Preprocessor;
25
26pub mod eltl;
27pub mod preprocessing;
28pub mod reachability_specification;
29
30pub trait ModelChecker: Sized {
33 type ModelCheckerContext: ModelCheckerContext;
36
37 type ModelCheckerOptions;
39
40 type SpecType: SpecificationTrait<Self::ModelCheckerContext>;
42 type ThresholdAutomatonType: TATrait<
45 Self::ModelCheckerContext,
46 <Self::SpecType as SpecificationTrait<Self::ModelCheckerContext>>::InternalSpecType,
47 >;
48
49 type InitializationError: error::Error;
52 type ModelCheckingError: error::Error;
54
55 #[allow(clippy::type_complexity)]
63 fn initialize(
64 opts: Self::ModelCheckerOptions,
65 ta_spec: Vec<
66 (
67 <<Self as ModelChecker>::SpecType as SpecificationTrait<
68 Self::ModelCheckerContext,
69 >>::InternalSpecType,
70 Vec<Self::ThresholdAutomatonType>,
71 ),
72 >,
73 ctx: Self::ModelCheckerContext,
74 ) -> Result<Self, Self::InitializationError>;
75
76 #[allow(clippy::type_complexity)]
91 fn new(
92 ctx_opts: Option<
93 <<Self as ModelChecker>::ModelCheckerContext as ModelCheckerContext>::ContextOptions,
94 >,
95 mc_opts: Self::ModelCheckerOptions,
96 preprocessors: Vec<Box<dyn Preprocessor<GeneralThresholdAutomaton, <Self::SpecType as SpecificationTrait<Self::ModelCheckerContext>>::InternalSpecType, Self::ModelCheckerContext>>>,
97 ta: GeneralThresholdAutomaton,
98 spec: impl Iterator<Item = (String, ELTLExpression)>,
99 ) -> Result<
100 Self,
101 ModelCheckerSetupError<
102 <<Self as ModelChecker>::ThresholdAutomatonType as TATrait<
103 Self::ModelCheckerContext, <Self::SpecType as SpecificationTrait<Self::ModelCheckerContext>>::InternalSpecType,
104 >>::TransformationError,
105 <<Self as ModelChecker>::SpecType as SpecificationTrait<
106 Self::ModelCheckerContext,
107 >>::TransformationError,
108 <<Self as ModelChecker>::ModelCheckerContext as ModelCheckerContext>::CreationError,
109 Self::InitializationError,
110 >,
111 >{
112 let ctx = Self::ModelCheckerContext::try_new(ctx_opts);
113 if let Err(ctx_err) = ctx {
114 return Err(ModelCheckerSetupError::ErrorContextSetup(ctx_err));
115 }
116 let ctx = ctx.unwrap();
117
118 let spec = Self::SpecType::try_from_eltl(spec, &ctx);
119 if let Err(spec_err) = spec {
120 return Err(ModelCheckerSetupError::ErrorTransformingSpec(spec_err));
121 }
122 let spec = spec.unwrap();
123
124 let ta_spec = Self::SpecType::transform_threshold_automaton(ta, spec, &ctx);
126
127 let ta_spec = ta_spec
128 .into_iter()
129 .map(|(spec, mut ta)| {
130 for processor in preprocessors.iter() {
132 processor.process(&mut ta, &spec, &ctx);
133 }
134
135 trace!("Threshold automaton for property {spec} after preprocessing: {ta}");
136
137 let ta = Self::ThresholdAutomatonType::try_from_general_ta(ta, &ctx, &spec)?;
138
139 Ok((spec, ta))
140 })
141 .collect::<Result<Vec<_>, _>>();
142 if let Err(ta_err) = ta_spec {
143 return Err(ModelCheckerSetupError::ErrorTransformingTA(ta_err));
144 }
145 let ta_spec = ta_spec.unwrap();
146
147 let mc = Self::initialize(mc_opts, ta_spec, ctx.clone());
148 if let Err(mc_err) = mc {
149 return Err(ModelCheckerSetupError::ErrorInitializingModelChecker(
150 mc_err,
151 ));
152 }
153
154 Ok(mc.unwrap())
155 }
156
157 fn verify(
163 self,
164 abort_on_violation: bool,
165 ) -> Result<ModelCheckerResult, Self::ModelCheckingError>;
166}
167
168#[derive(Debug, Clone, PartialEq)]
170pub enum ModelCheckerSetupError<
171 TE: error::Error,
172 SE: error::Error,
173 CE: error::Error,
174 IE: error::Error,
175> {
176 ErrorTransformingTA(TE),
179 ErrorTransformingSpec(SE),
182 ErrorContextSetup(CE),
184 ErrorInitializingModelChecker(IE),
186}
187
188impl<TE, SE, CE, IE> fmt::Display for ModelCheckerSetupError<TE, SE, CE, IE>
189where
190 TE: error::Error,
191 SE: error::Error,
192 CE: error::Error,
193 IE: error::Error,
194{
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 ModelCheckerSetupError::ErrorTransformingTA(e) => write!(
198 f,
199 "Failed to transform threshold automaton into required form for the model checker. Error: {e}"
200 ),
201 ModelCheckerSetupError::ErrorTransformingSpec(e) => write!(
202 f,
203 "Failed to transform specification into the required form for model checking. Error: {e}"
204 ),
205 ModelCheckerSetupError::ErrorContextSetup(e) => {
206 write!(f, "Failed to setup model checking context. Error: {e}")
207 }
208 ModelCheckerSetupError::ErrorInitializingModelChecker(e) => {
209 write!(f, "Failed to initialize the model checker. Error: {e}")
210 }
211 }
212 }
213}
214
215impl<TE, SE, CE, IE> error::Error for ModelCheckerSetupError<TE, SE, CE, IE>
216where
217 TE: error::Error,
218 SE: error::Error,
219 CE: error::Error,
220 IE: error::Error,
221{
222}
223
224#[derive(Debug, Clone, PartialEq)]
226pub enum ModelCheckerResult {
227 SAFE,
229 UNSAFE(Vec<(String, Box<Path>)>),
234 UNKNOWN(Vec<String>),
239}
240
241impl ModelCheckerResult {
242 pub fn is_safe(&self) -> bool {
244 matches!(self, ModelCheckerResult::SAFE)
245 }
246}
247
248pub trait SpecificationTrait<C: ModelCheckerContext>: Sized + fmt::Debug {
251 type TransformationError: error::Error + Sized;
253 type InternalSpecType: Sized + TargetSpec;
255
256 fn try_from_eltl(
258 spec: impl Iterator<Item = (String, ELTLExpression)>,
259 ctx: &C,
260 ) -> Result<Vec<Self>, Self::TransformationError>;
261
262 fn transform_threshold_automaton<TA: ThresholdAutomaton + ModifiableThresholdAutomaton>(
266 ta: TA,
267 specs: Vec<Self>,
268 ctx: &C,
269 ) -> Vec<(Self::InternalSpecType, TA)>;
270}
271
272pub trait TargetSpec: Display {
278 fn get_locations_in_target(&self) -> impl IntoIterator<Item = &Location>;
283
284 fn get_variable_constraint(&self) -> impl IntoIterator<Item = &LIAVariableConstraint>;
289}
290
291pub trait TATrait<MC: ModelCheckerContext, SC>: ThresholdAutomaton + Sized + fmt::Debug {
294 type TransformationError: error::Error;
296
297 fn try_from_general_ta(
300 ta: GeneralThresholdAutomaton,
301 ctx: &MC,
302 spec_ctx: &SC,
303 ) -> Result<Vec<Self>, Self::TransformationError>;
304}
305
306pub trait ModelCheckerContext: Sized + Clone + fmt::Debug {
311 type CreationError: error::Error;
313 type ContextOptions;
315
316 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError>;
320}
321
322impl ModelCheckerContext for SMTSolverBuilder {
324 type CreationError = SMTSolverBuilderError;
325 type ContextOptions = SMTSolverBuilderCfg;
326
327 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
328 if let Some(cfg) = opt {
329 return SMTSolverBuilder::new(&cfg);
330 }
331 SMTSolverBuilder::new_automatic_selection()
332 }
333}
334
335#[derive(Clone, Debug)]
337pub struct SMTBddContext {
338 smt_solver_builder: SMTSolverBuilder,
340 bdd_manager: BDDManager,
342}
343impl SMTBddContext {
344 pub fn smt_solver_builder(&self) -> &SMTSolverBuilder {
346 &self.smt_solver_builder
347 }
348 pub fn bdd_manager(&self) -> &BDDManager {
350 &self.bdd_manager
351 }
352}
353
354impl ProvidesSMTSolverBuilder for SMTBddContext {
355 fn get_solver_builder(&self) -> SMTSolverBuilder {
356 self.smt_solver_builder.clone()
357 }
358}
359
360impl ModelCheckerContext for SMTBddContext {
361 type CreationError = SMTSolverBuilderError;
362
363 type ContextOptions = (Option<SMTSolverBuilderCfg>, Option<BDDManagerConfig>);
364
365 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
366 let (smt_cfg, bdd_cfg) = opt.unwrap_or((None, None));
367
368 let bdd_mgr = bdd_cfg
369 .map(|cfg| cfg.mgr_from_config())
370 .unwrap_or_else(BDDManager::default);
371
372 let smt_solver_builder = smt_cfg
373 .map(|cfg| SMTSolverBuilder::new(&cfg))
374 .unwrap_or_else(SMTSolverBuilder::new_automatic_selection)?;
375
376 Ok(Self {
377 smt_solver_builder,
378 bdd_manager: bdd_mgr,
379 })
380 }
381}
382
383impl<C: ModelCheckerContext, SC> TATrait<C, SC> for GeneralThresholdAutomaton {
384 type TransformationError = DummyError;
385
386 fn try_from_general_ta(
387 ta: GeneralThresholdAutomaton,
388 _ctx: &C,
389 _spec_ctx: &SC,
390 ) -> Result<Vec<Self>, Self::TransformationError> {
391 Ok(vec![ta])
392 }
393}
394
395impl<C: ModelCheckerContext, SC> TATrait<C, SC> for LIAThresholdAutomaton {
396 type TransformationError = LIATransformationError;
397
398 fn try_from_general_ta(
399 ta: GeneralThresholdAutomaton,
400 _ctx: &C,
401 _spec_ctx: &SC,
402 ) -> Result<Vec<Self>, Self::TransformationError> {
403 let lta = LIAThresholdAutomaton::try_from(ta)?;
404
405 Ok(vec![lta])
406 }
407}
408
409#[derive(Debug)]
411pub struct DummyError {}
412
413impl fmt::Display for DummyError {
414 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 unreachable!("This error should never have been built")
416 }
417}
418
419impl error::Error for DummyError {}
420
421#[cfg(test)]
422mod tests {
423 use taco_smt_encoder::{SMTSolverBuilder, SMTSolverBuilderCfg};
424 use taco_threshold_automaton::{
425 BooleanVarConstraint, LocationConstraint, ParameterConstraint,
426 expressions::{ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter, Variable},
427 general_threshold_automaton::{
428 Action, GeneralThresholdAutomaton,
429 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
430 },
431 lia_threshold_automaton::LIAThresholdAutomaton,
432 };
433
434 use crate::TATrait;
435
436 #[test]
437 fn test_try_from_for_general_gta() {
438 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
439 .with_parameters(vec![
440 Parameter::new("n"),
441 Parameter::new("t"),
442 Parameter::new("f"),
443 ])
444 .unwrap()
445 .with_variables(vec![
446 Variable::new("var1"),
447 Variable::new("var2"),
448 Variable::new("var3"),
449 ])
450 .unwrap()
451 .with_locations(vec![
452 Location::new("loc1"),
453 Location::new("loc2"),
454 Location::new("loc3"),
455 ])
456 .unwrap()
457 .initialize()
458 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
459 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
460 ComparisonOp::Eq,
461 Box::new(IntegerExpression::Const(1)),
462 )])
463 .unwrap()
464 .with_initial_location_constraints(vec![
465 LocationConstraint::ComparisonExpression(
466 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
467 ComparisonOp::Eq,
468 Box::new(
469 IntegerExpression::Param(Parameter::new("n"))
470 - IntegerExpression::Param(Parameter::new("f")),
471 ),
472 ) | LocationConstraint::ComparisonExpression(
473 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
474 ComparisonOp::Eq,
475 Box::new(IntegerExpression::Const(0)),
476 ),
477 ])
478 .unwrap()
479 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
480 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
481 ComparisonOp::Gt,
482 Box::new(IntegerExpression::BinaryExpr(
483 Box::new(IntegerExpression::Const(3)),
484 IntegerOp::Mul,
485 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
486 )),
487 )])
488 .unwrap()
489 .with_rules(vec![
490 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
491 .with_actions(vec![
492 Action::new(
493 Variable::new("var1"),
494 IntegerExpression::Atom(Variable::new("var1")),
495 )
496 .unwrap(),
497 ])
498 .build(),
499 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
500 .with_guard(
501 BooleanVarConstraint::ComparisonExpression(
502 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
503 ComparisonOp::Eq,
504 Box::new(IntegerExpression::Const(1)),
505 ) & BooleanVarConstraint::ComparisonExpression(
506 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
507 ComparisonOp::Eq,
508 Box::new(IntegerExpression::Param(Parameter::new("n"))),
509 ),
510 )
511 .with_actions(vec![
512 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
513 Action::new(
514 Variable::new("var1"),
515 IntegerExpression::BinaryExpr(
516 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
517 IntegerOp::Add,
518 Box::new(IntegerExpression::Const(1)),
519 ),
520 )
521 .unwrap(),
522 ])
523 .build(),
524 ])
525 .unwrap()
526 .build();
527
528 let ctx = SMTSolverBuilder::new(&SMTSolverBuilderCfg::new_z3()).unwrap();
529 let got_ta = GeneralThresholdAutomaton::try_from_general_ta(ta.clone(), &ctx, &()).unwrap();
530
531 assert_eq!(got_ta, vec![ta])
532 }
533
534 #[test]
535 fn test_try_from_for_general_lta() {
536 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
537 .with_parameters(vec![
538 Parameter::new("n"),
539 Parameter::new("t"),
540 Parameter::new("f"),
541 ])
542 .unwrap()
543 .with_variables(vec![
544 Variable::new("var1"),
545 Variable::new("var2"),
546 Variable::new("var3"),
547 ])
548 .unwrap()
549 .with_locations(vec![
550 Location::new("loc1"),
551 Location::new("loc2"),
552 Location::new("loc3"),
553 ])
554 .unwrap()
555 .initialize()
556 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
557 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
558 ComparisonOp::Eq,
559 Box::new(IntegerExpression::Const(1)),
560 )])
561 .unwrap()
562 .with_initial_location_constraints(vec![
563 LocationConstraint::ComparisonExpression(
564 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
565 ComparisonOp::Eq,
566 Box::new(
567 IntegerExpression::Param(Parameter::new("n"))
568 - IntegerExpression::Param(Parameter::new("f")),
569 ),
570 ) | LocationConstraint::ComparisonExpression(
571 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
572 ComparisonOp::Eq,
573 Box::new(IntegerExpression::Const(0)),
574 ),
575 ])
576 .unwrap()
577 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
578 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
579 ComparisonOp::Gt,
580 Box::new(IntegerExpression::BinaryExpr(
581 Box::new(IntegerExpression::Const(3)),
582 IntegerOp::Mul,
583 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
584 )),
585 )])
586 .unwrap()
587 .with_rules(vec![
588 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
589 .with_actions(vec![
590 Action::new(
591 Variable::new("var1"),
592 IntegerExpression::Atom(Variable::new("var1")),
593 )
594 .unwrap(),
595 ])
596 .build(),
597 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
598 .with_guard(
599 BooleanVarConstraint::ComparisonExpression(
600 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
601 ComparisonOp::Eq,
602 Box::new(IntegerExpression::Const(1)),
603 ) & BooleanVarConstraint::ComparisonExpression(
604 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
605 ComparisonOp::Eq,
606 Box::new(IntegerExpression::Param(Parameter::new("n"))),
607 ),
608 )
609 .with_actions(vec![
610 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
611 Action::new(
612 Variable::new("var1"),
613 IntegerExpression::BinaryExpr(
614 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
615 IntegerOp::Add,
616 Box::new(IntegerExpression::Const(1)),
617 ),
618 )
619 .unwrap(),
620 ])
621 .build(),
622 ])
623 .unwrap()
624 .build();
625
626 let ctx = SMTSolverBuilder::new(&SMTSolverBuilderCfg::new_z3()).unwrap();
627 let got_ta = LIAThresholdAutomaton::try_from_general_ta(ta.clone(), &ctx, &()).unwrap();
628
629 let lta = ta.try_into().unwrap();
630
631 assert_eq!(got_ta, vec![lta])
632 }
633}