Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finite invariants #152

Draft
wants to merge 34 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
42ccd5b
Add test that shows the nesting bug
Alex-Fischman Jul 18, 2023
d6ace41
Add finite invariant inference module
Alex-Fischman Jul 10, 2023
209a35a
Autoformat
Alex-Fischman Jul 10, 2023
34651f2
Add unit test for finite
Alex-Fischman Jul 10, 2023
95deab9
Return Context with Bdd
Alex-Fischman Jul 10, 2023
9778909
Take an initial universe as input
Alex-Fischman Jul 10, 2023
42be606
Convert BDD to Term
Alex-Fischman Jul 11, 2023
1ff3c90
Build forall term
Alex-Fischman Jul 11, 2023
98bd113
Remove verify_module, now blocked on transition system extraction
Alex-Fischman Jul 11, 2023
669e865
Add MaybeSpannedTerm, plug finite into verify
Alex-Fischman Jul 13, 2023
fa37794
Change no-arg Apps to Ids
Alex-Fischman Jul 13, 2023
483d150
Move bdd_to_term into bdd.rs instead of finite.rs
Alex-Fischman Jul 13, 2023
f25e969
Make test not fail
Alex-Fischman Jul 13, 2023
01b255f
Remove finite.rs
Alex-Fischman Jul 13, 2023
d93154c
Remove finite from lib.rs
Alex-Fischman Jul 13, 2023
22cb416
Add finite.rs
Alex-Fischman Jul 13, 2023
767977d
Add finite to lib.rs
Alex-Fischman Jul 13, 2023
7c176b8
Add underapproximate(), add better error reporting for verify
Alex-Fischman Jul 14, 2023
625333d
Handle full traces in finite.rs
wilcoxjay Jul 19, 2023
ed95d5b
Update finite counterexample printing
Alex-Fischman Jul 14, 2023
7b094e6
Figured out bug
Alex-Fischman Jul 14, 2023
8f0f060
Ignore finite test for now
Alex-Fischman Jul 14, 2023
0950766
Reversed works! Remove underapproximate and unignore test
Alex-Fischman Jul 14, 2023
0dc0dac
Reference existing lockserver file instead of copy-paste
Alex-Fischman Jul 14, 2023
c4cf478
Add print_timing bool, add consensus test (that hasn't terminated)
wilcoxjay Jul 19, 2023
82cbeaf
Fix finite tests assertions
Alex-Fischman Jul 17, 2023
c30fb7f
Add finite CLI, add finite::invariant doc comments
Alex-Fischman Jul 17, 2023
410615d
Improve invariant() return type, remember that consensus doesn't have…
Alex-Fischman Jul 17, 2023
ccb7e74
Inline defs for finite.rs
wilcoxjay Jul 19, 2023
a81f4d2
Make finite return counterexamples instead of printing
Alex-Fischman Jul 20, 2023
8d4da5c
Update finite code to use model back-conversion
Alex-Fischman Jul 22, 2023
a4e1f3f
Fix errors after rebase
Alex-Fischman Jul 23, 2023
1a2f246
Fix print {}s
Alex-Fischman Jul 24, 2023
dddcec0
Fix errors after rebasing
Alex-Fischman Aug 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions inference/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ indexmap = { version = "1.9.3", features = ["rayon", "std"] }
cadical = "0.1.14"
biodivine-lib-bdd = "0.5.1"
thiserror = "1.0.40"
codespan-reporting = "0.11.1"
127 changes: 127 additions & 0 deletions inference/src/finite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2022-2023 VMware, Inc.
// SPDX-License-Identifier: BSD-2-Clause

//! Infer an inductive invariant from the set of reachable states
//! with small sort bounds.

use bounded::bdd::*;
use fly::{semantics::*, syntax::*, transitions::*};
use solver::conf::SolverConf;
use std::collections::HashMap;
use thiserror::Error;
use verify::{error::SolveError, module::verify_destructured_module};

/// The result of a successful run of finite::invariant.
pub enum FiniteAnswer {
/// The module wasn't safe to begin with
BddCounterexample(Vec<Model>),
/// The inferred invariant failed
InvariantFail(Term, SolveError),
/// The inferred invariant succeeded
InvariantInferred(Term),
}

#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum FiniteError {
#[error("{0}")]
ExtractionError(ExtractionError),
#[error("{0}")]
CheckerError(CheckerError),
}

