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

Add multithread-safe functionality for the dictionary/ordering

* Streamline a couple of API calls
* Expose more structs and methods to the public API
* Breaking some API (though nothing which is currently used in the binary)
This commit is contained in:
Stefan Ellmauthaler 2022-07-27 11:50:14 +02:00
parent 00674f7c5e
commit 84aa627159
Failed to extract signature
5 changed files with 134 additions and 78 deletions

View File

@ -47,16 +47,11 @@ impl Adf {
pub fn from_parser(parser: &AdfParser) -> Self { pub fn from_parser(parser: &AdfParser) -> Self {
log::info!("[Start] instantiating BDD"); log::info!("[Start] instantiating BDD");
let mut result = Self { let mut result = Self {
ordering: VarContainer::from_parser( ordering: parser.var_container(),
parser.namelist_rc_refcell(),
parser.dict_rc_refcell(),
),
bdd: Bdd::new(), bdd: Bdd::new(),
ac: vec![Term(0); parser.namelist_rc_refcell().as_ref().borrow().len()], ac: vec![Term(0); parser.dict_size()],
}; };
(0..parser.namelist_rc_refcell().borrow().len()) (0..parser.dict_size()).into_iter().for_each(|value| {
.into_iter()
.for_each(|value| {
log::trace!("adding variable {}", Var(value)); log::trace!("adding variable {}", Var(value));
result.bdd.variable(Var(value)); result.bdd.variable(Var(value));
}); });
@ -87,7 +82,7 @@ impl Adf {
bio_ac: &[biodivine_lib_bdd::Bdd], bio_ac: &[biodivine_lib_bdd::Bdd],
) -> Self { ) -> Self {
let mut result = Self { let mut result = Self {
ordering: VarContainer::copy(ordering), ordering: ordering.clone(),
bdd: Bdd::new(), bdd: Bdd::new(),
ac: vec![Term(0); bio_ac.len()], ac: vec![Term(0); bio_ac.len()],
}; };
@ -900,11 +895,16 @@ mod test {
parser.parse()(input).unwrap(); parser.parse()(input).unwrap();
let adf = Adf::from_parser(&parser); let adf = Adf::from_parser(&parser);
assert_eq!(adf.ordering.names().as_ref().borrow()[0], "a"); assert_eq!(adf.ordering.name(Var(0)), Some("a".to_string()));
assert_eq!(adf.ordering.names().as_ref().borrow()[1], "c"); assert_eq!(adf.ordering.names().read().unwrap()[0], "a");
assert_eq!(adf.ordering.names().as_ref().borrow()[2], "b"); assert_eq!(adf.ordering.name(Var(1)), Some("c".to_string()));
assert_eq!(adf.ordering.names().as_ref().borrow()[3], "e"); assert_eq!(adf.ordering.names().read().unwrap()[1], "c");
assert_eq!(adf.ordering.names().as_ref().borrow()[4], "d"); assert_eq!(adf.ordering.name(Var(2)), Some("b".to_string()));
assert_eq!(adf.ordering.names().read().unwrap()[2], "b");
assert_eq!(adf.ordering.name(Var(3)), Some("e".to_string()));
assert_eq!(adf.ordering.names().read().unwrap()[3], "e");
assert_eq!(adf.ordering.name(Var(4)), Some("d".to_string()));
assert_eq!(adf.ordering.names().read().unwrap()[4], "d");
assert_eq!(adf.ac, vec![Term(4), Term(2), Term(7), Term(15), Term(12)]); assert_eq!(adf.ac, vec![Term(4), Term(2), Term(7), Term(15), Term(12)]);
@ -915,11 +915,11 @@ mod test {
parser.varsort_alphanum(); parser.varsort_alphanum();
let adf = Adf::from_parser(&parser); let adf = Adf::from_parser(&parser);
assert_eq!(adf.ordering.names().as_ref().borrow()[0], "a"); assert_eq!(adf.ordering.names().read().unwrap()[0], "a");
assert_eq!(adf.ordering.names().as_ref().borrow()[1], "b"); assert_eq!(adf.ordering.names().read().unwrap()[1], "b");
assert_eq!(adf.ordering.names().as_ref().borrow()[2], "c"); assert_eq!(adf.ordering.names().read().unwrap()[2], "c");
assert_eq!(adf.ordering.names().as_ref().borrow()[3], "d"); assert_eq!(adf.ordering.names().read().unwrap()[3], "d");
assert_eq!(adf.ordering.names().as_ref().borrow()[4], "e"); assert_eq!(adf.ordering.names().read().unwrap()[4], "e");
assert_eq!(adf.ac, vec![Term(3), Term(7), Term(2), Term(11), Term(13)]); assert_eq!(adf.ac, vec![Term(3), Term(7), Term(2), Term(11), Term(13)]);
} }

View File

@ -40,19 +40,17 @@ impl Adf {
pub fn from_parser(parser: &AdfParser) -> Self { pub fn from_parser(parser: &AdfParser) -> Self {
log::info!("[Start] instantiating BDD"); log::info!("[Start] instantiating BDD");
let mut bdd_var_builder = biodivine_lib_bdd::BddVariableSetBuilder::new(); let mut bdd_var_builder = biodivine_lib_bdd::BddVariableSetBuilder::new();
let namelist = parser.namelist_rc_refcell().as_ref().borrow().clone(); let namelist = parser
.namelist()
.read()
.expect("ReadLock on namelist failed")
.clone();
let slice_vec: Vec<&str> = namelist.iter().map(<_>::as_ref).collect(); let slice_vec: Vec<&str> = namelist.iter().map(<_>::as_ref).collect();
bdd_var_builder.make_variables(&slice_vec); bdd_var_builder.make_variables(&slice_vec);
let bdd_variables = bdd_var_builder.build(); let bdd_variables = bdd_var_builder.build();
let mut result = Self { let mut result = Self {
ordering: VarContainer::from_parser( ordering: parser.var_container(),
parser.namelist_rc_refcell(), ac: vec![bdd_variables.mk_false(); parser.dict_size()],
parser.dict_rc_refcell(),
),
ac: vec![
bdd_variables.mk_false();
parser.namelist_rc_refcell().as_ref().borrow().len()
],
vars: bdd_variables.variables(), vars: bdd_variables.variables(),
varset: bdd_variables, varset: bdd_variables,
rewrite: None, rewrite: None,

View File

@ -2,49 +2,63 @@
use super::{Term, Var}; use super::{Term, Var};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; use std::{collections::HashMap, fmt::Display, sync::Arc, sync::RwLock};
#[derive(Serialize, Deserialize, Debug)] /// A container which acts as a dictionary as well as an ordering of variables.
pub(crate) struct VarContainer { /// *names* is a list of variable-names and the sequence of the values is inducing the order of variables.
names: Rc<RefCell<Vec<String>>>, /// *mapping* allows to search for a variable name and to receive the corresponding position in the variable list (`names`).
mapping: Rc<RefCell<HashMap<String, usize>>>, #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VarContainer {
names: Arc<RwLock<Vec<String>>>,
mapping: Arc<RwLock<HashMap<String, usize>>>,
} }
impl Default for VarContainer { impl Default for VarContainer {
fn default() -> Self { fn default() -> Self {
VarContainer { VarContainer {
names: Rc::new(RefCell::new(Vec::new())), names: Arc::new(RwLock::new(Vec::new())),
mapping: Rc::new(RefCell::new(HashMap::new())), mapping: Arc::new(RwLock::new(HashMap::new())),
} }
} }
} }
impl VarContainer { impl VarContainer {
pub fn from_parser( pub(crate) fn from_parser(
names: Rc<RefCell<Vec<String>>>, names: Arc<RwLock<Vec<String>>>,
mapping: Rc<RefCell<HashMap<String, usize>>>, mapping: Arc<RwLock<HashMap<String, usize>>>,
) -> VarContainer { ) -> VarContainer {
VarContainer { names, mapping } VarContainer { names, mapping }
} }
pub fn copy(from: &Self) -> Self { /// Get the [Var] used by the `Bdd` which corresponds to the given [&str].
VarContainer { /// Returns [None] if no matching value is found.
names: from.names.clone(),
mapping: from.mapping.clone(),
}
}
pub fn variable(&self, name: &str) -> Option<Var> { pub fn variable(&self, name: &str) -> Option<Var> {
self.mapping.borrow().get(name).map(|val| Var(*val)) self.mapping
.read()
.ok()
.and_then(|map| map.get(name).map(|val| Var(*val)))
} }
/// Get the name which corresponds to the given [Var].
/// Returns [None] if no matching value is found.
pub fn name(&self, var: Var) -> Option<String> { pub fn name(&self, var: Var) -> Option<String> {
self.names.borrow().get(var.value()).cloned() self.names
.read()
.ok()
.and_then(|name| name.get(var.value()).cloned())
} }
#[allow(dead_code)] pub(crate) fn names(&self) -> Arc<RwLock<Vec<String>>> {
pub fn names(&self) -> Rc<RefCell<Vec<String>>> { Arc::clone(&self.names)
Rc::clone(&self.names) }
pub(crate) fn mappings(&self) -> Arc<RwLock<HashMap<String, usize>>> {
Arc::clone(&self.mapping)
}
/// Creates a [PrintDictionary] for output purposes.
pub fn print_dictionary(&self) -> PrintDictionary {
PrintDictionary::new(self)
} }
} }
/// A struct which holds the dictionary to print interpretations and allows to instantiate printable interpretations. /// A struct which holds the dictionary to print interpretations and allows to instantiate printable interpretations.
@ -56,7 +70,7 @@ pub struct PrintDictionary {
impl PrintDictionary { impl PrintDictionary {
pub(crate) fn new(order: &VarContainer) -> Self { pub(crate) fn new(order: &VarContainer) -> Self {
Self { Self {
ordering: VarContainer::copy(order), ordering: order.clone(),
} }
} }
/// creates a [PrintableInterpretation] for output purposes /// creates a [PrintableInterpretation] for output purposes

View File

@ -169,14 +169,16 @@ The enum [`Heuristic`][crate::adf::heuristics::Heuristic] allows one to choose a
use adf_bdd::parser::AdfParser; use adf_bdd::parser::AdfParser;
use adf_bdd::adf::Adf; use adf_bdd::adf::Adf;
use adf_bdd::adf::heuristics::Heuristic; use adf_bdd::adf::heuristics::Heuristic;
use adf_bdd::datatypes::Term; use adf_bdd::datatypes::{Term, adf::VarContainer};
// create a channel // create a channel
let (s, r) = crossbeam_channel::unbounded(); let (s, r) = crossbeam_channel::unbounded();
let variables = VarContainer::default();
let variables_worker = variables.clone();
// spawn a solver thread // spawn a solver thread
let solving = std::thread::spawn(move || { let solving = std::thread::spawn(move || {
// use the above example as input // 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 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(); let parser = AdfParser::with_var_container(variables_worker);
parser.parse()(&input).expect("parsing worked well"); parser.parse()(&input).expect("parsing worked well");
// use hybrid approach // use hybrid approach
let mut adf = adf_bdd::adfbiodivine::Adf::from_parser(&parser).hybrid_step(); let mut adf = adf_bdd::adfbiodivine::Adf::from_parser(&parser).hybrid_step();
@ -184,9 +186,12 @@ let solving = std::thread::spawn(move || {
adf.stable_nogood_channel(Heuristic::Simple, s); adf.stable_nogood_channel(Heuristic::Simple, s);
}); });
let printer = variables.print_dictionary();
// print results as they are computed // print results as they are computed
while let Ok(result) = r.recv() { while let Ok(result) = r.recv() {
println!("stable model: {:?}", result); print!("stable model: {:?} \n", result);
// use dictionary
print!("stable model with variable names: {}", printer.print_interpretation(&result));
# assert_eq!(result, vec![Term(1),Term(1),Term(0),Term(0)]); # assert_eq!(result, vec![Term(1),Term(1),Term(0),Term(0)]);
} }
// waiting for the other thread to close // waiting for the other thread to close

View File

@ -10,7 +10,13 @@ use nom::{
sequence::{delimited, preceded, separated_pair, terminated}, sequence::{delimited, preceded, separated_pair, terminated},
IResult, IResult,
}; };
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::collections::HashMap;
use std::{
cell::RefCell,
sync::{Arc, RwLock},
};
use crate::datatypes::adf::VarContainer;
/// A representation of a formula, still using the strings from the input. /// A representation of a formula, still using the strings from the input.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
@ -127,8 +133,8 @@ impl std::fmt::Debug for Formula<'_> {
/// Note that the parser can be utilised by an [ADF][`crate::adf::Adf`] to initialise it with minimal overhead. /// Note that the parser can be utilised by an [ADF][`crate::adf::Adf`] to initialise it with minimal overhead.
#[derive(Debug)] #[derive(Debug)]
pub struct AdfParser<'a> { pub struct AdfParser<'a> {
namelist: Rc<RefCell<Vec<String>>>, namelist: Arc<RwLock<Vec<String>>>,
dict: Rc<RefCell<HashMap<String, usize>>>, dict: Arc<RwLock<HashMap<String, usize>>>,
formulae: RefCell<Vec<Formula<'a>>>, formulae: RefCell<Vec<Formula<'a>>>,
formulaname: RefCell<Vec<String>>, formulaname: RefCell<Vec<String>>,
} }
@ -136,8 +142,8 @@ pub struct AdfParser<'a> {
impl Default for AdfParser<'_> { impl Default for AdfParser<'_> {
fn default() -> Self { fn default() -> Self {
AdfParser { AdfParser {
namelist: Rc::new(RefCell::new(Vec::new())), namelist: Arc::new(RwLock::new(Vec::new())),
dict: Rc::new(RefCell::new(HashMap::new())), dict: Arc::new(RwLock::new(HashMap::new())),
formulae: RefCell::new(Vec::new()), formulae: RefCell::new(Vec::new()),
formulaname: RefCell::new(Vec::new()), formulaname: RefCell::new(Vec::new()),
} }
@ -176,8 +182,14 @@ where
fn parse_statement(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> { fn parse_statement(&'a self) -> impl FnMut(&'a str) -> IResult<&'a str, ()> {
|input| { |input| {
let mut dict = self.dict.borrow_mut(); let mut dict = self
let mut namelist = self.namelist.borrow_mut(); .dict
.write()
.expect("RwLock of dict could not get write access");
let mut namelist = self
.namelist
.write()
.expect("RwLock of namelist could not get write access");
let (remain, statement) = let (remain, statement) =
terminated(AdfParser::statement, terminated(tag("."), multispace0))(input)?; terminated(AdfParser::statement, terminated(tag("."), multispace0))(input)?;
if !dict.contains_key(statement) { if !dict.contains_key(statement) {
@ -200,16 +212,31 @@ where
} }
} }
impl<'a> AdfParser<'a> {
/// Creates a new parser, utilising the already existing [VarContainer]
pub fn with_var_container(var_container: VarContainer) -> AdfParser<'a> {
AdfParser {
namelist: var_container.names(),
dict: var_container.mappings(),
formulae: RefCell::new(Vec::new()),
formulaname: RefCell::new(Vec::new()),
}
}
}
impl AdfParser<'_> { impl AdfParser<'_> {
/// after an update to the namelist, all indizes are updated /// after an update to the namelist, all indizes are updated
fn regenerate_indizes(&self) { fn regenerate_indizes(&self) {
self.namelist self.namelist
.as_ref() .read()
.borrow() .expect("ReadLock on namelist failed")
.iter() .iter()
.enumerate() .enumerate()
.for_each(|(i, elem)| { .for_each(|(i, elem)| {
self.dict.as_ref().borrow_mut().insert(elem.clone(), i); self.dict
.write()
.expect("WriteLock on dict failed")
.insert(elem.clone(), i);
}); });
} }
@ -217,7 +244,10 @@ impl AdfParser<'_> {
/// Results, which got used before might become corrupted. /// Results, which got used before might become corrupted.
/// Ensure that all used data is physically copied. /// Ensure that all used data is physically copied.
pub fn varsort_lexi(&self) -> &Self { pub fn varsort_lexi(&self) -> &Self {
self.namelist.as_ref().borrow_mut().sort_unstable(); self.namelist
.write()
.expect("WriteLock on namelist failed")
.sort_unstable();
self.regenerate_indizes(); self.regenerate_indizes();
self self
} }
@ -227,8 +257,8 @@ impl AdfParser<'_> {
/// Ensure that all used data is physically copied. /// Ensure that all used data is physically copied.
pub fn varsort_alphanum(&self) -> &Self { pub fn varsort_alphanum(&self) -> &Self {
self.namelist self.namelist
.as_ref() .write()
.borrow_mut() .expect("WriteLock on namelist failed")
.string_sort_unstable(natural_lexical_cmp); .string_sort_unstable(natural_lexical_cmp);
self.regenerate_indizes(); self.regenerate_indizes();
self self
@ -343,14 +373,18 @@ impl AdfParser<'_> {
/// Allows insight of the number of parsed statements. /// Allows insight of the number of parsed statements.
pub fn dict_size(&self) -> usize { pub fn dict_size(&self) -> usize {
//self.dict.borrow().len() //self.dict.borrow().len()
self.dict.as_ref().borrow().len() self.dict.read().expect("ReadLock on dict failed").len()
} }
/// Returns the number-representation and position of a given statement in string-representation. /// Returns the number-representation and position of a given statement in string-representation.
/// ///
/// Will return [None] if the string does no occur in the dictionary. /// Will return [None] if the string does no occur in the dictionary.
pub fn dict_value(&self, value: &str) -> Option<usize> { pub fn dict_value(&self, value: &str) -> Option<usize> {
self.dict.as_ref().borrow().get(value).copied() self.dict
.read()
.expect("ReadLock on dict failed")
.get(value)
.copied()
} }
/// Returns the acceptance condition of a statement at the given position. /// Returns the acceptance condition of a statement at the given position.
@ -360,12 +394,17 @@ impl AdfParser<'_> {
self.formulae.borrow().get(idx).cloned() self.formulae.borrow().get(idx).cloned()
} }
pub(crate) fn dict_rc_refcell(&self) -> Rc<RefCell<HashMap<String, usize>>> { pub(crate) fn dict(&self) -> Arc<RwLock<HashMap<String, usize>>> {
Rc::clone(&self.dict) Arc::clone(&self.dict)
} }
pub(crate) fn namelist_rc_refcell(&self) -> Rc<RefCell<Vec<String>>> { pub(crate) fn namelist(&self) -> Arc<RwLock<Vec<String>>> {
Rc::clone(&self.namelist) Arc::clone(&self.namelist)
}
/// Returns a [`VarContainer`][crate::datatypes::adf::VarContainer] which allows to access the variable information gathered by the parser
pub fn var_container(&self) -> VarContainer {
VarContainer::from_parser(self.namelist(), self.dict())
} }
pub(crate) fn formula_count(&self) -> usize { pub(crate) fn formula_count(&self) -> usize {
@ -379,8 +418,8 @@ impl AdfParser<'_> {
.map(|name| { .map(|name| {
*self *self
.dict .dict
.as_ref() .read()
.borrow() .expect("ReadLock on dict failed")
.get(name) .get(name)
.expect("Dictionary should contain all the used formulanames") .expect("Dictionary should contain all the used formulanames")
}) })