1use core::fmt;
6use std::{convert::Infallible, 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 internal_spec;
28pub mod preprocessing;
29
30pub type ContextOf<M> = <M as ModelChecker>::ModelCheckerContext;
32
33pub type InternalSpecOf<M> =
35 <<M as ModelChecker>::SpecType as SpecificationTrait<ContextOf<M>>>::InternalSpecType;
36
37pub type TAErrorOf<M> = <<M as ModelChecker>::ThresholdAutomatonType as TATrait<
40 ContextOf<M>,
41 InternalSpecOf<M>,
42>>::TransformationError;
43
44pub type SpecErrorOf<M> =
47 <<M as ModelChecker>::SpecType as SpecificationTrait<ContextOf<M>>>::TransformationError;
48
49pub type ContextErrorOf<M> = <ContextOf<M> as ModelCheckerContext>::CreationError;
51
52pub type ContextOptionsOf<M> = <ContextOf<M> as ModelCheckerContext>::ContextOptions;
54
55pub type PreprocessorOf<M> =
58 Box<dyn Preprocessor<GeneralThresholdAutomaton, InternalSpecOf<M>, ContextOf<M>>>;
59
60pub type TASpecOf<M> = Vec<(
63 String,
64 InternalSpecOf<M>,
65 Vec<<M as ModelChecker>::ThresholdAutomatonType>,
66)>;
67
68pub trait ModelChecker: Sized {
71 type ModelCheckerContext: ModelCheckerContext;
74
75 type ModelCheckerOptions;
77
78 type SpecType: SpecificationTrait<Self::ModelCheckerContext>;
80 type ThresholdAutomatonType: TATrait<ContextOf<Self>, InternalSpecOf<Self>>;
83
84 type InitializationError: error::Error;
87 type ModelCheckingError: error::Error;
89
90 fn initialize(
102 opts: Self::ModelCheckerOptions,
103 ta_spec: TASpecOf<Self>,
104 unknown: Vec<String>,
105 ctx: Self::ModelCheckerContext,
106 ) -> Result<Self, Self::InitializationError>;
107
108 fn new(
123 ctx_opts: Option<ContextOptionsOf<Self>>,
124 mc_opts: Self::ModelCheckerOptions,
125 preprocessors: Vec<PreprocessorOf<Self>>,
126 ta: GeneralThresholdAutomaton,
127 spec: impl Iterator<Item = (String, ELTLExpression)>,
128 ) -> Result<Self, ModelCheckerSetupError<Self>> {
129 let ctx = Self::ModelCheckerContext::try_new(ctx_opts);
130 if let Err(ctx_err) = ctx {
131 return Err(ModelCheckerSetupError::ErrorContextSetup(ctx_err));
132 }
133 let ctx = ctx.unwrap();
134
135 let spec = Self::SpecType::try_from_eltl(spec, &ctx);
136 if let Err(spec_err) = spec {
137 return Err(ModelCheckerSetupError::ErrorTransformingSpec(spec_err));
138 }
139 let (spec, unknown) = spec.unwrap();
140
141 let ta_spec = Self::SpecType::transform_threshold_automaton(ta, spec, &ctx);
143
144 let ta_spec = ta_spec
145 .into_iter()
146 .map(|(name, spec, mut ta)| {
147 for processor in preprocessors.iter() {
149 processor.process(&mut ta, &spec, &ctx);
150 }
151
152 trace!("Threshold automaton for property {spec} after preprocessing: {ta}");
153
154 let ta = Self::ThresholdAutomatonType::try_from_general_ta(ta, &ctx, &spec)?;
155
156 Ok((name, spec, ta))
157 })
158 .collect::<Result<Vec<_>, _>>();
159 if let Err(ta_err) = ta_spec {
160 return Err(ModelCheckerSetupError::ErrorTransformingTA(ta_err));
161 }
162 let ta_spec = ta_spec.unwrap();
163
164 let mc = Self::initialize(mc_opts, ta_spec, unknown, ctx.clone());
165 if let Err(mc_err) = mc {
166 return Err(ModelCheckerSetupError::ErrorInitializingModelChecker(
167 mc_err,
168 ));
169 }
170
171 Ok(mc.unwrap())
172 }
173
174 fn verify(
180 self,
181 abort_on_violation: bool,
182 ) -> Result<ModelCheckerResult, Self::ModelCheckingError>;
183}
184
185#[derive(Debug)]
190pub enum ModelCheckerSetupError<M: ModelChecker> {
191 ErrorTransformingTA(TAErrorOf<M>),
194 ErrorTransformingSpec(SpecErrorOf<M>),
197 ErrorContextSetup(ContextErrorOf<M>),
199 ErrorInitializingModelChecker(M::InitializationError),
201}
202
203impl<M: ModelChecker> fmt::Display for ModelCheckerSetupError<M> {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 ModelCheckerSetupError::ErrorTransformingTA(e) => write!(
207 f,
208 "Failed to transform threshold automaton into required form for the model checker. Error: {e}"
209 ),
210 ModelCheckerSetupError::ErrorTransformingSpec(e) => write!(
211 f,
212 "Failed to transform specification into the required form for model checking. Error: {e}"
213 ),
214 ModelCheckerSetupError::ErrorContextSetup(e) => {
215 write!(f, "Failed to setup model checking context. Error: {e}")
216 }
217 ModelCheckerSetupError::ErrorInitializingModelChecker(e) => {
218 write!(f, "Failed to initialize the model checker. Error: {e}")
219 }
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq)]
226pub enum ModelCheckerResult {
227 SAFE,
229 UNSAFE {
231 violations: Vec<(String, Box<Path>)>,
234 unknown: Vec<String>,
237 },
238 UNKNOWN(Vec<String>),
241}
242
243impl ModelCheckerResult {
244 pub fn is_safe(&self) -> bool {
246 matches!(self, ModelCheckerResult::SAFE)
247 }
248}
249
250pub trait SpecificationTrait<C: ModelCheckerContext>: Sized + fmt::Debug {
253 type TransformationError: error::Error + Sized;
255 type InternalSpecType: Sized + TASpecification;
257
258 fn try_from_eltl(
263 spec: impl Iterator<Item = (String, ELTLExpression)>,
264 ctx: &C,
265 ) -> Result<(Vec<Self>, Vec<String>), Self::TransformationError>;
266
267 fn transform_threshold_automaton<TA: ThresholdAutomaton + ModifiableThresholdAutomaton>(
272 ta: TA,
273 specs: Vec<Self>,
274 ctx: &C,
275 ) -> Vec<(String, Self::InternalSpecType, TA)>;
276}
277
278pub trait TASpecification: Display {
284 fn locs_appearing(&self) -> impl IntoIterator<Item = &Location>;
289
290 fn var_constraint(&self) -> impl IntoIterator<Item = &LIAVariableConstraint>;
295}
296
297pub trait TATrait<MC: ModelCheckerContext, SC>: ThresholdAutomaton + Sized + fmt::Debug {
300 type TransformationError: error::Error;
302
303 fn try_from_general_ta(
306 ta: GeneralThresholdAutomaton,
307 ctx: &MC,
308 spec_ctx: &SC,
309 ) -> Result<Vec<Self>, Self::TransformationError>;
310}
311
312pub trait ModelCheckerContext: Sized + Clone + fmt::Debug {
317 type CreationError: error::Error;
319 type ContextOptions;
321
322 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError>;
326}
327
328impl ModelCheckerContext for SMTSolverBuilder {
330 type CreationError = SMTSolverBuilderError;
331 type ContextOptions = SMTSolverBuilderCfg;
332
333 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
334 if let Some(cfg) = opt {
335 return SMTSolverBuilder::new(&cfg);
336 }
337 SMTSolverBuilder::new_automatic_selection()
338 }
339}
340
341#[derive(Clone, Debug)]
343pub struct SMTBddContext {
344 smt_solver_builder: SMTSolverBuilder,
346 bdd_manager: BDDManager,
348}
349impl SMTBddContext {
350 pub fn smt_solver_builder(&self) -> &SMTSolverBuilder {
352 &self.smt_solver_builder
353 }
354 pub fn bdd_manager(&self) -> &BDDManager {
356 &self.bdd_manager
357 }
358}
359
360impl ProvidesSMTSolverBuilder for SMTBddContext {
361 fn get_solver_builder(&self) -> SMTSolverBuilder {
362 self.smt_solver_builder.clone()
363 }
364}
365
366impl ModelCheckerContext for SMTBddContext {
367 type CreationError = SMTSolverBuilderError;
368
369 type ContextOptions = (Option<SMTSolverBuilderCfg>, Option<BDDManagerConfig>);
370
371 fn try_new(opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
372 let (smt_cfg, bdd_cfg) = opt.unwrap_or((None, None));
373
374 let bdd_mgr = bdd_cfg
375 .map(|cfg| cfg.mgr_from_config())
376 .unwrap_or_else(BDDManager::default);
377
378 let smt_solver_builder = smt_cfg
379 .map(|cfg| SMTSolverBuilder::new(&cfg))
380 .unwrap_or_else(SMTSolverBuilder::new_automatic_selection)?;
381
382 Ok(Self {
383 smt_solver_builder,
384 bdd_manager: bdd_mgr,
385 })
386 }
387}
388
389impl<C: ModelCheckerContext, SC> TATrait<C, SC> for GeneralThresholdAutomaton {
390 type TransformationError = Infallible;
391
392 fn try_from_general_ta(
393 ta: GeneralThresholdAutomaton,
394 _ctx: &C,
395 _spec_ctx: &SC,
396 ) -> Result<Vec<Self>, Self::TransformationError> {
397 Ok(vec![ta])
398 }
399}
400
401impl<C: ModelCheckerContext, SC> TATrait<C, SC> for LIAThresholdAutomaton {
402 type TransformationError = LIATransformationError;
403
404 fn try_from_general_ta(
405 ta: GeneralThresholdAutomaton,
406 _ctx: &C,
407 _spec_ctx: &SC,
408 ) -> Result<Vec<Self>, Self::TransformationError> {
409 let lta = LIAThresholdAutomaton::try_from(ta)?;
410
411 Ok(vec![lta])
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use taco_smt_encoder::{SMTSolverBuilder, SMTSolverBuilderCfg};
418 use taco_threshold_automaton::{
419 BooleanVarConstraint, LocationConstraint, ParameterConstraint,
420 expressions::{ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter, Variable},
421 general_threshold_automaton::{
422 Action, GeneralThresholdAutomaton,
423 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
424 },
425 lia_threshold_automaton::LIAThresholdAutomaton,
426 };
427
428 use crate::TATrait;
429
430 #[test]
431 fn test_try_from_for_general_gta() {
432 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
433 .with_parameters(vec![
434 Parameter::new("n"),
435 Parameter::new("t"),
436 Parameter::new("f"),
437 ])
438 .unwrap()
439 .with_variables(vec![
440 Variable::new("var1"),
441 Variable::new("var2"),
442 Variable::new("var3"),
443 ])
444 .unwrap()
445 .with_locations(vec![
446 Location::new("loc1"),
447 Location::new("loc2"),
448 Location::new("loc3"),
449 ])
450 .unwrap()
451 .initialize()
452 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
453 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
454 ComparisonOp::Eq,
455 Box::new(IntegerExpression::Const(1)),
456 )])
457 .unwrap()
458 .with_initial_location_constraints(vec![
459 LocationConstraint::ComparisonExpression(
460 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
461 ComparisonOp::Eq,
462 Box::new(
463 IntegerExpression::Param(Parameter::new("n"))
464 - IntegerExpression::Param(Parameter::new("f")),
465 ),
466 ) | LocationConstraint::ComparisonExpression(
467 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
468 ComparisonOp::Eq,
469 Box::new(IntegerExpression::Const(0)),
470 ),
471 ])
472 .unwrap()
473 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
474 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
475 ComparisonOp::Gt,
476 Box::new(IntegerExpression::BinaryExpr(
477 Box::new(IntegerExpression::Const(3)),
478 IntegerOp::Mul,
479 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
480 )),
481 )])
482 .unwrap()
483 .with_rules(vec![
484 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
485 .with_actions(vec![
486 Action::new(
487 Variable::new("var1"),
488 IntegerExpression::Atom(Variable::new("var1")),
489 )
490 .unwrap(),
491 ])
492 .build(),
493 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
494 .with_guard(
495 BooleanVarConstraint::ComparisonExpression(
496 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
497 ComparisonOp::Eq,
498 Box::new(IntegerExpression::Const(1)),
499 ) & BooleanVarConstraint::ComparisonExpression(
500 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
501 ComparisonOp::Eq,
502 Box::new(IntegerExpression::Param(Parameter::new("n"))),
503 ),
504 )
505 .with_actions(vec![
506 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
507 Action::new(
508 Variable::new("var1"),
509 IntegerExpression::BinaryExpr(
510 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
511 IntegerOp::Add,
512 Box::new(IntegerExpression::Const(1)),
513 ),
514 )
515 .unwrap(),
516 ])
517 .build(),
518 ])
519 .unwrap()
520 .build();
521
522 let ctx = SMTSolverBuilder::new(&SMTSolverBuilderCfg::new_z3()).unwrap();
523 let got_ta = GeneralThresholdAutomaton::try_from_general_ta(ta.clone(), &ctx, &()).unwrap();
524
525 assert_eq!(got_ta, vec![ta])
526 }
527
528 #[test]
529 fn test_try_from_for_general_lta() {
530 let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
531 .with_parameters(vec![
532 Parameter::new("n"),
533 Parameter::new("t"),
534 Parameter::new("f"),
535 ])
536 .unwrap()
537 .with_variables(vec![
538 Variable::new("var1"),
539 Variable::new("var2"),
540 Variable::new("var3"),
541 ])
542 .unwrap()
543 .with_locations(vec![
544 Location::new("loc1"),
545 Location::new("loc2"),
546 Location::new("loc3"),
547 ])
548 .unwrap()
549 .initialize()
550 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
551 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
552 ComparisonOp::Eq,
553 Box::new(IntegerExpression::Const(1)),
554 )])
555 .unwrap()
556 .with_initial_location_constraints(vec![
557 LocationConstraint::ComparisonExpression(
558 Box::new(IntegerExpression::Atom(Location::new("loc1"))),
559 ComparisonOp::Eq,
560 Box::new(
561 IntegerExpression::Param(Parameter::new("n"))
562 - IntegerExpression::Param(Parameter::new("f")),
563 ),
564 ) | LocationConstraint::ComparisonExpression(
565 Box::new(IntegerExpression::Atom(Location::new("loc2"))),
566 ComparisonOp::Eq,
567 Box::new(IntegerExpression::Const(0)),
568 ),
569 ])
570 .unwrap()
571 .with_resilience_conditions(vec![ParameterConstraint::ComparisonExpression(
572 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
573 ComparisonOp::Gt,
574 Box::new(IntegerExpression::BinaryExpr(
575 Box::new(IntegerExpression::Const(3)),
576 IntegerOp::Mul,
577 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
578 )),
579 )])
580 .unwrap()
581 .with_rules(vec![
582 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
583 .with_actions(vec![
584 Action::new(
585 Variable::new("var1"),
586 IntegerExpression::Atom(Variable::new("var1")),
587 )
588 .unwrap(),
589 ])
590 .build(),
591 RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
592 .with_guard(
593 BooleanVarConstraint::ComparisonExpression(
594 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
595 ComparisonOp::Eq,
596 Box::new(IntegerExpression::Const(1)),
597 ) & BooleanVarConstraint::ComparisonExpression(
598 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
599 ComparisonOp::Eq,
600 Box::new(IntegerExpression::Param(Parameter::new("n"))),
601 ),
602 )
603 .with_actions(vec![
604 Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
605 Action::new(
606 Variable::new("var1"),
607 IntegerExpression::BinaryExpr(
608 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
609 IntegerOp::Add,
610 Box::new(IntegerExpression::Const(1)),
611 ),
612 )
613 .unwrap(),
614 ])
615 .build(),
616 ])
617 .unwrap()
618 .build();
619
620 let ctx = SMTSolverBuilder::new(&SMTSolverBuilderCfg::new_z3()).unwrap();
621 let got_ta = LIAThresholdAutomaton::try_from_general_ta(ta.clone(), &ctx, &()).unwrap();
622
623 let lta = ta.try_into().unwrap();
624
625 assert_eq!(got_ta, vec![lta])
626 }
627}