1
0
mirror of https://github.com/ellmau/adf-obdd.git synced 2025-12-19 09:29:36 +01:00
adf-obdd/lib/src/nogoods/threevalued.rs
Stefan Ellmauthaler 6ee643ae20
Add first 3 value nogood handling
Tests for conclusions drawn upon an no-good has been done
2023-03-14 17:13:45 +01:00

366 lines
12 KiB
Rust

//! Three-valued NoGoods
use roaring::RoaringBitmap;
use crate::datatypes::Term;
/// An interpretation
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Interpretation(NoGood);
impl From<NoGood> for Interpretation {
fn from(ng: NoGood) -> Self {
Interpretation(ng)
}
}
impl Interpretation {
/// Creates an [Interpretation], based on the given Vector of [Terms][Term]
pub fn from_term_vec(term_vec: &[Term]) -> Self {
Interpretation(NoGood::from_term_vec(term_vec))
}
fn invert(mut self) -> Self {
self.0 = self.0.invert();
self
}
fn active(&self) -> RoaringBitmap {
self.0.active()
}
fn hint(&self) -> RoaringBitmap {
((&self.0.can_be_true & &self.0.can_be_false)
| (&self.0.can_be_true & &self.0.can_be_und)
| (&self.0.can_be_false & &self.0.can_be_und))
& &self.active()
}
pub fn len(&self) -> usize {
self.0.len()
}
fn idx_as_triple(&self, idx: u32) -> (bool, bool, bool) {
self.0.idx_as_triple(idx)
}
pub fn conclude(mut self, ng: &NoGood) -> Conclusion {
log::debug!("conclude: {:?} with NoGood {:?}", self, ng);
if self.len() + 1 < ng.len() { // not matching
}
let interpretation_cred_active = self.active();
let interpretation_active = &interpretation_cred_active & (self.hint() ^ self.0.full());
let nogood_active = ng.active();
let implication = (&interpretation_active ^ &nogood_active) & &nogood_active;
let scep_match = self.sceptical_matches(ng);
match implication.len() {
// learning might be possible
1 => {
if (scep_match & interpretation_active).len() == nogood_active.len() - 1 {
let idx: u32 = implication
.min()
.expect("Checked that implication has a minimal value to be found.");
let (ng_t, ng_f, ng_u) = ng.idx_as_triple(idx);
let mut changes: u8 = 0;
if ng_t && self.0.can_be_true.remove(idx) {
changes += 1;
}
if ng_f && self.0.can_be_false.remove(idx) {
changes += 1;
}
if ng_u && self.0.can_be_und.remove(idx) {
changes += 1;
}
if self.0.can_be_true.contains(idx)
|| self.0.can_be_false.contains(idx)
|| self.0.can_be_und.contains(idx)
{
if changes > 0 {
Conclusion::Update(self)
} else {
Conclusion::NoChange(self)
}
} else {
// inconsistency
Conclusion::Inconsistent(self)
}
} else {
Conclusion::NoChange(self)
}
}
// ng-consistency check might be possible
_ => {
if (scep_match & interpretation_active).len() == nogood_active.len() {
Conclusion::Inconsistent(self)
} else {
Conclusion::NoChange(self)
}
}
}
}
fn credulous_matches(&self, other: &NoGood) -> RoaringBitmap {
let true_match = (&self.0.can_be_true ^ &other.can_be_true) ^ self.0.full();
let false_match = (&self.0.can_be_false ^ &other.can_be_false) ^ self.0.full();
let und_match = (&self.0.can_be_und ^ &other.can_be_und) ^ self.0.full();
true_match | false_match | und_match
}
fn sceptical_matches(&self, other: &NoGood) -> RoaringBitmap {
let true_match = (&self.0.can_be_true ^ &other.can_be_true) ^ self.0.full();
let false_match = (&self.0.can_be_false ^ &other.can_be_false) ^ self.0.full();
let und_match = (&self.0.can_be_und ^ &other.can_be_und) ^ self.0.full();
true_match & false_match & und_match
}
/// Returns the credulous and sceptical matches of the [Interpretation] and a given [NoGood]
fn matches(&self, other: &NoGood) -> (RoaringBitmap, RoaringBitmap) {
let true_match = (&self.0.can_be_true ^ &other.can_be_true) ^ self.0.full();
let false_match = (&self.0.can_be_false ^ &other.can_be_false) ^ self.0.full();
let und_match = (&self.0.can_be_und ^ &other.can_be_und) ^ self.0.full();
(
&true_match | &false_match | &und_match,
true_match & false_match & und_match,
)
}
}
#[derive(Debug)]
pub enum Conclusion {
Update(Interpretation),
NoChange(Interpretation),
Inconsistent(Interpretation),
}
impl Conclusion {
fn consistent(self) -> Option<Interpretation> {
match self {
Conclusion::Update(val) => Some(val),
Conclusion::NoChange(val) => Some(val),
Conclusion::Inconsistent(_) => None,
}
}
}
/// Representation of an Interpretation with Hints about truth-values.
#[derive(Debug, Clone)]
pub struct NoGood {
can_be_true: RoaringBitmap,
can_be_false: RoaringBitmap,
can_be_und: RoaringBitmap,
size: u32,
}
impl Default for NoGood {
fn default() -> Self {
Self {
can_be_true: Default::default(),
can_be_false: Default::default(),
can_be_und: Default::default(),
size: Default::default(),
}
}
}
impl Eq for NoGood {}
impl PartialEq for NoGood {
fn eq(&self, other: &Self) -> bool {
(&self.can_be_true ^ &other.can_be_true).is_empty()
&& (&self.can_be_false ^ &other.can_be_false).is_empty()
&& (&self.can_be_und ^ &other.can_be_und).is_empty()
}
}
impl From<Interpretation> for NoGood {
fn from(interpretation: Interpretation) -> Self {
interpretation.0.invert()
}
}
impl NoGood {
fn invert(mut self) -> Self {
self.can_be_true ^= self.full();
self.can_be_false ^= self.full();
self.can_be_und ^= self.full();
self
}
fn idx_as_triple(&self, idx: u32) -> (bool, bool, bool) {
(
self.can_be_true.contains(idx),
self.can_be_false.contains(idx),
self.can_be_und.contains(idx),
)
}
fn full(&self) -> RoaringBitmap {
let mut result = RoaringBitmap::default();
result.insert_range(0..self.size);
result
}
/// Creates a [NoGood], based on the given Vector of [Terms][Term]
pub fn from_term_vec(term_vec: &[Term]) -> Self {
let mut result = Self {
can_be_true: RoaringBitmap::default(),
can_be_false: RoaringBitmap::default(),
can_be_und: RoaringBitmap::default(),
size: TryInto::<u32>::try_into(term_vec.len()).expect("no-good learner implementation is based on the assumption that only u32::MAX-many variables are in place"),
};
result.can_be_true.insert_range(0..result.size);
result.can_be_false.insert_range(0..result.size);
result.can_be_und.insert_range(0..result.size);
log::debug!("{:?}", result);
term_vec.iter().enumerate().for_each(|(idx, val)| {
let idx:u32 = idx.try_into().expect("no-good learner implementation is based on the assumption that only u32::MAX-many variables are in place");
if val.is_truth_value() {
log::trace!("idx {idx} val: {val:?}");
if val.is_true() {
result.can_be_false.remove(idx);
result.can_be_und.remove(idx);
}else{
result.can_be_true.remove(idx);
result.can_be_und.remove(idx);
}
}
});
log::debug!("{:?}", result);
result
}
/// Returns the number of set variables.
pub fn len(&self) -> usize {
self.active()
.len()
.try_into()
.expect("Expecting to be on a 64 bit system")
}
fn active(&self) -> RoaringBitmap {
(&self.can_be_true & &self.can_be_false & &self.can_be_und) ^ &self.full()
}
fn no_matches(&self, other: &NoGood) -> RoaringBitmap {
let no_true_match = &self.can_be_true ^ &other.can_be_true;
let no_false_match = &self.can_be_false ^ &other.can_be_false;
let no_und_match = &self.can_be_und ^ &other.can_be_und;
no_true_match | no_false_match | no_und_match
}
}
#[cfg(test)]
mod test {
use super::*;
use test_log::test;
#[test]
fn create_ng() {
let terms = vec![Term::TOP, Term(22), Term(13232), Term::BOT, Term::TOP];
let ng = NoGood::from_term_vec(&terms);
assert_eq!(terms.len(), 5);
assert_eq!(ng.size, 5);
log::debug!("{:?}", ng.active());
assert_eq!(ng.active().len(), 3);
assert_eq!(ng.idx_as_triple(0), (true, false, false));
assert_eq!(ng.idx_as_triple(1), (true, true, true));
assert_eq!(ng.idx_as_triple(2), (true, true, true));
assert_eq!(ng.idx_as_triple(3), (false, true, false));
assert_eq!(ng.idx_as_triple(4), (true, false, false));
}
#[test]
fn create_interpretation() {
let terms = vec![Term::TOP, Term(22), Term(13232), Term::BOT, Term::TOP];
let interpretation = Interpretation::from_term_vec(&terms);
assert_eq!(interpretation.active().len(), 3);
assert_eq!(interpretation.idx_as_triple(0), (true, false, false));
assert_eq!(interpretation.idx_as_triple(1), (true, true, true));
assert_eq!(interpretation.idx_as_triple(2), (true, true, true));
assert_eq!(interpretation.idx_as_triple(3), (false, true, false));
assert_eq!(interpretation.idx_as_triple(4), (true, false, false));
}
#[test]
fn conclude() {
let ng1 = Interpretation::from_term_vec(&[
Term::TOP,
Term(22),
Term::TOP,
Term::BOT,
Term::TOP,
Term(22),
]);
let ng2 = Interpretation::from_term_vec(&[
Term::TOP,
Term(22),
Term(13232),
Term::BOT,
Term::TOP,
Term(22),
]);
let ng3 = Interpretation::from_term_vec(&[
Term::TOP,
Term(22),
Term::BOT,
Term::BOT,
Term::TOP,
Term(22),
]);
let mut ng4 = ng3.clone();
ng4.0.can_be_und.insert(2);
ng4.0.can_be_false.remove(2);
let ng_too_big = Interpretation::from_term_vec(&[
Term::TOP,
Term(22),
Term::TOP,
Term::BOT,
Term::TOP,
Term::BOT,
]);
assert!(ng1.clone().conclude(&ng2.0).consistent().is_none());
let result = ng2.clone().conclude(&ng1.0).consistent().unwrap();
assert_eq!(result.idx_as_triple(0), (true, false, false));
assert_eq!(result.idx_as_triple(1), (true, true, true));
assert_eq!(result.idx_as_triple(2), (false, true, true));
assert_eq!(result.idx_as_triple(3), (false, true, false));
assert_eq!(result.idx_as_triple(4), (true, false, false));
let result = result.conclude(&ng3.0).consistent().unwrap();
assert_eq!(result.idx_as_triple(0), (true, false, false));
assert_eq!(result.idx_as_triple(1), (true, true, true));
assert_eq!(result.idx_as_triple(2), (false, false, true));
assert_eq!(result.idx_as_triple(3), (false, true, false));
assert_eq!(result.idx_as_triple(4), (true, false, false));
assert!(result.conclude(&ng4.0).consistent().is_none());
let result = ng2
.clone()
.conclude(&ng1.0)
.consistent()
.and_then(|i| i.conclude(&ng4.0).consistent())
.unwrap();
assert_eq!(result.idx_as_triple(0), (true, false, false));
assert_eq!(result.idx_as_triple(1), (true, true, true));
assert_eq!(result.idx_as_triple(2), (false, true, false));
assert_eq!(result.idx_as_triple(3), (false, true, false));
assert_eq!(result.idx_as_triple(4), (true, false, false));
log::debug!("{:?}", ng2.clone().conclude(&ng_too_big.0));
if let Conclusion::NoChange(interpretation) = ng2.clone().conclude(&ng_too_big.0) {
assert_eq!(interpretation, ng2);
} else {
panic!("test failed");
}
}
}