rewrite, simplify

This commit is contained in:
Malte Tammena 2024-03-13 22:26:04 +01:00
parent a1a779c404
commit 1ab554fc8e
10 changed files with 340 additions and 250 deletions

82
src/aba/debug.rs Normal file
View file

@ -0,0 +1,82 @@
#![cfg(test)]
use std::collections::{BTreeMap, HashMap, HashSet};
use super::{Aba, Num};
#[derive(Debug, Default, Clone)]
pub struct DebugAba {
aba: Aba,
forward_map: HashMap<char, Num>,
backward_map: BTreeMap<Num, char>,
next: Num,
}
impl DebugAba {
pub fn with_assumption(mut self, assumption: char, inverse: char) -> Self {
let assumption = self.forward(assumption);
let inverse = self.forward(inverse);
self.aba = self.aba.with_assumption(assumption, inverse);
self
}
pub fn with_rule<'n, B: IntoIterator<Item = char>>(mut self, head: char, body: B) -> Self {
let head = self.forward(head);
let body: Vec<_> = body
.into_iter()
.scan(&mut self, |aba, elem| {
let elem = aba.forward(elem);
Some(elem)
})
.collect();
self.aba = self.aba.with_rule(head, body);
self
}
pub fn aba(&self) -> &Aba {
&self.aba
}
pub fn forward_atom(&self, atom: char) -> Option<Num> {
self.forward_map.get(&atom).cloned()
}
pub fn forward_set(&self, set: HashSet<char>) -> Option<HashSet<Num>> {
set.into_iter()
.map(|atom| self.forward_atom(atom))
.collect()
}
pub fn forward_sets<S: IntoIterator<Item = HashSet<char>>>(
&self,
sets: S,
) -> Option<Vec<HashSet<Num>>> {
sets.into_iter().map(|set| self.forward_set(set)).collect()
}
pub fn backward_sets<S: IntoIterator<Item = HashSet<Num>>>(
&self,
sets: S,
) -> Option<Vec<HashSet<char>>> {
sets.into_iter()
.map(|set| {
set.into_iter()
.map(|atom| self.backward_map.get(&atom).cloned())
.collect()
})
.collect()
}
fn forward(&mut self, atom: char) -> Num {
match self.forward_map.get(&atom) {
Some(atom) => *atom,
None => {
let next = self.next;
self.next += 1;
self.forward_map.insert(atom, next);
self.backward_map.insert(next, atom);
next
}
}
}
}

View file

