Skip to content

Commit 5dc65a5

Browse files
authored
Add support for wild-card propositions (#9)
* WIP: Add model checking for formulae that use wild-card propositions * Refactor, fix small bugs, add test cases, modify HctlTreeNode * Extend tests * Update readme regarding new functionality * Refactoring * Add validation step for substitution context. * Fix github workflow * Update instructions * Downgrade clap dependency for compatibility * Downgrade termcolor dependency for compatibility * Remove unused githooks
1 parent d063cbb commit 5dc65a5

File tree

19 files changed

+1021
-385
lines changed

19 files changed

+1021
-385
lines changed

.githooks/README.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

.githooks/pre-commit

Lines changed: 0 additions & 7 deletions
This file was deleted.

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ jobs:
8989
- name: Setup cargo-tarpaulin.
9090
run: cargo install cargo-tarpaulin
9191
- name: Run tarpaulin to compute coverage.
92-
run: cargo tarpaulin --verbose --lib --examples --all-features --out Xml
92+
run: cargo tarpaulin --verbose --lib --examples --all-features --out xml
9393
- name: Upload to codecov.io
9494
uses: codecov/codecov-action@v1.0.2
9595
with:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "biodivine-hctl-model-checker"
3-
version = "0.1.7"
3+
version = "0.2.0"
44
authors = ["Ondřej Huvar <xhuvar@fi.muni.cz>", "Samuel Pastva <sam.pastva@gmail.com>"]
55
edition = "2021"
66
description = "Library for symbolic HCTL model checking on partially defined Boolean networks."

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ This includes properties like stability, bi-stability, attractors, or oscillator
1818
To run the model checker, you will need the Rust compiler.
1919
We recommend following the instructions on [rustlang.org](https://www.rust-lang.org/learn/get-started).
2020

21+
If you are not familiar with Rust, there are also Python bindings for most of the important functionality in [AEON.py](https://github.com/sybila/biodivine-aeon-py).
22+
2123
## Functionality
2224

2325
This repository encompasses the CLI model-checking tool, and the model-checking library.
@@ -85,3 +87,10 @@ The operator precedence is following (the lower, the stronger):
8587
* hybrid operators: 8
8688

8789
However, it is strongly recommended to use parentheses wherever possible to prevent any parsing issues.
90+
91+
#### Wild-card properties
92+
93+
The library also provides functions to model check extended formulae that contain so called "wild-card propositions".
94+
These special propositions are evaluated as an arbitrary (coloured) set given by the user.
95+
This allows the re-use of already pre-computed results in subsequent computations.
96+
In formulae, the syntax of these propositions is `%property_name%`.

src/analysis.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//! Model-checking analysis from start to finish, with progress output and result prints.
22
33
use crate::evaluation::algorithm::{compute_steady_states, eval_node};
4-
use crate::evaluation::eval_info::EvalInfo;
4+
use crate::evaluation::eval_context::EvalContext;
55
use crate::mc_utils::{collect_unique_hctl_vars, get_extended_symbolic_graph};
66
use crate::preprocessing::parser::parse_hctl_formula;
77
use crate::preprocessing::utils::check_props_and_rename_vars;
88
use crate::result_print::*;
99

1010
use biodivine_lib_param_bn::BooleanNetwork;
1111

12-
use std::collections::{HashMap, HashSet};
12+
use std::collections::HashMap;
1313
use std::time::SystemTime;
1414

1515
/// Perform the whole model checking analysis regarding several (individual) formulae. This
@@ -42,7 +42,7 @@ pub fn analyse_formulae(
4242
);
4343

4444
let modified_tree = check_props_and_rename_vars(tree, HashMap::new(), String::new(), bn)?;
45-
let num_hctl_vars = collect_unique_hctl_vars(modified_tree.clone(), HashSet::new()).len();
45+
let num_hctl_vars = collect_unique_hctl_vars(modified_tree.clone()).len();
4646
print_if_allowed(
4747
format!("Modified version: {}", modified_tree.subform_str),
4848
print_op,
@@ -86,7 +86,7 @@ pub fn analyse_formulae(
8686
print_if_allowed("-----".to_string(), print_op);
8787

8888
// find duplicate sub-formulae throughout all formulae + initiate caching structures
89-
let mut eval_info = EvalInfo::from_multiple_trees(&parsed_trees);
89+
let mut eval_info = EvalContext::from_multiple_trees(&parsed_trees);
9090
print_if_allowed(
9191
format!(
9292
"Found following duplicate sub-formulae (canonized): {:?}",
@@ -153,14 +153,13 @@ pub fn analyse_formula(
153153

154154
#[cfg(test)]
155155
mod tests {
156-
use crate::analysis::analyse_formulae;
156+
use crate::analysis::{analyse_formula, analyse_formulae};
157157
use crate::result_print::PrintOptions;
158158
use biodivine_lib_param_bn::BooleanNetwork;
159159

160160
#[test]
161161
/// Simple test to check whether the whole analysis runs without an error.
162162
fn test_analysis_run() {
163-
let formulae = vec!["!{x}: AG EF {x}".to_string(), "!{x}: AF {x}".to_string()];
164163
let model = r"
165164
$frs2:(!erk & fgfr)
166165
fgfr -> frs2
@@ -176,6 +175,10 @@ mod tests {
176175
";
177176
let bn = BooleanNetwork::try_from(model).unwrap();
178177

178+
let formulae = vec!["!{x}: AG EF {x}".to_string(), "!{x}: AF {x}".to_string()];
179179
assert!(analyse_formulae(&bn, formulae, PrintOptions::WithProgress).is_ok());
180+
181+
let formula = "erk & fgfr & frs2 & ~shc".to_string(); // simple to avoid long prints
182+
assert!(analyse_formula(&bn, formula, PrintOptions::Exhaustive).is_ok());
180183
}
181184
}

src/evaluation/algorithm.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::aeon::scc_computation::compute_attractor_states;
44
use crate::evaluation::canonization::get_canonical_and_mapping;
5-
use crate::evaluation::eval_info::EvalInfo;
5+
use crate::evaluation::eval_context::EvalContext;
66
use crate::evaluation::hctl_operators_evaluation::*;
77
use crate::evaluation::low_level_operations::substitute_hctl_var;
88
use crate::preprocessing::node::{HctlTreeNode, NodeType};
@@ -20,7 +20,7 @@ use std::collections::HashMap;
2020
pub fn eval_node(
2121
node: HctlTreeNode,
2222
graph: &SymbolicAsyncGraph,
23-
eval_info: &mut EvalInfo,
23+
eval_info: &mut EvalContext,
2424
steady_states: &GraphColoredVertices,
2525
) -> GraphColoredVertices {
2626
// first check whether this node does not belong in the duplicates
@@ -89,6 +89,8 @@ pub fn eval_node(
8989
Atomic::False => graph.mk_empty_vertices(),
9090
Atomic::Var(name) => eval_hctl_var(graph, name.as_str()),
9191
Atomic::Prop(name) => eval_prop(graph, &name),
92+
// should not be reachable, as wild-card nodes are always evaluated earlier using cache
93+
Atomic::WildCardProp(_) => unreachable!(),
9294
},
9395
NodeType::UnaryNode(op, child) => match op {
9496
UnaryOp::Not => eval_neg(graph, &eval_node(*child, graph, eval_info, steady_states)),
@@ -269,8 +271,8 @@ mod tests {
269271
#[test]
270272
/// Test recognition of fixed-point pattern.
271273
fn test_fixed_point_pattern() {
272-
let tree = create_hybrid(
273-
create_unary(create_var_node("x".to_string()), UnaryOp::Ax),
274+
let tree = HctlTreeNode::mk_hybrid_node(
275+
HctlTreeNode::mk_unary_node(HctlTreeNode::mk_var_node("x".to_string()), UnaryOp::Ax),
274276
"x".to_string(),
275277
HybridOp::Bind,
276278
);
@@ -280,9 +282,12 @@ mod tests {
280282
#[test]
281283
/// Test recognition of attractor pattern.
282284
fn test_attractor_pattern() {
283-
let tree = create_hybrid(
284-
create_unary(
285-
create_unary(create_var_node("x".to_string()), UnaryOp::Ef),
285+
let tree = HctlTreeNode::mk_hybrid_node(
286+
HctlTreeNode::mk_unary_node(
287+
HctlTreeNode::mk_unary_node(
288+
HctlTreeNode::mk_var_node("x".to_string()),
289+
UnaryOp::Ef,
290+
),
286291
UnaryOp::Ag,
287292
),
288293
"x".to_string(),

src/evaluation/eval_context.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//! Contains the structure to hold useful data to speed-up the computation.
2+
3+
use crate::evaluation::mark_duplicate_subform::{
4+
mark_duplicates_canonized_multiple, mark_duplicates_canonized_single,
5+
};
6+
use crate::preprocessing::node::HctlTreeNode;
7+
8+
use biodivine_lib_param_bn::symbolic_async_graph::GraphColoredVertices;
9+
10+
use std::collections::HashMap;
11+
12+
/// Struct holding information for efficient caching during the main computation.
13+
#[derive(Clone, Debug, PartialEq, Eq)]
14+
pub struct EvalContext {
15+
/// Duplicate sub-formulae and their counter
16+
pub duplicates: HashMap<String, i32>,
17+
/// Cached sub-formulae and their result + corresponding mapping of variable renaming
18+
pub cache: HashMap<String, (GraphColoredVertices, HashMap<String, String>)>,
19+
}
20+
21+
impl EvalContext {
22+
/// Instantiate the struct with precomputed duplicates and empty cache.
23+
pub fn new(duplicates: HashMap<String, i32>) -> EvalContext {
24+
EvalContext {
25+
duplicates,
26+
cache: HashMap::new(),
27+
}
28+
}
29+
30+
/// Instantiate the struct with precomputed duplicates and empty cache.
31+
pub fn from_single_tree(tree: &HctlTreeNode) -> EvalContext {
32+
EvalContext {
33+
duplicates: mark_duplicates_canonized_single(tree),
34+
cache: HashMap::new(),
35+
}
36+
}
37+
38+
/// Instantiate the struct with precomputed duplicates and empty cache.
39+
pub fn from_multiple_trees(trees: &Vec<HctlTreeNode>) -> EvalContext {
40+
EvalContext {
41+
duplicates: mark_duplicates_canonized_multiple(trees),
42+
cache: HashMap::new(),
43+
}
44+
}
45+
46+
/// Get the duplicates field containing the sub-formulae and their counter.
47+
pub fn get_duplicates(&self) -> HashMap<String, i32> {
48+
self.duplicates.clone()
49+
}
50+
51+
/// Get the cache field containing the cached sub-formulae, their result and var renaming.
52+
pub fn get_cache(&self) -> HashMap<String, (GraphColoredVertices, HashMap<String, String>)> {
53+
self.cache.clone()
54+
}
55+
56+
/// Extend the standard evaluation context with "pre-computed cache" regarding wild-card nodes.
57+
pub fn extend_context_with_wild_cards(
58+
&mut self,
59+
substitution_context: HashMap<String, GraphColoredVertices>,
60+
) {
61+
// For each `wild-card proposition` in `substitution_context`, increment its duplicate
62+
// counter. That way, the first occurrence will also be treated as duplicate and taken from
63+
// cache directly.
64+
for (prop_name, raw_set) in substitution_context.into_iter() {
65+
// we dont have to compute canonical sub-formula, because there are no HCTL variables
66+
let sub_formula = format!("%{}%", prop_name);
67+
if self.duplicates.contains_key(sub_formula.as_str()) {
68+
self.duplicates.insert(
69+
sub_formula.clone(),
70+
self.duplicates[sub_formula.as_str()] + 1,
71+
);
72+
} else {
73+
self.duplicates.insert(sub_formula.clone(), 1);
74+
}
75+
76+
// Add the raw sets directly to the cache to be used during evaluation.
77+
// The mapping for var renaming is empty, because there are no HCTL vars.
78+
self.cache.insert(sub_formula, (raw_set, HashMap::new()));
79+
}
80+
}
81+
}
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use crate::evaluation::eval_context::EvalContext;
86+
use crate::mc_utils::get_extended_symbolic_graph;
87+
use crate::preprocessing::parser::{parse_extended_formula, parse_hctl_formula};
88+
89+
use biodivine_lib_param_bn::BooleanNetwork;
90+
91+
use std::collections::HashMap;
92+
93+
#[test]
94+
/// Test equivalent ways to generate EvalContext object.
95+
fn test_eval_context_creation() {
96+
let formula = "!{x}: (AX {x} & AX {x})";
97+
let syntax_tree = parse_hctl_formula(formula).unwrap();
98+
99+
let expected_duplicates = HashMap::from([("(Ax {var0})".to_string(), 1)]);
100+
let eval_info = EvalContext::new(expected_duplicates.clone());
101+
102+
assert_eq!(eval_info, EvalContext::from_single_tree(&syntax_tree));
103+
assert_eq!(
104+
eval_info,
105+
EvalContext::from_multiple_trees(&vec![syntax_tree])
106+
);
107+
assert_eq!(eval_info.get_duplicates(), expected_duplicates);
108+
109+
// check that cache is always initially empty
110+
assert!(eval_info.get_cache().is_empty());
111+
}
112+
113+
#[test]
114+
/// Test extension of the EvalContext with "pre-computed cache" regarding wild-card nodes.
115+
fn test_eval_context_extension() {
116+
// prepare placeholder BN and STG
117+
let bn = BooleanNetwork::try_from_bnet("v1, v1").unwrap();
118+
let stg = get_extended_symbolic_graph(&bn, 2).unwrap();
119+
120+
let formula = "!{x}: 3{y}: (@{x}: ~{y} & %subst%) & (@{y}: %subst%)";
121+
let syntax_tree = parse_extended_formula(formula).unwrap();
122+
let mut eval_info = EvalContext::from_single_tree(&syntax_tree);
123+
124+
assert_eq!(
125+
eval_info.get_duplicates(),
126+
HashMap::from([("%subst%".to_string(), 1)])
127+
);
128+
assert_eq!(eval_info.get_cache(), HashMap::new());
129+
130+
let raw_set = stg.mk_unit_colored_vertices();
131+
let substitution_context = HashMap::from([("subst".to_string(), raw_set.clone())]);
132+
eval_info.extend_context_with_wild_cards(substitution_context);
133+
let expected_cache = HashMap::from([("%subst%".to_string(), (raw_set, HashMap::new()))]);
134+
135+
assert_eq!(
136+
eval_info.get_duplicates(),
137+
HashMap::from([("%subst%".to_string(), 2)])
138+
);
139+
assert_eq!(eval_info.get_cache(), expected_cache);
140+
}
141+
}

src/evaluation/eval_info.rs

Lines changed: 0 additions & 70 deletions
This file was deleted.

0 commit comments

Comments
 (0)