1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-19 09:29:36 +01:00

code-style and documentation tidying (#34)

* Remove `unwrap()` from code
Remove `unwrap()` from the code (except tests).
Replaced it by either using `expect()` or handling the error more
gracefully.

* Improve documentation
Add examples on how to use the library
Add explanaitions on the dual use of crate::datatypes::Term
Relates to #33 

* Update README
* Add contributing guidelines
This commit is contained in:
Stefan Ellmauthaler 2022-03-14 21:05:53 +01:00 committed by GitHub
parent 31d6c88d90
commit 80915b1df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 314 additions and 79 deletions

80
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,80 @@
# How to contribute
We are really glad you are reading this, because we need developers to help this project come to fruition.
## Folder structure
The project is divided into two different cargo-projects under one rust-workspace.
* `lib` is where the library is located
* `bin` is where the binary is located
* `res` is a folder, where different resources for testing and benchmarks are located (usually managed through `git submodules`
Easy first tasks for contributing will be to integrate implemented features from the library, which are not represented in the binary.
## Testing
Please make sure to test your additional features.
### Unit-Testing
Unit tests are done for each module by an associated `test` sub-module.
It can either be directly in the `<module.rs>` file or in an additional `test` sub-directory.
Please try to generate meaningful tests, with sane data. It would be most appreciated if there are some real-world flavours.
Add `quickcheck` tests whenever it is applicable.
### Integration-Testing
Integration testing is done in the related `tests` directory on the top-level of the crate.
## Submitting changes
Please send a [GitHub Pull Request to ellmau/adf-obdd](https://github.com/ellmau/adf-obdd/pull/new/main) with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). When you send a pull request be sure to check open and claimed tickets first. We can always use more test coverage. Please follow our coding conventions (below).
Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should have a commit-paragraph and/or a related and appropriately mentioned [issue](https://github.com/ellmau/adf-obdd/issues).
Before creating the pull request be sure to check if
- [ ] all already existing tests are passing,
- [ ] new tests are passing,
- [ ] `clippy` does not complain about your code, and
- [ ] the code has been formatted with `rustfmt`.
If you have done changes to any other folder than `lib` and `bin`, choose the `repository`-label for the pull request.
We are monitoring our code-coverage through [coveralls](https://coveralls.io), so it is expected that additional changes do not reduce the test-coverage significantly.
Keep in mind, that the current coverage-tools have some issues with rust-code and sometimes report lower code-coverage than it is in reality.
Finally, if you create a pull request for work in progress, please mark this by creating a draft pull request.
### Commit messages
To create uniform and easy to read commit messages, please stick to the following conventions:
* Capitalise the first word
* Do not end the message title in punctuation
* Use imperative mood
* The message title shall not exceed 50 characters
* Be direct, do not use filler words (e.g. "I think", "maybe", "kind of", ...)
* Use [Github Issue/PR Keywords](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests) in the message description part where applicable
* Link to other related pull requests, issues, commits, comments, ... to have a concise representation of the context in the message description
* Sign your commit whenever possible
A commit message is usually consisting of the first line, the so-called `message-title`, one free line, and the `message description` which may take the following lines.
## Coding conventions
Start reading our code and you'll get the hang of it.
* We use `rustfmt` as code-convention. (you can use whatever styles you like, just let `rustfmt` format the code before you commit)
* We try to reduce redundancies in enumeration-variant names.
* We follow the code-conventions and naming-conventions of the current Rust version.
* We write `clippy`-conform code, so follow `clippy` suggestions where applicable. If you write a compiler-exception (i.e. `#[allow(...)]`) describe your decision to do so in a meaningful comment. We advise to mark this code-segment in the pull-request as a code-comment too.
* `rustdoc` is obligatory for crate-exposed structures (e.g. `enum`, `struct`, `fn`, ...).
* `rustdoc` is nice to have for non-crate-exposed structures.
* We try to have one atomic commit for refactoring work done.
* Error-handling shall follow these guidelines:
* `panic!` (and `expect()`, `assert!`, `unreachable!` etc.) is fine for situations that should not occur, e.g., if there is some invariant that makes the situation impossible, or where graceful recovery is impossible, but not otherwise, and
* `unwrap()` should (almost?) always be `expect()` instead (exceptions are in tests).
* Use `unsafe` code only if :
* It is checked that there is no safe way to achieve the functionality,
* it has been discussed with the core development team in detail,
* the unsafe part is tested even more carefully than the rest of the code, and
* you will persistently insist on a detailed code-review

View File

@ -11,38 +11,41 @@ An ordered binary decision diagram is a normalised representation of binary func
## Usage of the binary ## Usage of the binary
``` ```
USAGE: USAGE:
adf_bdd [FLAGS] [OPTIONS] <input> adf_bdd [OPTIONS] <INPUT>
FLAGS:
--com Compute the complete models
--grd Compute the grounded model
-h, --help Prints help information
--import Import an adf- bdd state instead of an adf
-q Sets log verbosity to only errors
--an Sorts variables in an alphanumeric manner
--lx Sorts variables in an lexicographic manner
--stm Compute the stable models
--stmpre Compute the stable models with a pre-filter (only hybrid lib-mode)
--stmrew Compute the stable models with a single-formula rewriting (only hybrid lib-mode)
--stmrew2 Compute the stable models with a single-formula rewriting on internal representation(only hybrid
lib-mode)
-V, --version Prints version information
-v Sets log verbosity (multiple times means more verbose)
OPTIONS:
--export <export> Export the adf-bdd state after parsing and BDD instantiation to the given filename
--lib <implementation> choose the bdd implementation of either 'biodivine', 'naive', or hybrid [default:
biodivine]
--rust_log <rust-log> Sets the verbosity to 'warn', 'info', 'debug' or 'trace' if -v and -q are not use
[env: RUST_LOG=debug]
ARGS: ARGS:
<input> Input filename <INPUT> Input filename
OPTIONS:
--an Sorts variables in an alphanumeric manner
--com Compute the complete models
--counter <COUNTER> Set if the (counter-)models shall be computed and printed,
possible values are 'nai' and 'mem' for naive and memoization
repectively (only works in hybrid and naive mode)
--export <EXPORT> Export the adf-bdd state after parsing and BDD instantiation to
the given filename
--grd Compute the grounded model
-h, --help Print help information
--import Import an adf- bdd state instead of an adf
--lib <IMPLEMENTATION> choose the bdd implementation of either 'biodivine', 'naive', or
hybrid [default: hybrid]
--lx Sorts variables in an lexicographic manner
-q Sets log verbosity to only errors
--rust_log <RUST_LOG> Sets the verbosity to 'warn', 'info', 'debug' or 'trace' if -v and
-q are not use [env: RUST_LOG=debug]
--stm Compute the stable models
--stmpre Compute the stable models with a pre-filter (only hybrid lib-mode)
--stmrew Compute the stable models with a single-formula rewriting (only
hybrid lib-mode)
--stmrew2 Compute the stable models with a single-formula rewriting on
internal representation(only hybrid lib-mode)
-v Sets log verbosity (multiple times means more verbose)
-V, --version Print version information
``` ```
Note that import and export only works if the naive library is chosen Note that import and export only works if the naive library is chosen
Right now there is no additional information to the computed models, so if you use --com --grd --stm the borders between the results are not obviously communicated. Right now there is no additional information to the computed models, so if you use `--com --grd --stm` as the command line arguments the borders between the results are not obviously communicated.
They can be easily identified though: They can be easily identified though:
- The computation is always in the same order - The computation is always in the same order
- grd - grd

View File

@ -17,33 +17,36 @@ In addition some further features, like counter-model counting is not supported
# Usage # Usage
```plain ```plain
USAGE: USAGE:
adf_bdd [FLAGS] [OPTIONS] <input> adf_bdd [OPTIONS] <INPUT>
FLAGS:
--com Compute the complete models
--grd Compute the grounded model
-h, --help Prints help information
--import Import an adf- bdd state instead of an adf
-q Sets log verbosity to only errors
--an Sorts variables in an alphanumeric manner
--lx Sorts variables in an lexicographic manner
--stm Compute the stable models
--stmpre Compute the stable models with a pre-filter (only hybrid lib-mode)
--stmrew Compute the stable models with a single-formula rewriting (only hybrid lib-mode)
--stmrew2 Compute the stable models with a single-formula rewriting on internal representation(only hybrid
lib-mode)
-V, --version Prints version information
-v Sets log verbosity (multiple times means more verbose)
OPTIONS:
--export <export> Export the adf-bdd state after parsing and BDD instantiation to the given filename
--lib <implementation> choose the bdd implementation of either 'biodivine', 'naive', or hybrid [default:
biodivine]
--rust_log <rust-log> Sets the verbosity to 'warn', 'info', 'debug' or 'trace' if -v and -q are not use
[env: RUST_LOG=debug]
ARGS: ARGS:
<input> Input filename <INPUT> Input filename
OPTIONS:
--an Sorts variables in an alphanumeric manner
--com Compute the complete models
--counter <COUNTER> Set if the (counter-)models shall be computed and printed,
possible values are 'nai' and 'mem' for naive and memoization
repectively (only works in hybrid and naive mode)
--export <EXPORT> Export the adf-bdd state after parsing and BDD instantiation to
the given filename
--grd Compute the grounded model
-h, --help Print help information
--import Import an adf- bdd state instead of an adf
--lib <IMPLEMENTATION> choose the bdd implementation of either 'biodivine', 'naive', or
hybrid [default: hybrid]
--lx Sorts variables in an lexicographic manner
-q Sets log verbosity to only errors
--rust_log <RUST_LOG> Sets the verbosity to 'warn', 'info', 'debug' or 'trace' if -v and
-q are not use [env: RUST_LOG=debug]
--stm Compute the stable models
--stmpre Compute the stable models with a pre-filter (only hybrid lib-mode)
--stmrew Compute the stable models with a single-formula rewriting (only
hybrid lib-mode)
--stmrew2 Compute the stable models with a single-formula rewriting on
internal representation(only hybrid lib-mode)
-v Sets log verbosity (multiple times means more verbose)
-V, --version Print version information
``` ```
*/ */
@ -81,7 +84,7 @@ struct App {
#[clap(long = "rust_log", env)] #[clap(long = "rust_log", env)]
rust_log: Option<String>, rust_log: Option<String>,
/// choose the bdd implementation of either 'biodivine', 'naive', or hybrid /// choose the bdd implementation of either 'biodivine', 'naive', or hybrid
#[clap(long = "lib", default_value = "biodivine")] #[clap(long = "lib", default_value = "hybrid")]
implementation: String, implementation: String,
/// Sets log verbosity (multiple times means more verbose) /// Sets log verbosity (multiple times means more verbose)
#[clap(short, parse(from_occurrences), group = "verbosity")] #[clap(short, parse(from_occurrences), group = "verbosity")]
@ -152,8 +155,13 @@ impl App {
match self.implementation.as_str() { match self.implementation.as_str() {
"hybrid" => { "hybrid" => {
let parser = adf_bdd::parser::AdfParser::default(); let parser = adf_bdd::parser::AdfParser::default();
parser.parse()(&input).unwrap(); match parser.parse()(&input) {
log::info!("[Done] parsing"); Ok(_) => log::info!("[Done] parsing"),
Err(e) => {
log::error!("Error during parsing:\n{} \n\n cannot continue, panic!", e);
panic!("Parsing failed, see log for further details")
}
}
if self.sort_lex { if self.sort_lex {
parser.varsort_lexi(); parser.varsort_lexi();
} }
@ -222,7 +230,13 @@ impl App {
log::error!("Modelcounting not supported in biodivine mode"); log::error!("Modelcounting not supported in biodivine mode");
} }
let parser = adf_bdd::parser::AdfParser::default(); let parser = adf_bdd::parser::AdfParser::default();
parser.parse()(&input).unwrap(); match parser.parse()(&input) {
Ok(_) => log::info!("[Done] parsing"),
Err(e) => {
log::error!("Error during parsing:\n{} \n\n cannot continue, panic!", e);
panic!("Parsing failed, see log for further details")
}
}
log::info!("[Done] parsing"); log::info!("[Done] parsing");
if self.sort_lex { if self.sort_lex {
parser.varsort_lexi(); parser.varsort_lexi();
@ -263,18 +277,28 @@ impl App {
let mut adf = if self.import { let mut adf = if self.import {
#[cfg(not(feature = "adhoccounting"))] #[cfg(not(feature = "adhoccounting"))]
{ {
serde_json::from_str(&input).unwrap() serde_json::from_str(&input).expect("Old feature should work")
} }
#[cfg(feature = "adhoccounting")] #[cfg(feature = "adhoccounting")]
{ {
let mut result: Adf = serde_json::from_str(&input).unwrap(); let mut result: Adf =
serde_json::from_str(&input).expect("Old feature should work");
log::debug!("test"); log::debug!("test");
result.fix_import(); result.fix_import();
result result
} }
} else { } else {
let parser = AdfParser::default(); let parser = AdfParser::default();
parser.parse()(&input).unwrap(); match parser.parse()(&input) {
Ok(_) => log::info!("[Done] parsing"),
Err(e) => {
log::error!(
"Error during parsing:\n{} \n\n cannot continue, panic!",
e
);
panic!("Parsing failed, see log for further details")
}
}
log::info!("[Done] parsing"); log::info!("[Done] parsing");
if self.sort_lex { if self.sort_lex {
parser.varsort_lexi(); parser.varsort_lexi();

View File

@ -19,6 +19,8 @@ use crate::{
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
/// Representation of an ADF, with an ordering and dictionary of statement <-> number relations, a binary decision diagram, and a list of acceptance functions in Term representation /// Representation of an ADF, with an ordering and dictionary of statement <-> number relations, a binary decision diagram, and a list of acceptance functions in Term representation
///
/// Please note that due to the nature of the underlying reduced and ordered Bdd the concept of a [`Term`][crate::datatypes::Term] represents one (sub) formula as well as truth-values.
pub struct Adf { pub struct Adf {
ordering: VarContainer, ordering: VarContainer,
bdd: Bdd, bdd: Bdd,
@ -66,7 +68,9 @@ impl Adf {
new_order, new_order,
parser.ac_at(insert_order) parser.ac_at(insert_order)
); );
let result_term = result.term(&parser.ac_at(insert_order).unwrap()); let result_term = result.term(&parser.ac_at(insert_order).expect(
"Index should exist, because the data originates from the same parser object",
));
result.ac[*new_order] = result_term; result.ac[*new_order] = result_term;
}); });
log::info!("[Success] instantiated"); log::info!("[Success] instantiated");
@ -138,7 +142,7 @@ impl Adf {
Formula::Bot => Bdd::constant(false), Formula::Bot => Bdd::constant(false),
Formula::Top => Bdd::constant(true), Formula::Top => Bdd::constant(true),
Formula::Atom(val) => { Formula::Atom(val) => {
let t1 = self.ordering.variable(val).unwrap(); let t1 = self.ordering.variable(val).expect("Variable should exist, because the ordering has been filled by the same parser as the input formula comes from");
self.bdd.variable(t1) self.bdd.variable(t1)
} }
Formula::Not(val) => { Formula::Not(val) => {

View File

@ -22,6 +22,8 @@ use derivative::Derivative;
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug)] #[derivative(Debug)]
/// Representation of an ADF, with an ordering and dictionary of statement <-> number relations, a binary decision diagram, and a list of acceptance functions in biodivine representation together with a variable-list (needed by biodivine) /// Representation of an ADF, with an ordering and dictionary of statement <-> number relations, a binary decision diagram, and a list of acceptance functions in biodivine representation together with a variable-list (needed by biodivine)
///
/// To be compatible with results from the own implementation of the Bdd-based [`Adf`][crate::adf::Adf], we use the [`Term`][crate::datatypes::Term]-based representation for the various computed models.
pub struct Adf { pub struct Adf {
ordering: VarContainer, ordering: VarContainer,
ac: Vec<Bdd>, ac: Vec<Bdd>,
@ -69,7 +71,7 @@ impl Adf {
); );
result.ac[*new_order] = result result.ac[*new_order] = result
.varset .varset
.eval_expression(&parser.ac_at(insert_order).unwrap().to_boolean_expr()); .eval_expression(&parser.ac_at(insert_order).expect("Insert order needs to exist, as all the data originates from the same parser object").to_boolean_expr());
log::trace!("instantiated {}", result.ac[*new_order]); log::trace!("instantiated {}", result.ac[*new_order]);
}); });
log::info!("[Success] instantiated"); log::info!("[Success] instantiated");
@ -97,7 +99,7 @@ impl Adf {
.name(crate::datatypes::Var(*new_order)) .name(crate::datatypes::Var(*new_order))
.expect("Variable should exist"), .expect("Variable should exist"),
)), )),
Box::new(parser.ac_at(insert_order).unwrap().to_boolean_expr()), Box::new(parser.ac_at(insert_order).expect("Insert order needs to exist, as all the data originates from the same parser object").to_boolean_expr()),
)), )),
) )
}, },
@ -293,7 +295,7 @@ impl Adf {
let internal_rewriting: Bdd; let internal_rewriting: Bdd;
let sr: &Bdd; let sr: &Bdd;
if self.has_stm_rewriting() { if self.has_stm_rewriting() {
sr = self.rewrite.as_ref().unwrap(); sr = self.rewrite.as_ref().expect("self.rewrite has to exist, because it has been checked just before calling this reference");
} else { } else {
internal_rewriting = self.stable_representation(); internal_rewriting = self.stable_representation();
sr = &internal_rewriting; sr = &internal_rewriting;

View File

@ -102,8 +102,14 @@ impl Display for PrintableInterpretation<'_> {
} else { } else {
write!(f, "u(").expect("writing Interpretation failed!"); write!(f, "u(").expect("writing Interpretation failed!");
} }
write!(f, "{}) ", self.ordering.name(Var(pos)).unwrap()) write!(
.expect("writing Interpretation failed!"); f,
"{}) ",
self.ordering
.name(Var(pos))
.expect("Variable originates from same parser object as the ordering")
)
.expect("writing Interpretation failed!");
}); });
writeln!(f) writeln!(f)
} }

