fix admissibility, unify with verification

This commit is contained in:
Malte Tammena 2023-12-07 15:13:15 +01:00
parent 7b24b2b96a
commit 328cd79636
7 changed files with 213 additions and 205 deletions

View file

@ -4,31 +4,111 @@ use crate::{
aba::{inference_helper, Aba, Inference}, aba::{inference_helper, Aba, Inference},
clauses::{Atom, Clause, ClauseList}, clauses::{Atom, Clause, ClauseList},
literal::{InferenceAtom, InferenceAtomHelper, IntoLiteral}, literal::{InferenceAtom, InferenceAtomHelper, IntoLiteral},
mapper::Mapper,
}; };
use super::{LoopControl, MultishotProblem}; use super::{LoopControl, MultishotProblem, Problem, SolverState};
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Admissibility<A: Atom> { pub struct Admissibility<A: Atom> {
found: Vec<HashSet<A>>, found: Vec<HashSet<A>>,
} }
pub struct VerifyAdmissibility<A: Atom> {
pub assumptions: Vec<A>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct SetInference<A: Atom>(A); pub struct SetInference<A: Atom>(A);
#[derive(Debug)] #[derive(Debug)]
pub struct SetInferenceHelper<A: Atom>(usize, A); pub struct SetInferenceHelper<A: Atom>(usize, A);
fn initial_clauses<A: Atom>(aba: &Aba<A>) -> ClauseList {
let mut clauses = vec![];
// Create inference for the problem set
inference_helper::<SetInference<_>, _>(aba).collect_into(&mut clauses);
// Attack the inference of the aba, if an attack exists
for (assumption, inverse) in &aba.inverses {
[
// For any assumption `a` and it's inverse `b`:
// Inference(a) <=> not SetInference(b)
Clause::from(vec![
Inference::new(assumption.clone()).pos(),
SetInference::new(inverse.clone()).pos(),
]),
Clause::from(vec![
Inference::new(assumption.clone()).neg(),
SetInference::new(inverse.clone()).neg(),
]),
// Prevent attacks from the opponent to the selected set
// For any assumption `a` and it's inverse `b`:
// Inference(b) and SetInference(a) => bottom
Clause::from(vec![
Inference::new(inverse.clone()).neg(),
SetInference::new(assumption.clone()).neg(),
]),
// Prevent self-attacks
// For any assumption `a` and it's inverse `b`:
// SetInference(a) and SetInference(b) => bottom
Clause::from(vec![
SetInference::new(assumption.clone()).neg(),
SetInference::new(inverse.clone()).neg(),
]),
]
.into_iter()
.collect_into(&mut clauses);
}
clauses
}
fn construct_found_set<A: Atom>(state: SolverState<'_, A>) -> HashSet<A> {
state
.aba
.inverses
.keys()
.filter_map(|assumption| {
let literal = SetInference::new(assumption.clone()).pos();
let raw = state.map.get_raw(&literal)?;
match state.solver.value(raw) {
Some(true) => Some(assumption.clone()),
_ => None,
}
})
.collect()
}
impl<A: Atom> Problem<A> for Admissibility<A> {
type Output = HashSet<A>;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList {
let mut clauses = initial_clauses(aba);
// Prevent the empty set
let no_empty_set: Clause = aba
.inverses
.keys()
.map(|assumption| SetInference::new(assumption.clone()).pos())
.collect();
clauses.push(no_empty_set);
clauses
}
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output {
if state.sat_result {
construct_found_set(state)
} else {
HashSet::new()
}
}
}
impl<A: Atom> MultishotProblem<A> for Admissibility<A> { impl<A: Atom> MultishotProblem<A> for Admissibility<A> {
type Output = Vec<HashSet<A>>; type Output = Vec<HashSet<A>>;
fn additional_clauses(&self, aba: &Aba<A>, iteration: usize) -> ClauseList { fn additional_clauses(&self, aba: &Aba<A>, iteration: usize) -> ClauseList {
match iteration { match iteration {
0 => { 0 => {
let mut clauses = vec![]; let mut clauses = initial_clauses(aba);
// Create inference for the problem set
inference_helper::<SetInference<_>, _>(aba).collect_into(&mut clauses);
// Prevent the empty set // Prevent the empty set
let no_empty_set: Clause = aba let no_empty_set: Clause = aba
.inverses .inverses
@ -36,50 +116,6 @@ impl<A: Atom> MultishotProblem<A> for Admissibility<A> {
.map(|assumption| SetInference::new(assumption.clone()).pos()) .map(|assumption| SetInference::new(assumption.clone()).pos())
.collect(); .collect();
clauses.push(no_empty_set); clauses.push(no_empty_set);
// Attack the inference of the aba, if an attack exists
for (assumption, inverse) in &aba.inverses {
[
// For any assumption `a` and it's inverse `b`:
// Inference(a) <=> not SetInference(b) and not SetInference(a)
Clause::from(vec![
Inference::new(assumption.clone()).neg(),
SetInference::new(inverse.clone()).neg(),
]),
Clause::from(vec![
Inference::new(assumption.clone()).neg(),
SetInference::new(assumption.clone()).neg(),
]),
Clause::from(vec![
SetInference::new(inverse.clone()).pos(),
SetInference::new(assumption.clone()).pos(),
Inference::new(assumption.clone()).pos(),
]),
// Prevent attacks from the opponent to the selected set
// For any assumption `a` and it's inverse `b`:
// Inference(b) and SetInference(a) => bottom
Clause::from(vec![
Inference::new(inverse.clone()).neg(),
SetInference::new(assumption.clone()).neg(),
]),
// Prevent self-attacks
// For any assumption `a` and it's inverse `b`:
// SetInference(a) and SetInference(b) => bottom
Clause::from(vec![
SetInference::new(assumption.clone()).neg(),
SetInference::new(inverse.clone()).neg(),
]),
// Prevent attacks from the set to the opponent
// For any assumption `a` and it's inverse `b`:
// Inference(a) and SetInference(b) => bottom
Clause::from(vec![
Inference::new(assumption.clone()).neg(),
SetInference::new(inverse.clone()).neg(),
]),
]
.into_iter()
.collect_into(&mut clauses);
}
clauses clauses
} }
idx => { idx => {
@ -103,24 +139,19 @@ impl<A: Atom> MultishotProblem<A> for Admissibility<A> {
} }
} }
fn feedback( fn feedback(&mut self, state: SolverState<'_, A>) -> LoopControl {
&mut self, if !state.sat_result {
aba: &Aba<A>,
sat_result: bool,
solver: &cadical::Solver,
map: &Mapper,
) -> LoopControl {
if !sat_result {
return LoopControl::Stop; return LoopControl::Stop;
} }
// TODO: Somehow query the mapper about things instead of this // TODO: Somehow query the mapper about things instead of this
let found = aba let found = state
.aba
.inverses .inverses
.keys() .keys()
.filter_map(|assumption| { .filter_map(|assumption| {
let literal = SetInference::new(assumption.clone()).pos(); let literal = SetInference::new(assumption.clone()).pos();
let raw = map.get_raw(&literal)?; let raw = state.map.get_raw(&literal)?;
match solver.value(raw) { match state.solver.value(raw) {
Some(true) => Some(assumption.clone()), Some(true) => Some(assumption.clone()),
_ => None, _ => None,
} }
@ -132,9 +163,7 @@ impl<A: Atom> MultishotProblem<A> for Admissibility<A> {
fn construct_output( fn construct_output(
mut self, mut self,
_aba: &Aba<A>, _state: SolverState<'_, A>,
_sat_result: bool,
_solver: &cadical::Solver,
_total_iterations: usize, _total_iterations: usize,
) -> Self::Output { ) -> Self::Output {
// Re-Add the empty set // Re-Add the empty set
@ -143,6 +172,33 @@ impl<A: Atom> MultishotProblem<A> for Admissibility<A> {
} }
} }
impl<A: Atom> Problem<A> for VerifyAdmissibility<A> {
type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> crate::clauses::ClauseList {
let mut clauses = initial_clauses(aba);
// Force inference on all members of the set
for assumption in aba.assumptions() {
let inf = SetInference::new(assumption.clone());
if self.assumptions.contains(assumption) {
clauses.push(Clause::from(vec![inf.pos()]))
} else {
clauses.push(Clause::from(vec![inf.neg()]))
}
}
clauses
}
fn construct_output(self, state: SolverState<'_, A>) -> Self::Output {
state.sat_result
}
fn check(&self, aba: &Aba<A>) -> bool {
// Make sure that every assumption is part of the ABA
self.assumptions.iter().all(|a| aba.contains_assumption(a))
}
}
impl<A: Atom> InferenceAtom<A> for SetInference<A> { impl<A: Atom> InferenceAtom<A> for SetInference<A> {
type Helper = SetInferenceHelper<A>; type Helper = SetInferenceHelper<A>;

View file

@ -1,12 +1,10 @@
use cadical::Solver;
use crate::{ use crate::{
aba::{Aba, Inference, Inverse}, aba::{Aba, Inference, Inverse},
clauses::{Clause, ClauseList}, clauses::{Clause, ClauseList},
literal::{InferenceAtom, IntoLiteral}, literal::{InferenceAtom, IntoLiteral},
}; };
use super::Problem; use super::{Problem, SolverState};
pub struct ConflictFreeness { pub struct ConflictFreeness {
pub assumptions: Vec<char>, pub assumptions: Vec<char>,
@ -44,8 +42,8 @@ impl Problem<char> for ConflictFreeness {
clauses clauses
} }
fn construct_output(self, sat_result: bool, _: &Aba<char>, _: &Solver) -> Self::Output { fn construct_output(self, state: SolverState<'_, char>) -> Self::Output {
sat_result state.sat_result
} }
fn check(&self, aba: &Aba<char>) -> bool { fn check(&self, aba: &Aba<char>) -> bool {

View file

@ -10,7 +10,6 @@ use super::Aba;
pub mod admissibility; pub mod admissibility;
pub mod conflict_free; pub mod conflict_free;
pub mod verify_admissibility;
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LoopControl { pub enum LoopControl {
@ -18,10 +17,17 @@ pub enum LoopControl {
Stop, Stop,
} }
pub struct SolverState<'a, A: Atom + 'a> {
aba: &'a Aba<A>,
sat_result: bool,
solver: &'a Solver,
map: &'a Mapper,
}
pub trait Problem<A: Atom> { pub trait Problem<A: Atom> {
type Output; type Output;
fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList; fn additional_clauses(&self, aba: &Aba<A>) -> ClauseList;
fn construct_output(self, sat_result: bool, aba: &Aba<A>, solver: &Solver) -> Self::Output; fn construct_output(self, state: SolverState<'_, A>) -> Self::Output;
fn check(&self, _aba: &Aba<A>) -> bool { fn check(&self, _aba: &Aba<A>) -> bool {
true true
@ -31,20 +37,8 @@ pub trait Problem<A: Atom> {
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<A>, iteration: usize) -> ClauseList;
fn feedback( fn feedback(&mut self, state: SolverState<'_, A>) -> LoopControl;
&mut self, fn construct_output(self, state: SolverState<'_, A>, total_iterations: usize) -> Self::Output;
aba: &Aba<A>,
sat_result: bool,
solver: &Solver,
map: &Mapper,
) -> LoopControl;
fn construct_output(
self,
aba: &Aba<A>,
sat_result: bool,
solver: &Solver,
total_iterations: usize,
) -> Self::Output;
fn check(&self, _aba: &Aba<A>) -> bool { fn check(&self, _aba: &Aba<A>) -> bool {
true true
@ -62,7 +56,12 @@ pub fn solve<A: Atom, P: Problem<A>>(problem: P, aba: &Aba<A>) -> Result<P::Outp
map.as_raw_iter(&additional_clauses) map.as_raw_iter(&additional_clauses)
.for_each(|raw| sat.add_clause(raw)); .for_each(|raw| sat.add_clause(raw));
if let Some(sat_result) = sat.solve() { if let Some(sat_result) = sat.solve() {
Ok(problem.construct_output(sat_result, aba, &sat)) Ok(problem.construct_output(SolverState {
aba,
sat_result,
solver: &sat,
map: &map,
}))
} else { } else {
Err(Error::SatCallInterrupted) Err(Error::SatCallInterrupted)
} }
@ -94,11 +93,24 @@ pub fn multishot_solve<A: Atom, P: MultishotProblem<A>>(
let rec = map.reconstruct(&sat).collect::<Vec<_>>(); let rec = map.reconstruct(&sat).collect::<Vec<_>>();
eprintln!("{rec:#?}"); eprintln!("{rec:#?}");
} }
let control = problem.feedback(aba, sat_result, &sat, &map); let control = problem.feedback(SolverState {
aba,
sat_result,
solver: &sat,
map: &map,
});
if control == LoopControl::Stop { if control == LoopControl::Stop {
break sat_result; break sat_result;
} }
iteration += 1; iteration += 1;
}; };
Ok(problem.construct_output(aba, final_result, &sat, iteration)) Ok(problem.construct_output(
SolverState {
aba,
sat_result: final_result,
solver: &sat,
map: &map,
},
iteration,
))
} }

View file

@ -1,74 +0,0 @@
use crate::{
aba::{inference_helper, Aba, Inference, Inverse},
clauses::{Atom, Clause},
literal::{InferenceAtom, IntoLiteral},
};
use super::{admissibility::SetInference, Problem};
pub struct VerifyAdmissibility<A: Atom> {
pub assumptions: Vec<A>,
}
impl<A: Atom> Problem<A> for VerifyAdmissibility<A> {
type Output = bool;
fn additional_clauses(&self, aba: &Aba<A>) -> crate::clauses::ClauseList {
let mut clauses = vec![];
// Create inference for the problem set
inference_helper::<SetInference<_>, _>(aba).collect_into(&mut clauses);
// Force inference on all members of the set
aba.inverses
.keys()
.cloned()
.map(|assumption| {
if self.assumptions.contains(&assumption) {
Clause::from(vec![SetInference::new(assumption).pos()])
} else {
Clause::from(vec![SetInference::new(assumption).neg()])
}
})
.collect_into(&mut clauses);
// Attack the inference of the aba, if an attack exists
for elem in aba.universe() {
for assumption in self.assumptions.iter() {
clauses.push(Clause::from(vec![
SetInference::new(assumption.clone()).neg(),
Inverse {
from: assumption.clone(),
to: elem.clone(),
}
.neg(),
Inference::new(elem.clone()).neg(),
]))
}
for assumption in aba.assumptions() {
clauses.push(Clause::from(vec![
SetInference::new(assumption.clone()).neg(),
Inverse {
from: assumption.clone(),
to: elem.clone(),
}
.neg(),
SetInference::new(elem.clone()).neg(),
]))
}
}
clauses
}
fn construct_output(
self,
sat_result: bool,
_: &crate::aba::Aba<A>,
_: &cadical::Solver,
) -> Self::Output {
sat_result
}
fn check(&self, aba: &Aba<A>) -> bool {
// Make sure that every assumption is part of the ABA
self.assumptions.iter().all(|a| aba.contains_assumption(a))
}
}

View file

@ -21,6 +21,7 @@ pub struct Args {
pub file: PathBuf, pub file: PathBuf,
} }
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum Problems { pub enum Problems {
#[clap(visible_alias = "ve-ad")] #[clap(visible_alias = "ve-ad")]
@ -30,4 +31,7 @@ pub enum Problems {
}, },
#[clap(visible_alias = "ee-ad")] #[clap(visible_alias = "ee-ad")]
EnumerateAdmissibility, EnumerateAdmissibility,
/// Will only return the empty extension if no other is found
#[clap(visible_alias = "se-ad")]
SampleAdmissibility,
} }

View file

@ -3,7 +3,7 @@
use std::{collections::HashSet, fmt::Write, fs::read_to_string}; use std::{collections::HashSet, fmt::Write, fs::read_to_string};
use aba::problems::{admissibility::Admissibility, verify_admissibility::VerifyAdmissibility}; use aba::problems::admissibility::{Admissibility, VerifyAdmissibility};
use clap::Parser; use clap::Parser;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@ -35,10 +35,10 @@ mod tests;
fn __main() -> Result { fn __main() -> Result {
let args = args::Args::parse(); let args = args::Args::parse();
let content = read_to_string(&args.file).map_err(Error::OpeningAbaFile)?;
let aba = parser::aba_file(&content)?;
match args.problem { match args.problem {
args::Problems::VerifyAdmissibility { set } => { args::Problems::VerifyAdmissibility { set } => {
let content = read_to_string(&args.file).map_err(Error::OpeningAbaFile)?;
let aba = parser::aba_file(&content)?;
let result = aba::problems::solve( let result = aba::problems::solve(
VerifyAdmissibility { VerifyAdmissibility {
assumptions: set.into_iter().collect(), assumptions: set.into_iter().collect(),
@ -48,11 +48,13 @@ fn __main() -> Result {
print_bool_result(result); print_bool_result(result);
} }
args::Problems::EnumerateAdmissibility => { args::Problems::EnumerateAdmissibility => {
let content = read_to_string(&args.file).map_err(Error::OpeningAbaFile)?;
let aba = parser::aba_file(&content)?;
let result = aba::problems::multishot_solve(Admissibility::default(), &aba)?; let result = aba::problems::multishot_solve(Admissibility::default(), &aba)?;
print_witnesses_result(result)?; print_witnesses_result(result)?;
} }
args::Problems::SampleAdmissibility => {
let result = aba::problems::solve(Admissibility::default(), &aba)?;
print_witness_result(result)?;
}
} }
Ok(()) Ok(())
} }
@ -69,14 +71,16 @@ fn print_bool_result(result: bool) {
} }
fn print_witnesses_result(result: Vec<HashSet<u32>>) -> Result { fn print_witnesses_result(result: Vec<HashSet<u32>>) -> Result {
result.into_iter().try_for_each(|set| { result.into_iter().try_for_each(print_witness_result)
let set = set }
.into_iter()
.try_fold(String::new(), |mut list, num| -> Result<_, Error> { fn print_witness_result(result: HashSet<u32>) -> Result {
write!(list, " {num}")?; let set = result
Result::Ok(list) .into_iter()
})?; .try_fold(String::new(), |mut list, num| -> Result<_, Error> {
println!("w{set}"); write!(list, " {num}")?;
Ok(()) Result::Ok(list)
}) })?;
println!("w{set}");
Ok(())
} }

View file

@ -2,21 +2,25 @@ use std::collections::HashSet;
use crate::aba::{ use crate::aba::{
problems::{ problems::{
admissibility::Admissibility, conflict_free::ConflictFreeness, admissibility::{Admissibility, VerifyAdmissibility},
verify_admissibility::VerifyAdmissibility, conflict_free::ConflictFreeness,
}, },
Aba, Aba,
}; };
#[test] fn simple_aba_example_1() -> Aba<char> {
fn simple_conflict_free_verification() { Aba::new()
let aba = Aba::new()
.with_assumption('a', 'r') .with_assumption('a', 'r')
.with_assumption('b', 's') .with_assumption('b', 's')
.with_assumption('c', 't') .with_assumption('c', 't')
.with_rule('p', ['q', 'a']) .with_rule('p', ['q', 'a'])
.with_rule('q', []) .with_rule('q', [])
.with_rule('r', ['b', 'c']); .with_rule('r', ['b', 'c'])
}
#[test]
fn simple_conflict_free_verification() {
let aba = simple_aba_example_1();
let set_checks = vec![ let set_checks = vec![
(vec![], true), (vec![], true),
(vec!['a'], true), (vec!['a'], true),
@ -40,12 +44,7 @@ fn simple_conflict_free_verification() {
#[test] #[test]
fn simple_admissible_verification() { fn simple_admissible_verification() {
let aba = Aba::new() let aba = simple_aba_example_1();
.with_assumption('a', 'c')
.with_assumption('b', 'd')
.with_rule('c', vec!['a'])
.with_rule('c', vec!['b'])
.with_rule('d', vec!['a']);
let set_checks = vec![ let set_checks = vec![
(vec![], true), (vec![], true),
(vec!['a', 'b'], false), (vec!['a', 'b'], false),
@ -66,25 +65,34 @@ fn simple_admissible_verification() {
} }
#[test] #[test]
fn simple_admissible_thing() { fn simple_admissible_example() {
let aba = Aba::new() let aba = simple_aba_example_1();
.with_assumption('a', 'r') let expected: Vec<HashSet<char>> = vec![set!(), set!('b'), set!('b', 'c'), set!('c')];
.with_assumption('b', 's')
.with_assumption('c', 't')
.with_rule('p', vec!['q', 'a'])
.with_rule('q', vec![])
.with_rule('r', vec!['b', 'c']);
let expected: Vec<HashSet<char>> = vec![
set!(),
set!('a', 'b'),
set!('a', 'c'),
set!('b'),
set!('b', 'c'),
set!('c'),
];
let result = crate::aba::problems::multishot_solve(Admissibility::default(), &aba).unwrap(); let result = crate::aba::problems::multishot_solve(Admissibility::default(), &aba).unwrap();
for elem in &expected { for elem in &expected {
assert!(result.contains(elem)); assert!(
result.contains(elem),
"{elem:?} was expected but not found in result"
);
}
for elem in &result {
assert!(
expected.contains(elem),
"{elem:?} was found in the result, but is not expected!"
);
}
}
#[test]
fn simple_admissible_example_with_defense() {
let aba = simple_aba_example_1().with_rule('t', vec!['a', 'b']);
let expected: Vec<HashSet<char>> = vec![set!(), set!('a', 'b'), set!('b'), set!('b', 'c')];
let result = crate::aba::problems::multishot_solve(Admissibility::default(), &aba).unwrap();
for elem in &expected {
assert!(
result.contains(elem),
"{elem:?} was expected but not found in result"
);
} }
for elem in &result { for elem in &result {
assert!( assert!(