@ -4,12 +4,12 @@
//! //!
//! ## Example //! ## Example
//! ``` //! ```
//! # use aba2sat::aba::Aba; //! # use aba2sat::aba::debug::DebugAba;
//! # use aba2sat::aba::problems::solve; //! # use aba2sat::aba::problems::solve;
//! # use aba2sat::aba::problems::admissibility::VerifyAdmissibleExtension; //! # use aba2sat::aba::problems::admissibility::VerifyAdmissibleExtension;
//! let aba = //! let aba =
//! // Start with an empty framework //! // Start with an empty framework
//! Aba::default() //! DebugAba::default()
//! // Add an assumption 'a' with inverse 'p' //! // Add an assumption 'a' with inverse 'p'
//! .with_assumption('a', 'p') //! .with_assumption('a', 'p')
//! // Add an assumption 'b' with inverse 'q' //! // Add an assumption 'b' with inverse 'q'
@ -35,38 +35,44 @@ use crate::{
literal::{IntoLiteral, Literal, TheoryAtom}, literal::{IntoLiteral, Literal, TheoryAtom},
}; };
pub mod debug;
pub mod problems; pub mod problems;
pub type Rule<A> = (A, HashSet<A>);
pub type RuleList<A> = Vec<Rule<A>>;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Aba<A: Atom> {
pub rules: RuleList<A>,
pub inverses: HashMap<A, A>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Theory<A: Atom>(A); pub struct Theory(Num);
impl<A: Atom> From<A> for Theory<A> { impl From<Num> for Theory {
fn from(value: A) -> Self { fn from(value: Num) -> Self {
Self(value) Self(value)
} }
} }
impl<A: Atom> Aba<A> { pub type Rule<A> = (A, HashSet<A>);
pub fn with_assumption(mut self, assumption: A, inverse: A) -> Self { pub type RuleList<A> = Vec<Rule<A>>;
pub type Num = u32;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Aba {
rules: RuleList<Num>,
inverses: HashMap<Num, Num>,
}
impl Aba {
pub fn with_assumption(mut self, assumption: Num, inverse: Num) -> Self {
self.inverses.insert(assumption, inverse); self.inverses.insert(assumption, inverse);
self self
} }
pub fn with_rule<B: IntoIterator<Item = A>>(mut self, head: A, body: B) -> Self { pub fn with_rule<B: IntoIterator<Item = Num>>(mut self, head: Num, body: B) -> Self {
self.rules.push((head, body.into_iter().collect())); let mut body_trans = HashSet::new();
body.into_iter().for_each(|elem| {
body_trans.insert(elem);
});
self.rules.push((head, body_trans));
self self
} }
pub fn universe(&self) -> impl Iterator<Item = &A> { pub fn universe(&self) -> impl Iterator<Item = &Num> {
// List of all elements of our ABA, basically our L (universe) // List of all elements of our ABA, basically our L (universe)
self.inverses self.inverses
.keys() .keys()
@ -75,11 +81,11 @@ impl<A: Atom> Aba<A> {
.chain(self.rules.iter().map(|(head, _)| head)) .chain(self.rules.iter().map(|(head, _)| head))
} }
pub fn assumptions(&self) -> impl Iterator<Item = &A> { pub fn assumptions(&self) -> impl Iterator<Item = &Num> {
self.inverses.keys() self.inverses.keys()
} }
pub fn contains_assumption(&self, a: &A) -> bool { pub fn contains_assumption(&self, a: &Num) -> bool {
self.inverses.contains_key(a) self.inverses.contains_key(a)
} }
@ -120,7 +126,7 @@ impl<A: Atom> Aba<A> {
} }
if body.iter().all(|atom| reachable.contains(atom)) { if body.iter().all(|atom| reachable.contains(atom)) {
marked_any = true; marked_any = true;
reachable.insert(head.clone()); reachable.insert(*head);
} }
} }
if !marked_any { if !marked_any {
@ -135,29 +141,21 @@ impl<A: Atom> Aba<A> {
} }
fn derive_rule_clauses(&self) -> impl Iterator<Item = Clause> + '_ { fn derive_rule_clauses(&self) -> impl Iterator<Item = Clause> + '_ {
theory_helper::<Theory<_>, _>(self) theory_helper::<Theory>(self)
} }
fn rule_heads(&self) -> impl Iterator<Item = &A> + '_ { fn rule_heads(&self) -> impl Iterator<Item = &Num> + '_ {
self.rules.iter().map(|(head, _)| head) self.rules.iter().map(|(head, _)| head)
} }
fn has_assumption(&self, atom: &A) -> bool {
self.inverses.contains_key(atom)
}
fn has_element(&self, element: &A) -> bool {
self.universe().any(|e| element == e)
}
} }
fn body_to_clauses<I: TheoryAtom<A>, A: Atom>(head: Literal, body: &HashSet<A>) -> ClauseList { fn body_to_clauses<I: TheoryAtom>(head: Literal, body: &HashSet<Num>) -> ClauseList {
let mut clauses = vec![]; let mut clauses = vec![];
let mut left_implication: Clause = body.iter().map(|elem| I::new(elem.clone()).neg()).collect(); let mut left_implication: Clause = body.iter().map(|elem| I::new(*elem).neg()).collect();
left_implication.push(head.clone().positive()); left_implication.push(head.clone().positive());
clauses.push(left_implication); clauses.push(left_implication);
body.iter() body.iter()
.map(|elem| vec![head.clone().negative(), I::new(elem.clone()).pos()].into()) .map(|elem| vec![head.clone().negative(), I::new(*elem).pos()].into())
.collect_into(&mut clauses); .collect_into(&mut clauses);
clauses clauses
} }
@ -187,7 +185,7 @@ fn body_to_clauses<I: TheoryAtom<A>, A: Atom>(head: Literal, body: &HashSet<A>)
/// A lot of the overhead is due to the fact that multiple bodies are an option, if that's /// A lot of the overhead is due to the fact that multiple bodies are an option, if that's
/// not given for a head `p` we use the simplified translation logic where `p` is true iff /// not given for a head `p` we use the simplified translation logic where `p` is true iff
/// `bodies(p)` is true. /// `bodies(p)` is true.
pub fn theory_helper<I: TheoryAtom<A>, A: Atom>(aba: &Aba<A>) -> impl Iterator<Item = Clause> + '_ { pub fn theory_helper<I: TheoryAtom>(aba: &Aba) -> impl Iterator<Item = Clause> + '_ {
// The combined list of rules, such that every // The combined list of rules, such that every
// head is unique and possible contains a list of bodies // head is unique and possible contains a list of bodies
let mut rules_combined = let mut rules_combined =
@ -203,7 +201,7 @@ pub fn theory_helper<I: TheoryAtom<A>, A: Atom>(aba: &Aba<A>) -> impl Iterator<I
// such that it cannot be derived at all. This is to prevent the solver from // such that it cannot be derived at all. This is to prevent the solver from
// guessing this atom on it's own // guessing this atom on it's own
aba.universe() aba.universe()
.filter(|atom| !aba.has_assumption(atom)) .filter(|atom| !aba.contains_assumption(atom))
.filter(|atom| !rule_heads.contains(atom)) .filter(|atom| !rule_heads.contains(atom))
.map(|atom| (atom, vec![])) .map(|atom| (atom, vec![]))
.collect_into(&mut rules_combined); .collect_into(&mut rules_combined);
@ -214,10 +212,10 @@ pub fn theory_helper<I: TheoryAtom<A>, A: Atom>(aba: &Aba<A>) -> impl Iterator<I
.flat_map(|(head, bodies)| match &bodies[..] { .flat_map(|(head, bodies)| match &bodies[..] {
// No bodies, add a clause that prevents the head from accuring in the theory // No bodies, add a clause that prevents the head from accuring in the theory
[] => { [] => {
vec![Clause::from(vec![I::new(head.clone()).neg()])] vec![Clause::from(vec![I::new(*head).neg()])]
} }
// A single body only, this is equivalent to a head that can only be derived by a single rule // A single body only, this is equivalent to a head that can only be derived by a single rule
[body] => body_to_clauses::<I, _>(I::new(head.clone()).pos(), body), [body] => body_to_clauses::<I>(I::new(*head).pos(), body),
// n bodies, we'll need to take extra care to allow any number of bodies to derive this // n bodies, we'll need to take extra care to allow any number of bodies to derive this
// head without logic errors // head without logic errors
bodies => { bodies => {
@ -226,21 +224,18 @@ pub fn theory_helper<I: TheoryAtom<A>, A: Atom>(aba: &Aba<A>) -> impl Iterator<I
.iter() .iter()
.enumerate() .enumerate()
.flat_map(|(idx, body)| { .flat_map(|(idx, body)| {
body_to_clauses::<I, _>( body_to_clauses::<I>(TheoryHelper::<I, _>::new(idx, *head).pos(), body)
TheoryHelper::<I, _>::new(idx, head.clone()).pos(),
body,
)
}) })
.collect_into(&mut clauses); .collect_into(&mut clauses);
let helpers: Vec<_> = (0..bodies.len()) let helpers: Vec<_> = (0..bodies.len())
.map(|idx| TheoryHelper::<I, _>::new(idx, head.clone()).pos()) .map(|idx| TheoryHelper::<I, _>::new(idx, *head).pos())
.collect(); .collect();
let mut right_implification: Clause = helpers.iter().cloned().collect(); let mut right_implification: Clause = helpers.iter().cloned().collect();
right_implification.push(I::new(head.clone()).neg()); right_implification.push(I::new(*head).neg());
clauses.push(right_implification); clauses.push(right_implification);
helpers helpers
.into_iter() .into_iter()
.map(|helper| Clause::from(vec![I::new(head.clone()).pos(), helper.negative()])) .map(|helper| Clause::from(vec![I::new(*head).pos(), helper.negative()]))
.collect_into(&mut clauses); .collect_into(&mut clauses);
clauses clauses
} }
@ -248,13 +243,13 @@ pub fn theory_helper<I: TheoryAtom<A>, A: Atom>(aba: &Aba<A>) -> impl Iterator<I
} }
#[derive(Debug)] #[derive(Debug)]
struct TheoryHelper<T: TheoryAtom<A>, A: Atom> { struct TheoryHelper<T: TheoryAtom, A: Atom> {
_idx: usize, _idx: usize,
_atom: A, _atom: A,
inner: PhantomData<T>, inner: PhantomData<T>,
} }
impl<T: TheoryAtom<A>, A: Atom> TheoryHelper<T, A> { impl<T: TheoryAtom, A: Atom> TheoryHelper<T, A> {
fn new(idx: usize, atom: A) -> Self { fn new(idx: usize, atom: A) -> Self {
Self { Self {
_idx: idx, _idx: idx,

View file

@ -2,8 +2,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{
aba::{theory_helper, Aba, Theory}, aba::{theory_helper, Aba, Num, Theory},
clauses::{Atom, Clause, ClauseList}, clauses::{Clause, ClauseList},
error::Error, error::Error,
literal::{IntoLiteral, TheoryAtom}, literal::{IntoLiteral, TheoryAtom},
Result, Result,
@ -13,8 +13,8 @@ use super::{LoopControl, MultishotProblem, Problem, SetTheory, SolverState};
/// Compute all admissible extensions for an [`Aba`] /// Compute all admissible extensions for an [`Aba`]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct EnumerateAdmissibleExtensions<A: Atom> { pub struct EnumerateAdmissibleExtensions {
found: Vec<HashSet<A>>, found: Vec<HashSet<Num>>,
} }
/// Sample an admissible extensions from an [`Aba`]. /// Sample an admissible extensions from an [`Aba`].
@ -23,88 +23,86 @@ pub struct EnumerateAdmissibleExtensions<A: Atom> {
pub struct SampleAdmissibleExtension; pub struct SampleAdmissibleExtension;
/// Verify wether `assumptions` is an admissible extension of an [`Aba`] /// Verify wether `assumptions` is an admissible extension of an [`Aba`]
pub struct VerifyAdmissibleExtension<A: Atom> { pub struct VerifyAdmissibleExtension {
pub assumptions: Vec<A>, pub assumptions: HashSet<Num>,
} }
/// Decide whether `assumption` is credulously admissible in an [`Aba`] /// Decide whether `assumption` is credulously admissible in an [`Aba`]
pub struct DecideCredulousAdmissibility<A> { pub struct DecideCredulousAdmissibility {
pub element: A, pub element: Num,
} }
pub fn initial_admissibility_clauses<I: std::fmt::Debug + 'static + TheoryAtom<A>, A: Atom>( pub fn initial_admissibility_clauses<I: std::fmt::Debug + 'static + TheoryAtom>(
aba: &Aba<A>, aba: &Aba,
) -> ClauseList { ) -> ClauseList {
let mut clauses = vec![]; let mut clauses = vec![];
// Create inference for the problem set // Create inference for the problem set
theory_helper::<I, _>(aba).collect_into(&mut clauses); theory_helper::<I>(aba).collect_into(&mut clauses);
// Attack the inference of the aba, if an attack exists // Attack the inference of the aba, if an attack exists
for (assumption, inverse) in &aba.inverses { for (assumption, inverse) in &aba.inverses {
[ [
// For any assumption `a` and it's inverse `b`: // For any assumption `a` and it's inverse `b`:
// a in th(A) <=> b not in th(S) // a in th(A) <=> b not in th(S)
Clause::from(vec![ Clause::from(vec![
Theory::new(assumption.clone()).pos(), Theory::new(*assumption).pos(),
SetTheory::new(inverse.clone()).pos(), SetTheory::new(*inverse).pos(),
]), ]),
Clause::from(vec![ Clause::from(vec![
Theory::new(assumption.clone()).neg(), Theory::new(*assumption).neg(),
SetTheory::new(inverse.clone()).neg(), SetTheory::new(*inverse).neg(),
]), ]),
// Prevent attacks from the opponent to the selected set // Prevent attacks from the opponent to the selected set
// For any assumption `a` and it's inverse `b`: // For any assumption `a` and it's inverse `b`:
// b in th(A) and a in th(S) => bottom // b in th(A) and a in th(S) => bottom
Clause::from(vec![ Clause::from(vec![
Theory::new(inverse.clone()).neg(), Theory::new(*inverse).neg(),
SetTheory::new(assumption.clone()).neg(), SetTheory::new(*assumption).neg(),
]), ]),
// Prevent self-attacks // Prevent self-attacks
// For any assumption `a` and it's inverse `b`: // For any assumption `a` and it's inverse `b`:
// a in th(S) and b in th(S) => bottom // a in th(S) and b in th(S) => bottom
Clause::from(vec![ Clause::from(vec![
SetTheory::new(assumption.clone()).neg(), SetTheory::new(*assumption).neg(),
SetTheory::new(inverse.clone()).neg(), SetTheory::new(*inverse).neg(),
]), ]),
] ]
.into_iter() .into_iter()
.collect_into(&mut clauses); .collect_into(&mut clauses);
} }
clauses clauses
} }
fn construct_found_set<A: Atom>(state: SolverState<'_, A>) -> HashSet<A> { fn construct_found_set(state: SolverState<'_>) -> HashSet<Num> {
state state
.aba .aba
.inverses .assumptions()
.keys()
.filter_map(|assumption| { .filter_map(|assumption| {
let literal = SetTheory::new(assumption.clone()).pos(); let literal = SetTheory::new(*assumption).pos();
let raw = state.map.get_raw(&literal)?; let raw = state.map.get_raw(&literal)?;
match state.solver.value(raw) { match state.solver.value(raw) {
Some(true) => Some(assumption.clone()), Some(true) => Some(*assumption),
_ => None, _ => None,
} }
}) })
.collect() .collect()
} }
impl<A: Atom> Problem<A> for SampleAdmissibleExtension { impl Problem for SampleAdmissibleExtension {
type Output = HashSet<A>; type Output = HashSet<Num>;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList { fn additional_clauses(&self, aba: &Aba) -> ClauseList {
let mut clauses = initial_admissibility_clauses::<SetTheory<_>, _>(aba); let mut clauses = initial_admissibility_clauses::<SetTheory>(aba);
// Prevent the empty set // Prevent the empty set
let no_empty_set: Clause = aba let no_empty_set: Clause = aba
.inverses .inverses
.keys() .keys()
.map(|assumption| SetTheory::new(assumption.clone()).pos()) .map(|assumption| SetTheory::new(*assumption).pos())
.collect(); .collect();
clauses.push(no_empty_set); clauses.push(no_empty_set);
clauses clauses
} }
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output { fn construct_output(self, state: SolverState<'_>) -> Self::Output {
if state.sat_result { if state.sat_result {
construct_found_set(state) construct_found_set(state)
} else { } else {
@ -113,18 +111,18 @@ impl<A: Atom> Problem<A> for SampleAdmissibleExtension {
} }
} }
impl<A: Atom> MultishotProblem<A> for EnumerateAdmissibleExtensions<A> { impl MultishotProblem<Num> for EnumerateAdmissibleExtensions {
type Output = Vec<HashSet<A>>; type Output = Vec<HashSet<Num>>;
fn additional_clauses(&self, aba: &Aba<A>, iteration: usize) -> ClauseList { fn additional_clauses(&self, aba: &Aba, iteration: usize) -> ClauseList {
match iteration { match iteration {
0 => { 0 => {
let mut clauses = initial_admissibility_clauses::<SetTheory<_>, _>(aba); let mut clauses = initial_admissibility_clauses::<SetTheory>(aba);
// Prevent the empty set // Prevent the empty set
let no_empty_set: Clause = aba let no_empty_set: Clause = aba
.inverses .inverses
.keys() .keys()
.map(|assumption| SetTheory::new(assumption.clone()).pos()) .map(|assumption| SetTheory::new(*assumption).pos())
.collect(); .collect();
clauses.push(no_empty_set); clauses.push(no_empty_set);
clauses clauses
@ -135,13 +133,12 @@ impl<A: Atom> MultishotProblem<A> for EnumerateAdmissibleExtensions<A> {
// {-a, b, -c, -d, e, f} must be true // {-a, b, -c, -d, e, f} must be true
let just_found = &self.found[idx - 1]; let just_found = &self.found[idx - 1];
let new_clause = aba let new_clause = aba
.inverses .assumptions()
.keys()
.map(|assumption| { .map(|assumption| {
if just_found.contains(assumption) { if just_found.contains(assumption) {
SetTheory::new(assumption.clone()).neg() SetTheory::new(*assumption).neg()
} else { } else {
SetTheory::new(assumption.clone()).pos() SetTheory::new(*assumption).pos()
} }
}) })
.collect(); .collect();
@ -150,7 +147,7 @@ impl<A: Atom> MultishotProblem<A> for EnumerateAdmissibleExtensions<A> {
} }
} }
fn feedback(&mut self, state: SolverState<'_, A>) -> LoopControl { fn feedback(&mut self, state: SolverState<'_>) -> LoopControl {
if !state.sat_result { if !state.sat_result {
return LoopControl::Stop; return LoopControl::Stop;
} }
@ -160,10 +157,10 @@ impl<A: Atom> MultishotProblem<A> for EnumerateAdmissibleExtensions<A> {
.inverses .inverses
.keys() .keys()
.filter_map(|assumption| { .filter_map(|assumption| {
let literal = SetTheory::new(assumption.clone()).pos(); let literal = SetTheory::new(*assumption).pos();
let raw = state.map.get_raw(&literal)?; let raw = state.map.get_raw(&literal)?;
match state.solver.value(raw) { match state.solver.value(raw) {
Some(true) => Some(assumption.clone()), Some(true) => Some(*assumption),
_ => None, _ => None,
} }
}) })
@ -174,23 +171,26 @@ impl<A: Atom> MultishotProblem<A> for EnumerateAdmissibleExtensions<A> {
fn construct_output( fn construct_output(
mut self, mut self,
_state: SolverState<'_, A>, _state: SolverState<'_>,
_total_iterations: usize, _total_iterations: usize,
) -> Self::Output { ) -> Self::Output {
// Re-Add the empty set // Re-Add the empty set
self.found.push(HashSet::new()); self.found.push(HashSet::new());
self.found self.found
.into_iter()
.map(|set| set.into_iter().collect())
.collect()
} }
} }
impl<A: Atom> Problem<A> for VerifyAdmissibleExtension<A> { impl Problem for VerifyAdmissibleExtension {
type Output = bool; type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> crate::clauses::ClauseList { fn additional_clauses(&self, aba: &Aba) -> crate::clauses::ClauseList {
let mut clauses = initial_admissibility_clauses::<SetTheory<_>, _>(aba); let mut clauses = initial_admissibility_clauses::<SetTheory>(aba);
// Force inference on all members of the set // Force inference on all members of the set
for assumption in aba.assumptions() { for assumption in aba.assumptions() {
let inf = SetTheory::new(assumption.clone()); let inf = SetTheory::new(*assumption);
if self.assumptions.contains(assumption) { if self.assumptions.contains(assumption) {
clauses.push(Clause::from(vec![inf.pos()])) clauses.push(Clause::from(vec![inf.pos()]))
} else { } else {
@ -200,16 +200,16 @@ impl<A: Atom> Problem<A> for VerifyAdmissibleExtension<A> {
clauses clauses
} }
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output { fn construct_output(self, state: SolverState<'_>) -> Self::Output {
state.sat_result state.sat_result
} }
fn check(&self, aba: &Aba<A>) -> Result { fn check(&self, aba: &Aba) -> Result {
// Make sure that every assumption is part of the ABA // Make sure that every assumption is part of the ABA
match self match self
.assumptions .assumptions
.iter() .iter()
.find(|a| !aba.contains_assumption(a)) .find(|assumption| !aba.contains_assumption(assumption))
{ {
Some(assumption) => Err(Error::ProblemCheckFailed(format!( Some(assumption) => Err(Error::ProblemCheckFailed(format!(
"Assumption {assumption:?} not present in ABA framework" "Assumption {assumption:?} not present in ABA framework"
@ -219,21 +219,21 @@ impl<A: Atom> Problem<A> for VerifyAdmissibleExtension<A> {
} }
} }
impl<A: Atom> Problem<A> for DecideCredulousAdmissibility<A> { impl Problem for DecideCredulousAdmissibility {
type Output = bool; type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList { fn additional_clauses(&self, aba: &Aba) -> ClauseList {
let mut clauses = initial_admissibility_clauses::<SetTheory<_>, _>(aba); let mut clauses = initial_admissibility_clauses::<SetTheory>(aba);
clauses.push(Clause::from(vec![SetTheory(self.element.clone()).pos()])); clauses.push(Clause::from(vec![SetTheory(self.element).pos()]));
clauses clauses
} }
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output { fn construct_output(self, state: SolverState<'_>) -> Self::Output {
state.sat_result state.sat_result
} }
fn check(&self, aba: &Aba<A>) -> Result { fn check(&self, aba: &Aba) -> Result {
if aba.has_assumption(&self.element) { if aba.contains_assumption(&self.element) {
Ok(()) Ok(())
} else { } else {
Err(Error::ProblemCheckFailed(format!( Err(Error::ProblemCheckFailed(format!(

View file

@ -1,8 +1,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{
aba::{Aba, Theory}, aba::{Aba, Num, Theory},
clauses::{Atom, Clause, ClauseList}, clauses::{Clause, ClauseList},
error::Error, error::Error,
literal::{IntoLiteral, TheoryAtom}, literal::{IntoLiteral, TheoryAtom},
Result, Result,
@ -14,34 +14,34 @@ use super::{
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct EnumerateCompleteExtensions<A> { pub struct EnumerateCompleteExtensions {
found: Vec<HashSet<A>>, found: Vec<HashSet<Num>>,
} }
/// Decide whether `assumption` is credulously complete in an [`Aba`] /// Decide whether `assumption` is credulously complete in an [`Aba`]
pub struct DecideCredulousComplete<A> { pub struct DecideCredulousComplete {
pub element: A, pub element: Num,
} }
fn initial_complete_clauses<A: Atom>(aba: &Aba<A>) -> ClauseList { fn initial_complete_clauses(aba: &Aba) -> ClauseList {
// Take everything from admissibility // Take everything from admissibility
let mut clauses = initial_admissibility_clauses::<SetTheory<_>, _>(aba); let mut clauses = initial_admissibility_clauses::<SetTheory>(aba);
// Additional complete logic // Additional complete logic
for (assumption, inverse) in &aba.inverses { for (assumption, inverse) in &aba.inverses {
// For any assumption `a` and it's inverse `b`: // For any assumption `a` and it's inverse `b`:
// b not in th(A) => a in th(S) // b not in th(A) => a in th(S)
clauses.push(Clause::from(vec![ clauses.push(Clause::from(vec![
Theory(inverse.clone()).pos(), Theory(*inverse).pos(),
SetTheory(assumption.clone()).pos(), SetTheory(*assumption).pos(),
])); ]));
} }
clauses clauses
} }
impl<A: Atom> MultishotProblem<A> for EnumerateCompleteExtensions<A> { impl MultishotProblem<Num> for EnumerateCompleteExtensions {
type Output = Vec<HashSet<A>>; type Output = Vec<HashSet<Num>>;
fn additional_clauses(&self, aba: &Aba<A>, iteration: usize) -> ClauseList { fn additional_clauses(&self, aba: &Aba, iteration: usize) -> ClauseList {
match iteration { match iteration {
0 => initial_complete_clauses(aba), 0 => initial_complete_clauses(aba),
idx => { idx => {
@ -50,13 +50,12 @@ impl<A: Atom> MultishotProblem<A> for EnumerateCompleteExtensions<A> {
// {-a, b, -c, -d, e, f} must be true // {-a, b, -c, -d, e, f} must be true
let just_found = &self.found[idx - 1]; let just_found = &self.found[idx - 1];
let new_clause = aba let new_clause = aba
.inverses .assumptions()
.keys()
.map(|assumption| { .map(|assumption| {
if just_found.contains(assumption) { if just_found.contains(assumption) {
SetTheory::new(assumption.clone()).neg() SetTheory::new(*assumption).neg()
} else { } else {
SetTheory::new(assumption.clone()).pos() SetTheory::new(*assumption).pos()
} }
}) })
.collect(); .collect();
@ -65,7 +64,7 @@ impl<A: Atom> MultishotProblem<A> for EnumerateCompleteExtensions<A> {
} }
} }
fn feedback(&mut self, state: SolverState<'_, A>) -> LoopControl { fn feedback(&mut self, state: SolverState<'_>) -> LoopControl {
if !state.sat_result { if !state.sat_result {
return LoopControl::Stop; return LoopControl::Stop;
} }
@ -75,10 +74,10 @@ impl<A: Atom> MultishotProblem<A> for EnumerateCompleteExtensions<A> {
.inverses .inverses
.keys() .keys()
.filter_map(|assumption| { .filter_map(|assumption| {
let literal = SetTheory::new(assumption.clone()).pos(); let literal = SetTheory::new(*assumption).pos();
let raw = state.map.get_raw(&literal)?; let raw = state.map.get_raw(&literal)?;
match state.solver.value(raw) { match state.solver.value(raw) {
Some(true) => Some(assumption.clone()), Some(true) => Some(*assumption),
_ => None, _ => None,
} }
}) })
@ -87,30 +86,26 @@ impl<A: Atom> MultishotProblem<A> for EnumerateCompleteExtensions<A> {
LoopControl::Continue LoopControl::Continue
} }
fn construct_output( fn construct_output(self, _state: SolverState<'_>, _total_iterations: usize) -> Self::Output {
self,
_state: SolverState<'_, A>,
_total_iterations: usize,
) -> Self::Output {
self.found self.found
} }
} }
impl<A: Atom> Problem<A> for DecideCredulousComplete<A> { impl Problem for DecideCredulousComplete {
type Output = bool; type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList { fn additional_clauses(&self, aba: &Aba) -> ClauseList {
let mut clauses = initial_complete_clauses(aba); let mut clauses = initial_complete_clauses(aba);
clauses.push(Clause::from(vec![SetTheory(self.element.clone()).pos()])); clauses.push(Clause::from(vec![SetTheory(self.element).pos()]));
clauses clauses
} }
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output { fn construct_output(self, state: SolverState<'_>) -> Self::Output {
state.sat_result state.sat_result
} }
fn check(&self, aba: &Aba<A>) -> Result { fn check(&self, aba: &Aba) -> Result {
if aba.has_element(&self.element) { if aba.contains_assumption(&self.element) {
Ok(()) Ok(())
} else { } else {
Err(Error::ProblemCheckFailed(format!( Err(Error::ProblemCheckFailed(format!(

View file

@ -1,8 +1,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{
aba::{Aba, Theory}, aba::{Aba, Num, Theory},
clauses::{Atom, Clause, ClauseList}, clauses::{Clause, ClauseList},
error::Error, error::Error,
literal::{IntoLiteral, TheoryAtom}, literal::{IntoLiteral, TheoryAtom},
Result, Result,
@ -10,18 +10,18 @@ use crate::{
use super::{Problem, SolverState}; use super::{Problem, SolverState};
pub struct ConflictFreeness<A: Atom> { pub struct ConflictFreeness {
pub assumptions: HashSet<A>, pub assumptions: HashSet<Num>,
} }
impl<A: Atom> Problem<A> for ConflictFreeness<A> { impl Problem for ConflictFreeness {
type Output = bool; type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList { fn additional_clauses(&self, aba: &Aba) -> ClauseList {
let mut clauses = vec![]; let mut clauses = vec![];
// Make sure that every assumption in our problem is inferred and every other not // Make sure that every assumption in our problem is inferred and every other not
for assumption in aba.assumptions() { for assumption in aba.assumptions() {
let theory = Theory::new(assumption.clone()); let theory = Theory::new(*assumption);
if self.assumptions.contains(assumption) { if self.assumptions.contains(assumption) {
clauses.push(vec![theory.pos()].into()) clauses.push(vec![theory.pos()].into())
} else { } else {
@ -30,23 +30,23 @@ impl<A: Atom> Problem<A> for ConflictFreeness<A> {
} }
for (assumption, inverse) in &aba.inverses { for (assumption, inverse) in &aba.inverses {
clauses.push(Clause::from(vec![ clauses.push(Clause::from(vec![
Theory::new(assumption.clone()).neg(), Theory::new(*assumption).neg(),
Theory::new(inverse.clone()).neg(), Theory::new(*inverse).neg(),
])); ]));
} }
clauses clauses
} }
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output { fn construct_output(self, state: SolverState<'_>) -> Self::Output {
state.sat_result state.sat_result
} }
fn check(&self, aba: &Aba<A>) -> Result { fn check(&self, aba: &Aba) -> Result {
// Make sure that every assumption is part of the ABA // Make sure that every assumption is part of the ABA
match self match self
.assumptions .assumptions
.iter() .iter()
.find(|a| !aba.contains_assumption(a)) .find(|assumption| !aba.contains_assumption(assumption))
{ {
Some(assumption) => Err(Error::ProblemCheckFailed(format!( Some(assumption) => Err(Error::ProblemCheckFailed(format!(
"Assumption {:?} not present in ABA framework", "Assumption {:?} not present in ABA framework",

View file

@ -6,7 +6,7 @@ use crate::{
mapper::Mapper, mapper::Mapper,
}; };
use super::Aba; use super::{Aba, Num};
pub mod admissibility; pub mod admissibility;
pub mod complete; pub mod complete;
@ -18,20 +18,20 @@ pub enum LoopControl {
Stop, Stop,
} }
pub struct SolverState<'a, A: Atom + 'a> { pub struct SolverState<'a> {
aba: &'a Aba<A>, aba: &'a Aba,
sat_result: bool, sat_result: bool,
solver: &'a Solver, solver: &'a Solver,
map: &'a Mapper, map: &'a Mapper,
} }
#[doc(notable_trait)] #[doc(notable_trait)]
pub trait Problem<A: Atom> { pub trait Problem {
type Output; type Output;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList; fn additional_clauses(&self, aba: &Aba) -> ClauseList;
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output; fn construct_output(self, state: SolverState<'_>) -> Self::Output;
fn check(&self, _aba: &Aba<A>) -> Result { fn check(&self, _aba: &Aba) -> Result {
Ok(()) Ok(())
} }
} }
@ -39,26 +39,26 @@ pub trait Problem<A: Atom> {
#[doc(notable_trait)] #[doc(notable_trait)]
pub trait MultishotProblem<A: Atom> { pub trait MultishotProblem<A: Atom> {
type Output; type Output;
fn additional_clauses(&self, aba: &Aba<A>, iteration: usize) -> ClauseList; fn additional_clauses(&self, aba: &Aba, iteration: usize) -> ClauseList;
fn feedback(&mut self, state: SolverState<'_, A>) -> LoopControl; fn feedback(&mut self, state: SolverState<'_>) -> LoopControl;
fn construct_output(self, state: SolverState<'_, A>, total_iterations: usize) -> Self::Output; fn construct_output(self, state: SolverState<'_>, total_iterations: usize) -> Self::Output;
fn check(&self, _aba: &Aba<A>) -> Result { fn check(&self, _aba: &Aba) -> Result {
Ok(()) Ok(())
} }
} }
/// *(Literal)* `A` is element of `th(S)` /// *(Literal)* `A` is element of `th(S)`
#[derive(Debug)] #[derive(Debug)]
pub struct SetTheory<A: Atom>(A); pub struct SetTheory(Num);
impl<A: Atom> From<A> for SetTheory<A> { impl From<Num> for SetTheory {
fn from(value: A) -> Self { fn from(value: Num) -> Self {
Self(value) Self(value)
} }
} }
pub fn solve<A: Atom, P: Problem<A>>(problem: P, mut aba: Aba<A>) -> Result<P::Output> { pub fn solve<P: Problem>(problem: P, mut aba: Aba) -> Result<P::Output> {
// Trim the ABA, this is always safe // Trim the ABA, this is always safe
aba.trim(); aba.trim();
// Let the problem perform additional checks before starting the solver // Let the problem perform additional checks before starting the solver
@ -101,7 +101,7 @@ pub fn solve<A: Atom, P: Problem<A>>(problem: P, mut aba: Aba<A>) -> Result<P::O
pub fn multishot_solve<A: Atom, P: MultishotProblem<A>>( pub fn multishot_solve<A: Atom, P: MultishotProblem<A>>(
mut problem: P, mut problem: P,
mut aba: Aba<A>, mut aba: Aba,
) -> Result<P::Output> { ) -> Result<P::Output> {
// Trim the ABA, this is always safe // Trim the ABA, this is always safe
aba.trim(); aba.trim();

View file

@ -3,7 +3,7 @@ use std::{
fmt::Debug, fmt::Debug,
}; };
use crate::clauses::Atom; use crate::aba::Num;
/// A Literal can be used in SAT [`Clause`](crate::clauses::Clause)s /// A Literal can be used in SAT [`Clause`](crate::clauses::Clause)s
#[derive(Clone)] #[derive(Clone)]
@ -42,11 +42,12 @@ impl<T: Any + Debug + Sized> IntoLiteral for T {
/// the theory of a (sub-)set of assumptions (`th(S)`) in an [`Aba`](crate::aba::Aba). /// the theory of a (sub-)set of assumptions (`th(S)`) in an [`Aba`](crate::aba::Aba).
/// ///
/// See [`crate::aba::theory_helper`]. /// See [`crate::aba::theory_helper`].
pub trait TheoryAtom<A: Atom>: Sized + IntoLiteral + std::fmt::Debug + 'static { pub trait TheoryAtom: Sized + IntoLiteral + std::fmt::Debug + 'static {
fn new(atom: A) -> Self; fn new(atom: Num) -> Self;
} }
impl<A: Atom, T: From<A> + Sized + IntoLiteral + std::fmt::Debug + 'static> TheoryAtom<A> for T {
fn new(atom: A) -> Self { impl<T: From<Num> + Sized + IntoLiteral + std::fmt::Debug + 'static> TheoryAtom for T {
fn new(atom: Num) -> Self {
Self::from(atom) Self::from(atom)
} }
} }

View file

@ -4,12 +4,15 @@
use std::{collections::HashSet, fmt::Write as WriteFmt, fs::read_to_string, io::Write as WriteIo}; use std::{collections::HashSet, fmt::Write as WriteFmt, fs::read_to_string, io::Write as WriteIo};
use aba::problems::{ use aba::{
admissibility::{ problems::{
DecideCredulousAdmissibility, EnumerateAdmissibleExtensions, SampleAdmissibleExtension, admissibility::{
VerifyAdmissibleExtension, DecideCredulousAdmissibility, EnumerateAdmissibleExtensions, SampleAdmissibleExtension,
VerifyAdmissibleExtension,
},
complete::{DecideCredulousComplete, EnumerateCompleteExtensions},
}, },
complete::{DecideCredulousComplete, EnumerateCompleteExtensions}, Num,
}; };
use clap::Parser; use clap::Parser;
@ -22,13 +25,6 @@ macro_rules! set {
}} }}
} }
#[cfg(test)]
macro_rules! map {
($($from:expr => $to:expr),*) => {{
vec![$(($from, $to)),*].into_iter().collect()
}}
}
mod aba; mod aba;
mod args; mod args;
mod clauses; mod clauses;
@ -88,7 +84,7 @@ fn main() -> Result {
__main().inspect_err(|why| eprintln!("Error: {why}")) __main().inspect_err(|why| eprintln!("Error: {why}"))
} }
impl IccmaFormattable for Vec<HashSet<u32>> { impl IccmaFormattable for Vec<HashSet<Num>> {
fn fmt_iccma(&self) -> Result<String> { fn fmt_iccma(&self) -> Result<String> {
let output = self let output = self
.iter() .iter()
@ -102,7 +98,7 @@ impl IccmaFormattable for Vec<HashSet<u32>> {
} }
} }
impl IccmaFormattable for HashSet<u32> { impl IccmaFormattable for HashSet<Num> {
fn fmt_iccma(&self) -> Result<String> { fn fmt_iccma(&self) -> Result<String> {
let set = self let set = self
.iter() .iter()

View file

@ -35,10 +35,10 @@ use nom::multi::fold_many0;
use nom::sequence::{preceded, terminated, tuple}; use nom::sequence::{preceded, terminated, tuple};
use nom::IResult; use nom::IResult;
use crate::aba::Aba; use crate::aba::{Aba, Num};
use crate::error::Result; use crate::error::Result;
pub fn aba_file(input: &str) -> Result<Aba<u32>> { pub fn aba_file(input: &str) -> Result<Aba> {
let (input, number_of_atoms) = p_line(input)?; let (input, number_of_atoms) = p_line(input)?;
let (_, aba) = all_consuming(aba)(input)?; let (_, aba) = all_consuming(aba)(input)?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -68,12 +68,12 @@ fn p_line(input: &str) -> IResult<&str, u32> {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum AbaLine { enum AbaLine {
Comment, Comment,
Assumption(u32), Assumption(Num),
Inverse(u32, u32), Inverse(Num, Num),
Rule(u32, HashSet<u32>), Rule(Num, HashSet<Num>),
} }
fn aba(input: &str) -> IResult<&str, Aba<u32>> { fn aba(input: &str) -> IResult<&str, Aba> {
let parse_aba_line = terminated(alt((assumption, comment, inverse, rule)), eol_or_eoi); let parse_aba_line = terminated(alt((assumption, comment, inverse, rule)), eol_or_eoi);
let collect_aba = fold_many0( let collect_aba = fold_many0(
parse_aba_line, parse_aba_line,
@ -133,15 +133,15 @@ fn eol_or_eoi(input: &str) -> IResult<&str, &str> {
alt((newline, eof))(input) alt((newline, eof))(input)
} }
fn atom(input: &str) -> IResult<&str, u32> { fn atom(input: &str) -> IResult<&str, Num> {
verify(complete::u32, |&num| num != 0)(input) map(verify(complete::u32, |&num| num != 0), |atom| atom as Num)(input)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use crate::aba::Aba; use crate::aba::{Aba, Num};
fn assert_parse<F, T>(parser: F, input: &str, expected: T) fn assert_parse<F, T>(parser: F, input: &str, expected: T)
where where
@ -149,7 +149,7 @@ mod tests {
T: std::fmt::Debug + PartialEq, T: std::fmt::Debug + PartialEq,
{ {
let (_rest, result) = let (_rest, result) =
all_consuming(parser)(input).expect(&format!("Failed to parse {input:?}")); all_consuming(parser)(input).unwrap_or_else(|_| panic!("Failed to parse {input:?}"));
assert_eq!(result, expected); assert_eq!(result, expected);
} }
@ -198,7 +198,7 @@ mod tests {
fn assumption() { fn assumption() {
use super::AbaLine::Assumption; use super::AbaLine::Assumption;
assert_parse(super::assumption, "a 1", Assumption(1)); assert_parse(super::assumption, "a 1", Assumption(1));
assert_parse(super::assumption, "a 4294967295", Assumption(u32::MAX)); assert_parse(super::assumption, "a 4294967295", Assumption(Num::MAX));
assert_parse(super::assumption, "a\t1", Assumption(1)); assert_parse(super::assumption, "a\t1", Assumption(1));
assert_parse(super::assumption, "a\t 1", Assumption(1)); assert_parse(super::assumption, "a\t 1", Assumption(1));
assert_parse(super::assumption, "a\t 1 ", Assumption(1)); assert_parse(super::assumption, "a\t 1 ", Assumption(1));
@ -243,10 +243,7 @@ mod tests {
r#"a 2 r#"a 2
c 2 1 c 2 1
r 1 2 3"#, r 1 2 3"#,
Aba { Aba::default().with_assumption(2, 1).with_rule(1, [2, 3]),
rules: vec![(1, set!(2, 3))],
inverses: map![2 => 1],
},
) )
} }
@ -262,10 +259,7 @@ r 1 2 3
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
res, res,
Aba { Aba::default().with_assumption(2, 1).with_rule(1, [2, 3])
rules: vec![(1, set!(2, 3))],
inverses: map![2 => 1],
}
) )
} }
} }

View file

@ -1,16 +1,16 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::aba::{ use crate::aba::{
debug::DebugAba,
problems::{ problems::{
admissibility::{EnumerateAdmissibleExtensions, VerifyAdmissibleExtension}, admissibility::{EnumerateAdmissibleExtensions, VerifyAdmissibleExtension},
complete::{DecideCredulousComplete, EnumerateCompleteExtensions}, complete::DecideCredulousComplete,
conflict_free::ConflictFreeness, conflict_free::ConflictFreeness,
}, },
Aba,
}; };
fn simple_aba_example_1() -> Aba<char> { fn simple_aba_example_1() -> DebugAba {
Aba::default() DebugAba::default()
.with_assumption('a', 'r') .with_assumption('a', 'r')
.with_assumption('b', 's') .with_assumption('b', 's')
.with_assumption('c', 't') .with_assumption('c', 't')
@ -35,11 +35,20 @@ fn simple_conflict_free_verification() {
set_checks set_checks
.into_iter() .into_iter()
.for_each(|(assumptions, expectation)| { .for_each(|(assumptions, expectation): (HashSet<char>, _)| {
eprintln!("Checking set {assumptions:?}"); eprintln!("Checking set {assumptions:?}");
let result = let translated = aba.forward_set(assumptions.clone()).unwrap();
crate::aba::problems::solve(ConflictFreeness { assumptions }, aba.clone()).unwrap(); let result = crate::aba::problems::solve(
assert!(result == expectation); ConflictFreeness {
assumptions: translated,
},
aba.aba().clone(),
)
.unwrap();
assert!(
result == expectation,
"Expected {expectation} from solver, but got {result} while checking {assumptions:?}"
);
}) })
} }
@ -47,17 +56,18 @@ fn simple_conflict_free_verification() {
fn simple_admissible_verification() { fn simple_admissible_verification() {
let aba = simple_aba_example_1(); let aba = simple_aba_example_1();
let set_checks = vec![ let set_checks = vec![
(vec![], true), (set![], true),
(vec!['a', 'b'], false), (set!['a', 'b'], false),
(vec!['a'], false), (set!['a'], false),
(vec!['b'], true), (set!['b'], true),
]; ];
set_checks set_checks
.into_iter() .into_iter()
.for_each(|(assumptions, expectation)| { .for_each(|(assumptions, expectation): (HashSet<char>, _)| {
eprintln!("Checking set {assumptions:?}"); eprintln!("Checking set {assumptions:?}");
let translated= aba.forward_set(assumptions.clone()).unwrap();
let result = let result =
crate::aba::problems::solve(VerifyAdmissibleExtension { assumptions: assumptions.clone() }, aba.clone()).unwrap(); crate::aba::problems::solve(VerifyAdmissibleExtension { assumptions: translated }, aba.aba().clone()).unwrap();
assert!( assert!(
result == expectation, result == expectation,
"Expected {expectation} from solver, but got {result} while checking {assumptions:?}" "Expected {expectation} from solver, but got {result} while checking {assumptions:?}"
@ -69,18 +79,20 @@ fn simple_admissible_verification() {
fn simple_admissible_example() { fn simple_admissible_example() {
let aba = simple_aba_example_1(); let aba = simple_aba_example_1();
let expected: Vec<HashSet<char>> = vec![set!(), set!('b'), set!('b', 'c'), set!('c')]; let expected: Vec<HashSet<char>> = vec![set!(), set!('b'), set!('b', 'c'), set!('c')];
let result = let result = crate::aba::problems::multishot_solve(
crate::aba::problems::multishot_solve(EnumerateAdmissibleExtensions::default(), aba) EnumerateAdmissibleExtensions::default(),
.unwrap(); aba.aba().clone(),
for elem in &expected { )
.unwrap();
for elem in aba.forward_sets(expected.clone()).unwrap() {
assert!( assert!(
result.contains(elem), result.contains(&elem),
"{elem:?} was expected but not found in result" "{elem:?} was expected but not found in result"
); );
} }
for elem in &result { for elem in aba.backward_sets(result).unwrap() {
assert!( assert!(
expected.contains(elem), expected.contains(&elem),
"{elem:?} was found in the result, but is not expected!" "{elem:?} was found in the result, but is not expected!"
); );
} }
@ -88,20 +100,29 @@ fn simple_admissible_example() {
#[test] #[test]
fn simple_admissible_example_with_defense() { fn simple_admissible_example_with_defense() {
let aba = simple_aba_example_1().with_rule('t', vec!['a', 'b']); let aba = DebugAba::default()
.with_assumption('a', 'r')
.with_assumption('b', 's')
.with_assumption('c', 't')
.with_rule('p', ['q', 'a'])
.with_rule('q', [])
.with_rule('r', ['b', 'c'])
.with_rule('t', vec!['a', 'b']);
let expected: Vec<HashSet<char>> = vec![set!(), set!('a', 'b'), set!('b'), set!('b', 'c')]; let expected: Vec<HashSet<char>> = vec![set!(), set!('a', 'b'), set!('b'), set!('b', 'c')];
let result = let result = crate::aba::problems::multishot_solve(
crate::aba::problems::multishot_solve(EnumerateAdmissibleExtensions::default(), aba) EnumerateAdmissibleExtensions::default(),
.unwrap(); aba.aba().clone(),
for elem in &expected { )
.unwrap();
for elem in aba.forward_sets(expected.clone()).unwrap() {
assert!( assert!(
result.contains(elem), result.contains(&elem),
"{elem:?} was expected but not found in result" "{elem:?} was expected but not found in result"
); );
} }
for elem in &result { for elem in aba.backward_sets(result).unwrap() {
assert!( assert!(
expected.contains(elem), expected.contains(&elem),
"{elem:?} was found in the result, but is not expected!" "{elem:?} was found in the result, but is not expected!"
); );
} }
@ -109,7 +130,7 @@ fn simple_admissible_example_with_defense() {
#[test] #[test]
fn simple_admissible_atomic() { fn simple_admissible_atomic() {
let aba = Aba::default() let aba = DebugAba::default()
.with_assumption('a', 'p') .with_assumption('a', 'p')
.with_assumption('b', 'q') .with_assumption('b', 'q')
.with_assumption('c', 'r') .with_assumption('c', 'r')
@ -117,18 +138,20 @@ fn simple_admissible_atomic() {
.with_rule('q', ['a', 'c']); .with_rule('q', ['a', 'c']);
let expected: Vec<HashSet<char>> = let expected: Vec<HashSet<char>> =
vec![set!(), set!('b'), set!('c'), set!('a', 'c'), set!('b', 'c')]; vec![set!(), set!('b'), set!('c'), set!('a', 'c'), set!('b', 'c')];
let result = let result = crate::aba::problems::multishot_solve(
crate::aba::problems::multishot_solve(EnumerateAdmissibleExtensions::default(), aba) EnumerateAdmissibleExtensions::default(),
.unwrap(); aba.aba().clone(),
for elem in &expected { )
.unwrap();
for elem in aba.forward_sets(expected.clone()).unwrap() {
assert!( assert!(
result.contains(elem), result.contains(&elem),
"{elem:?} was expected but not found in result" "{elem:?} was expected but not found in result"
); );
} }
for elem in &result { for elem in aba.backward_sets(result).unwrap() {
assert!( assert!(
expected.contains(elem), expected.contains(&elem),
"{elem:?} was found in the result, but is not expected!" "{elem:?} was found in the result, but is not expected!"
); );
} }
@ -137,25 +160,27 @@ fn simple_admissible_atomic() {
#[test] #[test]
fn a_chain_with_no_beginning() { fn a_chain_with_no_beginning() {
// found this while grinding against ASPforABA (5aa9201) // found this while grinding against ASPforABA (5aa9201)
let aba = Aba::default() let aba = DebugAba::default()
.with_assumption('a', 'b') .with_assumption('a', 'b')
.with_assumption('b', 'c') .with_assumption('b', 'c')
.with_rule('c', ['a', 'd']) .with_rule('c', ['a', 'd'])
.with_rule('d', ['c']); .with_rule('d', ['c']);
let expected: Vec<HashSet<char>> = vec![set!(), set!('b')]; let expected: Vec<HashSet<char>> = vec![set!(), set!('b')];
// 'a' cannot be defended against b since c is not derivable // 'a' cannot be defended against b since c is not derivable
let result = let result = crate::aba::problems::multishot_solve(
crate::aba::problems::multishot_solve(EnumerateAdmissibleExtensions::default(), aba) EnumerateAdmissibleExtensions::default(),
.unwrap(); aba.aba().clone(),
for elem in &expected { )
.unwrap();
for elem in aba.forward_sets(expected.clone()).unwrap() {
assert!( assert!(
result.contains(elem), result.contains(&elem),
"{elem:?} was expected but not found in result" "{elem:?} was expected but not found in result"
); );
} }
for elem in &result { for elem in aba.backward_sets(result).unwrap() {
assert!( assert!(
expected.contains(elem), expected.contains(&elem),
"{elem:?} was found in the result, but is not expected!" "{elem:?} was found in the result, but is not expected!"
); );
} }
@ -164,13 +189,15 @@ fn a_chain_with_no_beginning() {
#[test] #[test]
#[ignore] #[ignore]
fn loops_and_conflicts() { fn loops_and_conflicts() {
let aba = Aba::default() let aba = DebugAba::default()
.with_assumption('a', 'b') .with_assumption('a', 'b')
.with_rule('b', ['a']) .with_rule('b', ['a'])
.with_rule('b', ['c']) .with_rule('b', ['c'])
.with_rule('c', ['b']) .with_rule('c', ['b'])
.with_rule('d', ['b']); .with_rule('d', ['b']);
let element = aba.forward_atom('d').unwrap();
let result = let result =
crate::aba::problems::solve(DecideCredulousComplete { element: 'd' }, aba).unwrap(); crate::aba::problems::solve(DecideCredulousComplete { element }, aba.aba().clone())
.unwrap();
assert!(!result, "d cannot be credulous complete"); assert!(!result, "d cannot be credulous complete");
} }