/// Try to find an invariant by getting the set of all backwards reachable states.
/// Returns Some((inv, true)) if an invariant is found, or None if a counterexample is found.
/// If the invariant that is guessed does not work, it returns Some((inv, false)).
pub fn invariant(
module: &Module,
universe: HashMap<String, usize>,
conf: &SolverConf,
print_timing: bool,
) -> Result<FiniteAnswer, FiniteError> {
// Get the set of reachable states
let (bdd, context) = match check_reversed(module, &universe, None, print_timing) {
Ok(CheckerAnswer::Convergence(bdd, context)) => (bdd, context),
Ok(CheckerAnswer::Counterexample(models)) => {
return Ok(FiniteAnswer::BddCounterexample(models))
}
Ok(CheckerAnswer::Unknown) => unreachable!(),
Err(e) => return Err(FiniteError::CheckerError(e)),
};

// Convert the Bdd to a Term
let (ast, bindings) = bdd_to_term(&bdd.not(), &context);

// Add not-equal clauses between same-sort elements
let mut not_equals = vec![];
for ((sort_1, element_1), name_1) in &bindings {
for ((sort_2, element_2), name_2) in &bindings {
if element_1 < element_2 && sort_1 == sort_2 {
not_equals.push(Term::BinOp(
BinOp::NotEquals,
Box::new(Term::Id(name_1.to_string())),
Box::new(Term::Id(name_2.to_string())),
));
}
}
}

// Wrap the Term in foralls
let binders = bindings
.into_iter()
.map(|((sort, _), name)| Binder {
name,
sort: Sort::Uninterpreted(sort.to_string()),
})
.collect();
let ast = Term::Quantified {
quantifier: Quantifier::Forall,
body: Box::new(Term::BinOp(
BinOp::Implies,
Box::new(Term::NAryOp(NOp::And, not_equals)),
Box::new(ast),
)),
binders,
};

// Try to verify the term
let mut destructured = extract(module).map_err(FiniteError::ExtractionError)?;
assert_eq!(1, destructured.proofs.len());
destructured.proofs[0].invariants = vec![Spanned {
x: ast.clone(),
span: None,
}];

if let Err(err) = verify_destructured_module(conf, &destructured, &module.signature) {
Ok(FiniteAnswer::InvariantFail(ast, err))
} else {
Ok(FiniteAnswer::InvariantInferred(ast))
}
}

#[cfg(test)]
mod tests {
use super::*;
use fly::sorts::sort_check_and_infer;
use solver::{backends::GenericBackend, backends::SolverType, solver_path};

#[test]
fn finite_lockserver() -> Result<(), FiniteError> {
let source = include_str!("../../temporal-verifier/examples/lockserver.fly");

let mut module = fly::parser::parse(source).unwrap();
sort_check_and_infer(&mut module).unwrap();

let universe = HashMap::from([("node".to_string(), 2)]);
let conf = SolverConf {
backend: GenericBackend::new(SolverType::Z3, &solver_path("z3")),
tee: None,
};

match invariant(&module, universe, &conf, false)? {
FiniteAnswer::InvariantInferred(_) => Ok(()),
FiniteAnswer::InvariantFail(inv, _) => panic!("invariant wasn't inductive: {inv}"),
FiniteAnswer::BddCounterexample(_) => panic!("counterexample found"),
}
}
}
1 change: 1 addition & 0 deletions inference/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

pub mod atoms;
pub mod basics;
pub mod finite;
pub mod fixpoint;
pub mod hashmap;
pub mod houdini;
Expand Down
51 changes: 51 additions & 0 deletions temporal-verifier/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ enum Command {
#[command(flatten)]
solver: SolverArgs,
},
FiniteInfer {
#[command(flatten)]
bounded: BoundedArgs, // depth bound is unused
#[command(flatten)]
solver: SolverArgs,
},
}

impl InferCommand {
Expand Down Expand Up @@ -431,6 +437,10 @@ impl Command {
bounded: BoundedArgs { file, .. },
..
} => file,
Command::FiniteInfer {
bounded: BoundedArgs { file, .. },
..
} => file,
}
}
}
Expand Down Expand Up @@ -744,6 +754,47 @@ impl App {
Err(error) => eprintln!("{error}"),
}
}
Command::FiniteInfer { bounded, solver } => {
m.inline_defs();
let back_convert_model = m.convert_non_bool_relations();
match inference::finite::invariant(
&m,
bounded.get_universe(&m.signature),
&solver.get_solver_conf(&file),
bounded.print_timing.unwrap_or(true),
) {
Ok(inference::finite::FiniteAnswer::BddCounterexample(models)) => {
println!(
"found counterexample:\n{}",
models_to_string(models.iter().map(back_convert_model))
)
}
Ok(inference::finite::FiniteAnswer::InvariantFail(term, err)) => {
eprintln!("invariant wasn't inductive: {term}");
for fail in &err.fails {
use verify::error::*;
match fail.reason {
FailureType::InitInv => println!("not implied by init:"),
FailureType::NotInductive => println!("not inductive:"),
FailureType::Unsupported => println!("unsupported:"),
}
match &fail.error {
QueryError::Sat(models) => {
println!(
"{}",
models_to_string(models.iter().map(&back_convert_model))
)
}
QueryError::Unknown(message) => eprintln!("{message}"),
}
}
}
Ok(inference::finite::FiniteAnswer::InvariantInferred(term)) => {
println!("found inductive invariant: {term}");
}
Err(error) => eprintln!("{error}"),
}
}
}
}
}