use crate::cnf::{clause_lit, fact_lit, rule_consequent_clause};
use crate::report::{CoreItem, label};
use crate::sat;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use elenchus_compiler::{Compiled, Origin, Value};
pub(crate) fn retract_assumptions(c: &Compiled) -> Vec<CoreItem> {
if !c.facts.iter().any(|f| f.soft) {
return Vec::new();
}
let all = constructs(c);
let is_soft: Vec<bool> = (0..all.len())
.map(|i| i < c.facts.len() && c.facts[i].soft)
.collect();
let hard_only: Vec<bool> = is_soft.iter().map(|&s| !s).collect();
if !subset_is_sat(c.atoms.len(), &all, &hard_only) {
return Vec::new();
}
let mut active = vec![true; all.len()];
if subset_is_sat(c.atoms.len(), &all, &active) {
return Vec::new();
}
for i in 0..all.len() {
if active[i] && is_soft[i] {
active[i] = false;
if subset_is_sat(c.atoms.len(), &all, &active) {
active[i] = true; }
}
}
let mut core: Vec<CoreItem> = (0..all.len())
.filter(|&i| active[i] && is_soft[i])
.map(|i| {
let f = &c.facts[i];
let label = if matches!(f.value, Value::False) {
alloc::format!("NOT {}", label(c, f.atom))
} else {
label(c, f.atom)
};
CoreItem {
origin: f.origin.clone(),
label,
}
})
.collect();
core.sort_by_key(|it| key(&it.origin));
core
}
pub(crate) struct Construct {
origin: Origin,
label: String,
clauses: Vec<Vec<sat::SatLit>>,
}
pub(crate) fn same_origin(a: &Origin, b: &Origin) -> bool {
a.source == b.source && a.line == b.line && a.premise == b.premise && a.kind == b.kind
}
pub(crate) fn constructs(c: &Compiled) -> Vec<Construct> {
let mut out: Vec<Construct> = Vec::new();
for f in &c.facts {
out.push(Construct {
origin: f.origin.clone(),
label: label(c, f.atom),
clauses: vec![vec![fact_lit(f)]],
});
}
let mut premises: Vec<Construct> = Vec::new();
for clause in &c.clauses {
let lits: Vec<sat::SatLit> = clause.lits.iter().map(clause_lit).collect();
match premises
.iter_mut()
.find(|k| same_origin(&k.origin, &clause.origin))
{
Some(k) => k.clauses.push(lits),
None => premises.push(Construct {
label: clause.origin.premise.clone().unwrap_or_default(),
origin: clause.origin.clone(),
clauses: vec![lits],
}),
}
}
out.extend(premises);
for r in &c.rules {
let clauses = r
.consequent
.iter()
.map(|cons| rule_consequent_clause(r, cons))
.collect();
out.push(Construct {
label: r.origin.premise.clone().unwrap_or_default(),
origin: r.origin.clone(),
clauses,
});
}
out
}
pub(crate) fn subset_is_sat(num_vars: usize, all: &[Construct], active: &[bool]) -> bool {
let mut cnf = sat::Cnf::new(num_vars);
for (k, &keep) in all.iter().zip(active) {
if keep {
for cl in &k.clauses {
cnf.add_clause(cl.clone());
}
}
}
sat::solve(&cnf).is_some()
}
pub(crate) fn candidate_via_assumptions(c: &Compiled, all: &[Construct]) -> Vec<bool> {
let base = c.atoms.len();
let mut cnf = sat::Cnf::new(base + all.len());
let sel = |i: usize| (base + i) as sat::Var;
for (i, k) in all.iter().enumerate() {
let s_neg = sat::SatLit::negative(sel(i));
for cl in &k.clauses {
let mut lits = Vec::with_capacity(cl.len() + 1);
lits.push(s_neg);
lits.extend_from_slice(cl);
cnf.add_clause(lits);
}
}
let assumptions: Vec<sat::SatLit> = (0..all.len())
.map(|i| sat::SatLit::positive(sel(i)))
.collect();
let mut active = vec![false; all.len()];
match sat::solve_assuming(&cnf, &assumptions) {
sat::Solved::Unsat(core) => {
for lit in core {
let v = lit.var() as usize;
if v >= base {
active[v - base] = true;
}
}
}
sat::Solved::Sat(_) => active.iter_mut().for_each(|a| *a = true),
}
active
}
pub(crate) fn minimal_unsat_core(c: &Compiled) -> Vec<CoreItem> {
let all = constructs(c);
let mut active = candidate_via_assumptions(c, &all);
for i in 0..all.len() {
if active[i] {
active[i] = false;
if subset_is_sat(c.atoms.len(), &all, &active) {
active[i] = true; }
}
}
let mut core: Vec<CoreItem> = all
.iter()
.zip(&active)
.filter(|&(_, &keep)| keep)
.map(|(k, _)| CoreItem {
origin: k.origin.clone(),
label: k.label.clone(),
})
.collect();
core.sort_by_key(|it| key(&it.origin));
core
}
pub(crate) fn key(o: &Origin) -> (String, u32) {
(o.source.clone(), o.line)
}