View File

@ -39,18 +39,127 @@ The binary predicate ac relates each statement to one propositional formula in p
- c(f): constant symbol "falsum" - inconsistency/bot - c(f): constant symbol "falsum" - inconsistency/bot
*/ */
//! ## Example input file: /*!
//! ```prolog ## Example input file:
//! s(a). ```prolog
//! s(b). s(a).
//! s(c). s(b).
//! s(d). s(c).
//! s(d).
//! ac(a,c(v)).
//! ac(b,or(a,b)). ac(a,c(v)).
//! ac(c,neg(b)). ac(b,or(a,b)).
//! ac(d,d). ac(c,neg(b)).
//! ``` ac(d,d).
```
*/
/*!
## Usage examples
First parse a given ADF and sort the statements, if needed.
```rust
use adf_bdd::parser::AdfParser;
use adf_bdd::adf::Adf;
// use the above example as input
let input = "s(a).s(b).s(c).s(d).ac(a,c(v)).ac(b,or(a,b)).ac(c,neg(b)).ac(d,d).";
let parser = AdfParser::default();
match parser.parse()(&input) {
Ok(_) => log::info!("[Done] parsing"),
Err(e) => {
log::error!(
"Error during parsing:\n{} \n\n cannot continue, panic!",
e
);
panic!("Parsing failed, see log for further details")
}
}
// sort lexicographic
parser.varsort_lexi();
```
### use the naive/in-crate implementation
```rust
# use adf_bdd::parser::AdfParser;
# use adf_bdd::adf::Adf;
# // use the above example as input
# let input = "s(a).s(b).s(c).s(d).ac(a,c(v)).ac(b,or(a,b)).ac(c,neg(b)).ac(d,d).";
# let parser = AdfParser::default();
# match parser.parse()(&input) {
# Ok(_) => log::info!("[Done] parsing"),
# Err(e) => {
# log::error!(
# "Error during parsing:\n{} \n\n cannot continue, panic!",
# e
# );
# panic!("Parsing failed, see log for further details")
# }
# }
# // sort lexicographic
# parser.varsort_lexi();
// create Adf
let mut adf = Adf::from_parser(&parser);
// compute and print the complete models
let printer = adf.print_dictionary();
for model in adf.complete() {
print!("{}", printer.print_interpretation(&model));
}
```
### use the biodivine implementation
```rust
# use adf_bdd::parser::AdfParser;
# use adf_bdd::adf::Adf;
# // use the above example as input
# let input = "s(a).s(b).s(c).s(d).ac(a,c(v)).ac(b,or(a,b)).ac(c,neg(b)).ac(d,d).";
# let parser = AdfParser::default();
# match parser.parse()(&input) {
# Ok(_) => log::info!("[Done] parsing"),
# Err(e) => {
# log::error!(
# "Error during parsing:\n{} \n\n cannot continue, panic!",
# e
# );
# panic!("Parsing failed, see log for further details")
# }
# }
# // sort lexicographic
# parser.varsort_lexi();
// create Adf
let adf = adf_bdd::adfbiodivine::Adf::from_parser(&parser);
// compute and print the complete models
let printer = adf.print_dictionary();
for model in adf.complete() {
print!("{}", printer.print_interpretation(&model));
}
```
### use the hybrid approach implementation
```rust
# use adf_bdd::parser::AdfParser;
# use adf_bdd::adf::Adf;
# // use the above example as input
# let input = "s(a).s(b).s(c).s(d).ac(a,c(v)).ac(b,or(a,b)).ac(c,neg(b)).ac(d,d).";
# let parser = AdfParser::default();
# match parser.parse()(&input) {
# Ok(_) => log::info!("[Done] parsing"),
# Err(e) => {
# log::error!(
# "Error during parsing:\n{} \n\n cannot continue, panic!",
# e
# );
# panic!("Parsing failed, see log for further details")
# }
# }
# // sort lexicographic
# parser.varsort_lexi();
// create biodivine Adf
let badf = adf_bdd::adfbiodivine::Adf::from_parser(&parser);
// instantiate the internally used adf after the reduction done by biodivine
let mut adf = badf.hybrid_step();
// compute and print the complete models
let printer = adf.print_dictionary();
for model in adf.complete() {
print!("{}", printer.print_interpretation(&model));
}
```
*/
#![deny( #![deny(
missing_debug_implementations, missing_debug_implementations,
missing_copy_implementations, missing_copy_implementations,

View File

@ -215,7 +215,7 @@ impl Bdd {
pub fn models(&self, term: Term, _memoization: bool) -> ModelCounts { pub fn models(&self, term: Term, _memoization: bool) -> ModelCounts {
#[cfg(feature = "adhoccounting")] #[cfg(feature = "adhoccounting")]
{ {
return self.count_cache.borrow().get(&term).unwrap().0; return self.count_cache.borrow().get(&term).expect("The term should be originating from this bdd, otherwise the result would be inconsistent anyways").0;
} }
#[cfg(not(feature = "adhoccounting"))] #[cfg(not(feature = "adhoccounting"))]
if _memoization { if _memoization {

View File

@ -370,7 +370,14 @@ impl AdfParser<'_> {
self.formulaname self.formulaname
.borrow() .borrow()
.iter() .iter()
.map(|name| *self.dict.as_ref().borrow().get(name).unwrap()) .map(|name| {
*self
.dict
.as_ref()
.borrow()
.get(name)
.expect("Dictionary should contain all the used formulanames")
})
.collect() .collect()
} }
} }