use std::cell::RefCell;
use std::collections::HashSet;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::time::{Duration, Instant};
use rustsat::instances::Cnf;
#[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
use rustsat::solvers::{GetInternalStats, Solve, SolveIncremental, SolverResult};
use rustsat::types::{Assignment, Lit};
use tracing::{info, warn};
use std::sync::atomic::Ordering::Relaxed;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SolverBackend {
#[cfg(not(target_arch = "wasm32"))]
Glucose,
#[cfg(not(target_arch = "wasm32"))]
CaDiCaL,
BatSat,
}
#[cfg(not(target_arch = "wasm32"))]
static SOLVER_BACKEND: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
#[cfg(not(target_arch = "wasm32"))]
pub fn set_solver_backend(backend: SolverBackend) {
SOLVER_BACKEND.store(backend as u8, Relaxed);
}
#[cfg(target_arch = "wasm32")]
pub fn set_solver_backend(_backend: SolverBackend) {}
#[cfg(target_arch = "wasm32")]
fn current_backend() -> SolverBackend {
SolverBackend::BatSat
}
#[cfg(not(target_arch = "wasm32"))]
fn current_backend() -> SolverBackend {
match SOLVER_BACKEND.load(Relaxed) {
0 => SolverBackend::Glucose,
1 => SolverBackend::CaDiCaL,
_ => SolverBackend::BatSat,
}
}
pub enum Solver {
#[cfg(not(target_arch = "wasm32"))]
Glucose(rustsat_glucose::core::Glucose),
#[cfg(not(target_arch = "wasm32"))]
CaDiCaL(rustsat_cadical::CaDiCaL<'static, 'static>),
BatSat(Box<rustsat_batsat::BasicSolver>),
}
unsafe impl Send for Solver {}
impl Default for Solver {
fn default() -> Self {
match current_backend() {
#[cfg(not(target_arch = "wasm32"))]
SolverBackend::Glucose => Solver::Glucose(Default::default()),
#[cfg(not(target_arch = "wasm32"))]
SolverBackend::CaDiCaL => Solver::CaDiCaL(Default::default()),
SolverBackend::BatSat => Solver::BatSat(Box::default()),
}
}
}
impl Solver {
fn add_cnf(&mut self, cnf: Cnf) -> anyhow::Result<()> {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.add_cnf(cnf)?,
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.add_cnf(cnf)?,
Solver::BatSat(s) => s.add_cnf(cnf)?,
}
Ok(())
}
fn add_unit(&mut self, lit: Lit) -> anyhow::Result<()> {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.add_unit(lit)?,
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.add_unit(lit)?,
Solver::BatSat(s) => s.add_unit(lit)?,
}
Ok(())
}
fn solve_assumps(&mut self, lits: &[Lit]) -> anyhow::Result<SolverResult> {
Ok(match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.solve_assumps(lits)?,
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.solve_assumps(lits)?,
Solver::BatSat(s) => s.solve_assumps(lits)?,
})
}
fn full_solution(&self) -> anyhow::Result<Assignment> {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.full_solution(),
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.full_solution(),
Solver::BatSat(s) => s.full_solution(),
}
}
fn core(&mut self) -> anyhow::Result<Vec<Lit>> {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.core(),
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.core(),
Solver::BatSat(s) => s.core(),
}
}
fn set_conflict_limit(&mut self, limit: i64) {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.set_limit(rustsat_glucose::Limit::Conflicts(limit)),
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s
.set_limit(rustsat_cadical::Limit::Conflicts(limit as i32))
.expect("CaDiCaL set_limit failed"),
Solver::BatSat(_) => {
let _ = limit;
}
}
}
fn clear_conflict_limit(&mut self) {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.set_limit(rustsat_glucose::Limit::None),
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s
.set_limit(rustsat_cadical::Limit::Conflicts(-1))
.expect("CaDiCaL set_limit failed"),
Solver::BatSat(_) => {}
}
}
fn conflicts(&self) -> usize {
match self {
#[cfg(not(target_arch = "wasm32"))]
Solver::Glucose(s) => s.conflicts(),
#[cfg(not(target_arch = "wasm32"))]
Solver::CaDiCaL(s) => s.conflicts(),
Solver::BatSat(_) => 0,
}
}
}
pub struct SatCore {
pub solver: Arc<Mutex<Solver>>,
pub cnf: Arc<Cnf>,
pub fixed: RefCell<HashSet<Lit>>,
}
static CONFLICT_LIMIT: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(1000);
const MAX_CONFLICT_LIMIT: i64 = 100_000_000;
static LIMITED_CALLS: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
static LIMITED_INTERRUPTED: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
const RAMP_WARMUP: i64 = 50;
const RAMP_THRESHOLD: f64 = 0.10;
static SOLVER_CALLS: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
static PHASE_FIX_VALUES_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static PHASE_MUTEX_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static PHASE_SOLVE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static PHASE_POST_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static PHASE_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub fn print_phase_breakdown() {
let calls = PHASE_CALLS.load(Relaxed);
if calls == 0 {
return;
}
let fv = PHASE_FIX_VALUES_NS.load(Relaxed);
let mu = PHASE_MUTEX_NS.load(Relaxed);
let sv = PHASE_SOLVE_NS.load(Relaxed);
let po = PHASE_POST_NS.load(Relaxed);
let tot = fv + mu + sv + po;
let pct = |x: u64| {
if tot == 0 {
0.0
} else {
100.0 * x as f64 / tot as f64
}
};
eprintln!("=== SAT-call phase breakdown (_no_limit path, summed across threads) ===");
eprintln!(" Calls timed: {calls}");
eprintln!(
" fix_values {:>10.3} s ({:5.1}%)",
fv as f64 / 1e9,
pct(fv)
);
eprintln!(
" mutex acquire {:>10.3} s ({:5.1}%)",
mu as f64 / 1e9,
pct(mu)
);
eprintln!(
" solve_assumps {:>10.3} s ({:5.1}%)",
sv as f64 / 1e9,
pct(sv)
);
eprintln!(
" post (stats etc) {:>10.3} s ({:5.1}%)",
po as f64 / 1e9,
pct(po)
);
eprintln!("========================================================================");
}
pub fn set_global_conflict_limit(val: i64) {
CONFLICT_LIMIT.store(val, Relaxed);
}
pub fn multiply_global_conflict_limit(factor: i64) -> (i64, i64) {
let old = CONFLICT_LIMIT.load(Relaxed);
if old <= 0 {
return (old, old);
}
let new = old.saturating_mul(factor);
CONFLICT_LIMIT.store(new, Relaxed);
(old, new)
}
pub fn get_solver_calls() -> i64 {
SOLVER_CALLS.load(Relaxed)
}
pub fn reset_solver_calls() {
SOLVER_CALLS.store(0, Relaxed);
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum SearchError {
#[error("The SAT solver reached it's search limit")]
Limit,
}
pub type SearchResult<T> = std::result::Result<T, SearchError>;
impl SatCore {
pub fn new(cnf: Arc<Cnf>) -> anyhow::Result<SatCore> {
let timing_on = tracing::enabled!(target: "satcore_build", tracing::Level::INFO);
let t_total = timing_on.then(Instant::now);
let t_solver = timing_on.then(Instant::now);
let mut solver = Solver::default();
if let Some(t) = t_solver {
let e = t.elapsed();
info!(target: "satcore_build", "SatCore::new: Solver::default() took {:?}", e);
}
let t_clone = timing_on.then(Instant::now);
let cnf_clone = cnf.as_ref().clone();
let n_clauses = cnf_clone.len();
if let Some(t) = t_clone {
let e = t.elapsed();
info!(target: "satcore_build",
"SatCore::new: cnf.clone() took {:?} ({} clauses)", e, n_clauses);
}
let t_addcnf = timing_on.then(Instant::now);
solver.add_cnf(cnf_clone)?;
if let Some(t) = t_addcnf {
let e = t.elapsed();
info!(target: "satcore_build",
"SatCore::new: solver.add_cnf({} clauses) took {:?}", n_clauses, e);
}
if let Some(t) = t_total {
let e = t.elapsed();
if e.as_secs_f64() > 0.5 {
warn!(target: "satcore_build",
"SatCore::new: total {:?} ({} clauses)", e, n_clauses);
} else {
info!(target: "satcore_build",
"SatCore::new: total {:?} ({} clauses)", e, n_clauses);
}
}
Ok(SatCore {
solver: Arc::new(Mutex::new(solver)),
cnf,
fixed: RefCell::new(HashSet::new()),
})
}
fn fix_values(&self, lits: &[Lit]) {
let timing_on = tracing::enabled!(target: "satcore_build", tracing::Level::INFO);
let t_total = timing_on.then(Instant::now);
let mut fixed = self.fixed.borrow_mut();
let fixed_before = fixed.len();
{
let mut solver = self.solver.lock().unwrap();
for &l in lits {
if !fixed.contains(&l) {
solver.add_unit(l).expect("FATAL: Solver bug 1");
fixed.insert(l);
}
}
}
assert!(
lits.iter().all(|l| fixed.contains(l)),
"fix_values: lits contains entries not in fixed (should be impossible after the loop above)"
);
let rebooted = fixed.len() > lits.len();
if rebooted {
let t_reboot = timing_on.then(Instant::now);
let mut solver = Solver::default();
solver
.add_cnf(self.cnf.as_ref().clone())
.expect("FATAL: Solver bug 2");
fixed.clear();
for &l in lits {
if !fixed.contains(&l) {
solver.add_unit(l).expect("FATAL: Solver bug 3");
fixed.insert(l);
}
}
let mut mutex_solver = self.solver.lock().unwrap();
*mutex_solver = solver;
if let Some(t) = t_reboot {
let e = t.elapsed();
warn!(target: "satcore_build",
"fix_values: REBOOTED solver — fixed_before={}, lits={}, rebuild took {:?}",
fixed_before, lits.len(), e);
}
}
if let Some(t) = t_total {
let e = t.elapsed();
if rebooted || e.as_secs_f64() > 0.05 {
info!(target: "satcore_build",
"fix_values: total {:?} (fixed_before={}, lits={}, rebooted={})",
e, fixed_before, lits.len(), rebooted);
}
}
}
fn do_solve_assumps_no_limit(solver: &mut MutexGuard<Solver>, lits: &[Lit]) -> SolverResult {
solver.clear_conflict_limit();
SOLVER_CALLS.fetch_add(1, Relaxed);
let conflicts_before = solver.conflicts();
let call_start = Instant::now();
let solve = solver.solve_assumps(lits).unwrap();
let call_duration = call_start.elapsed();
let conflicts_delta = solver.conflicts().saturating_sub(conflicts_before);
solver.clear_conflict_limit();
crate::stats::record_sat_call(call_duration, conflicts_delta, solve);
Self::warn_long_call(call_duration, conflicts_delta, &solve, lits, "no_limit");
solve
}
fn effective_limit(work_mult: f64) -> i64 {
let base = CONFLICT_LIMIT.load(Relaxed);
if base <= 0 || work_mult <= 0.0 || !work_mult.is_finite() {
return 0;
}
let scaled = (base as f64) * work_mult;
if scaled >= i64::MAX as f64 {
i64::MAX
} else {
(scaled as i64).max(1)
}
}
fn do_solve_assumps(
solver: &mut MutexGuard<Solver>,
lits: &[Lit],
work_mult: f64,
) -> SolverResult {
let limit = Self::effective_limit(work_mult);
if limit > 0 {
solver.set_conflict_limit(limit);
} else {
solver.clear_conflict_limit();
}
SOLVER_CALLS.fetch_add(1, Relaxed);
let conflicts_before = solver.conflicts();
let call_start = Instant::now();
let solve = solver.solve_assumps(lits).unwrap();
let call_duration = call_start.elapsed();
let conflicts_delta = solver.conflicts().saturating_sub(conflicts_before);
solver.clear_conflict_limit();
crate::stats::record_sat_call(call_duration, conflicts_delta, solve);
Self::warn_long_call(call_duration, conflicts_delta, &solve, lits, "limited");
if (work_mult - 1.0).abs() < f64::EPSILON {
if matches!(solve, SolverResult::Interrupted) {
LIMITED_INTERRUPTED.fetch_add(1, Relaxed);
}
let total = LIMITED_CALLS.fetch_add(1, Relaxed) + 1;
if total >= RAMP_WARMUP {
let interrupted = LIMITED_INTERRUPTED.load(Relaxed);
let ratio = interrupted as f64 / total as f64;
if ratio >= RAMP_THRESHOLD {
let limit = CONFLICT_LIMIT.load(Relaxed);
let new_limit = (limit * 10).min(MAX_CONFLICT_LIMIT);
if new_limit > limit {
eprintln!(
"Auto-ramp: {interrupted}/{total} calls interrupted ({:.0}%), increasing conflict limit from {limit} to {new_limit}",
ratio * 100.0,
);
CONFLICT_LIMIT.store(new_limit, Relaxed);
}
LIMITED_CALLS.store(0, Relaxed);
LIMITED_INTERRUPTED.store(0, Relaxed);
}
}
}
solve
}
const LONG_CALL_THRESHOLD: Duration = Duration::from_secs(10);
fn warn_long_call(
duration: Duration,
conflicts: usize,
result: &SolverResult,
lits: &[Lit],
path: &str,
) {
if duration >= Self::LONG_CALL_THRESHOLD {
eprintln!(
"LONG SAT call ({path}): {:.1}s, {conflicts} conflicts, result={result:?}, assumptions={} lits",
duration.as_secs_f64(),
lits.len(),
);
}
}
pub fn assumption_solve(
&self,
known: &[Lit],
lits: &[Lit],
work_mult: f64,
) -> SearchResult<bool> {
let t0 = Instant::now();
self.fix_values(known);
let t1 = Instant::now();
let mut solver = self.solver.lock().unwrap();
let t2 = Instant::now();
let solve = SatCore::do_solve_assumps(&mut solver, lits, work_mult);
let t3 = Instant::now();
let result = match solve {
rustsat::solvers::SolverResult::Sat => Ok(true),
rustsat::solvers::SolverResult::Unsat => Ok(false),
rustsat::solvers::SolverResult::Interrupted => Err(SearchError::Limit),
};
let t4 = Instant::now();
PHASE_FIX_VALUES_NS.fetch_add((t1 - t0).as_nanos() as u64, Relaxed);
PHASE_MUTEX_NS.fetch_add((t2 - t1).as_nanos() as u64, Relaxed);
PHASE_SOLVE_NS.fetch_add((t3 - t2).as_nanos() as u64, Relaxed);
PHASE_POST_NS.fetch_add((t4 - t3).as_nanos() as u64, Relaxed);
PHASE_CALLS.fetch_add(1, Relaxed);
info!(target: "solver", "Solution to {:?} is {:?}", lits, result);
result
}
pub fn assumption_solve_solution(
&self,
known: &[Lit],
lits: &[Lit],
work_mult: f64,
) -> SearchResult<Option<Assignment>> {
let t0 = Instant::now();
self.fix_values(known);
let t1 = Instant::now();
let mut solver = self.solver.lock().unwrap();
let t2 = Instant::now();
let solve = SatCore::do_solve_assumps(&mut solver, lits, work_mult);
let t3 = Instant::now();
let result = match solve {
rustsat::solvers::SolverResult::Sat => Ok(Some(solver.full_solution().unwrap())),
rustsat::solvers::SolverResult::Unsat => Ok(None),
rustsat::solvers::SolverResult::Interrupted => Err(SearchError::Limit),
};
let t4 = Instant::now();
PHASE_FIX_VALUES_NS.fetch_add((t1 - t0).as_nanos() as u64, Relaxed);
PHASE_MUTEX_NS.fetch_add((t2 - t1).as_nanos() as u64, Relaxed);
PHASE_SOLVE_NS.fetch_add((t3 - t2).as_nanos() as u64, Relaxed);
PHASE_POST_NS.fetch_add((t4 - t3).as_nanos() as u64, Relaxed);
PHASE_CALLS.fetch_add(1, Relaxed);
info!(target: "solver", "Solution to {:?} is {:?}", lits, result);
result
}
pub fn assumption_solve_no_limit(&self, known: &[Lit], lits: &[Lit]) -> bool {
let t0 = Instant::now();
self.fix_values(known);
let t1 = Instant::now();
let mut solver = self.solver.lock().unwrap();
let t2 = Instant::now();
let solve = SatCore::do_solve_assumps_no_limit(&mut solver, lits);
let t3 = Instant::now();
let result = match solve {
rustsat::solvers::SolverResult::Sat => true,
rustsat::solvers::SolverResult::Unsat => false,
rustsat::solvers::SolverResult::Interrupted => {
unreachable!("assumption_solve_no_limit must not hit a limit")
}
};
let t4 = Instant::now();
PHASE_FIX_VALUES_NS.fetch_add((t1 - t0).as_nanos() as u64, Relaxed);
PHASE_MUTEX_NS.fetch_add((t2 - t1).as_nanos() as u64, Relaxed);
PHASE_SOLVE_NS.fetch_add((t3 - t2).as_nanos() as u64, Relaxed);
PHASE_POST_NS.fetch_add((t4 - t3).as_nanos() as u64, Relaxed);
PHASE_CALLS.fetch_add(1, Relaxed);
result
}
pub fn assumption_solve_solution_no_limit(
&self,
known: &[Lit],
lits: &[Lit],
) -> Option<Assignment> {
self.fix_values(known);
let mut solver = self.solver.lock().unwrap();
let solve = SatCore::do_solve_assumps_no_limit(&mut solver, lits);
match solve {
rustsat::solvers::SolverResult::Sat => Some(solver.full_solution().unwrap()),
rustsat::solvers::SolverResult::Unsat => None,
rustsat::solvers::SolverResult::Interrupted => {
unreachable!("assumption_solve_solution_no_limit must not hit a limit")
}
}
}
pub fn solve_with_clause_no_limit(&self, assumps: &[Lit], clause: &[Lit]) -> bool {
let mut cnf = self.cnf.as_ref().clone();
cnf.add_clause(clause.iter().copied().collect());
let mut solver = Solver::default();
solver
.add_cnf(cnf)
.expect("FATAL: solver build in solve_with_clause_no_limit");
SOLVER_CALLS.fetch_add(1, Relaxed);
solver.clear_conflict_limit();
matches!(
solver.solve_assumps(assumps).unwrap(),
rustsat::solvers::SolverResult::Sat
)
}
pub fn assumption_solve_with_core(
&self,
known: &[Lit],
lits: &[Lit],
) -> SearchResult<Option<Vec<Lit>>> {
let t0 = Instant::now();
self.fix_values(known);
let t1 = Instant::now();
PHASE_FIX_VALUES_NS.fetch_add((t1 - t0).as_nanos() as u64, Relaxed);
self.raw_assumption_solve_with_core_timed(lits, t1)
}
fn raw_assumption_solve_with_core(&self, lits: &[Lit]) -> SearchResult<Option<Vec<Lit>>> {
self.raw_assumption_solve_with_core_timed(lits, Instant::now())
}
fn raw_assumption_solve_with_core_timed(
&self,
lits: &[Lit],
t_after_fix: Instant,
) -> SearchResult<Option<Vec<Lit>>> {
let mut solver = self.solver.lock().unwrap();
let t2 = Instant::now();
let solve = SatCore::do_solve_assumps(&mut solver, lits, 1.0);
let t3 = Instant::now();
let result = match solve {
rustsat::solvers::SolverResult::Sat => Ok(None),
rustsat::solvers::SolverResult::Unsat => Ok(Some(
solver.core().unwrap().into_iter().map(|l| !l).collect(),
)),
rustsat::solvers::SolverResult::Interrupted => Err(SearchError::Limit),
};
let t4 = Instant::now();
PHASE_MUTEX_NS.fetch_add((t2 - t_after_fix).as_nanos() as u64, Relaxed);
PHASE_SOLVE_NS.fetch_add((t3 - t2).as_nanos() as u64, Relaxed);
PHASE_POST_NS.fetch_add((t4 - t3).as_nanos() as u64, Relaxed);
PHASE_CALLS.fetch_add(1, Relaxed);
result
}
fn greedy_minimise(
&self,
initial_core: Vec<Lit>,
max_size: Option<i64>,
) -> SearchResult<Option<Vec<Lit>>> {
let mut core = initial_core;
if let Some(max_size) = max_size {
let num_groups = max_size as usize + 1;
while core.len() > num_groups * 2 {
let mut shrank = false;
for i in 0..num_groups {
let remaining: Vec<Lit> = core
.iter()
.enumerate()
.filter_map(
|(j, &lit)| {
if j % num_groups == i { None } else { Some(lit) }
},
)
.collect();
let candidate = self.raw_assumption_solve_with_core(&remaining)?;
if let Some(found) = candidate {
tracing::info!(target: "musdetail",
"bulk shrink: {} -> {} (group {}/{})",
core.len(), found.len(), i, num_groups);
core = found;
shrank = true;
break;
}
}
if !shrank {
return Ok(None);
}
}
}
let candidates = core.clone();
let mut known_core = Vec::new();
let mut known_size: i64 = 0;
for &lit in &candidates {
let location = core.iter().position(|&x| x == lit);
if let Some(location) = location {
let mut check_core = core.clone();
check_core.remove(location);
let candidate = self.raw_assumption_solve_with_core(&check_core)?;
if let Some(found) = candidate {
core = found;
} else {
known_size += 1;
known_core.push(lit);
if let Some(max_size) = max_size
&& known_size == max_size
{
assert!(known_core.len() as i64 == max_size);
let core = self.raw_assumption_solve_with_core(&known_core)?;
if let Some(found) = core {
assert!(found.len() as i64 == known_size);
return Ok(Some(found));
}
return Ok(None);
}
}
}
}
Ok(Some(core))
}
pub fn minimise_us(
&self,
known: &[Lit],
us: &[Lit],
max_size: Option<i64>,
) -> SearchResult<Vec<Lit>> {
Ok(self
.minimise_us_bounded(known, us, max_size)?
.expect("minimise_us: greedy_minimise must succeed (input is UNSAT)"))
}
pub fn minimise_us_bounded(
&self,
known: &[Lit],
us: &[Lit],
max_size: Option<i64>,
) -> SearchResult<Option<Vec<Lit>>> {
self.fix_values(known);
let initial = self.raw_assumption_solve_with_core(us)?;
let core = initial.expect("minimise_us: input must be an unsatisfiable subset");
self.greedy_minimise(core, max_size)
}
pub fn quick_mus(
&self,
known: &[Lit],
lits: &[Lit],
max_size: Option<i64>,
) -> SearchResult<Option<Vec<Lit>>> {
self.fix_values(known);
let core = self.raw_assumption_solve_with_core(lits)?;
match core {
None => Ok(None),
Some(core) => {
tracing::info!(target: "musdetail", "quick_mus: initial_core={} max_size={:?}",
core.len(), max_size);
Ok(self.greedy_minimise(core, max_size)?)
}
}
}
}
#[cfg(test)]
mod tests {
use rustsat::lit;
use super::*;
fn create_cnf() -> Arc<Cnf> {
let mut cnf = Cnf::new();
cnf.add_binary(lit![0], lit![1]);
cnf.add_binary(lit![0], !lit![1]);
Arc::new(cnf)
}
#[test]
fn test_assumption_solve_solution() -> anyhow::Result<()> {
let solver = SatCore::new(create_cnf())?;
let result = solver.assumption_solve_solution(&[], &[lit![1], lit![2]], 1.0)?;
assert!(result.is_some());
let result = solver.assumption_solve_solution(&[], &[lit![0]], 1.0)?;
assert!(result.is_some());
let result = solver.assumption_solve_solution(&[], &[!lit![0]], 1.0)?;
assert!(result.is_none());
Ok(())
}
#[test]
fn test_assumption_solve_core() -> anyhow::Result<()> {
let solver = SatCore::new(create_cnf())?;
let result = solver.assumption_solve_solution(&[], &[lit![1], lit![2]], 1.0)?;
assert!(result.is_some());
let result = solver.assumption_solve_solution(&[], &[lit![0]], 1.0)?;
assert!(result.is_some());
let result = solver.assumption_solve_solution(&[], &[!lit![0]], 1.0)?;
assert!(result.is_none());
Ok(())
}
#[test]
fn test_assumption_quick_mus() -> anyhow::Result<()> {
let solver = SatCore::new(create_cnf())?;
let result = solver.quick_mus(&[], &[lit![1], lit![2]], None)?;
assert!(result.is_none());
let result = solver.quick_mus(&[], &[lit![0]], None)?;
assert!(result.is_none());
let result = solver.quick_mus(&[], &[!lit![0]], None)?;
assert!(result.is_some());
Ok(())
}
#[test]
fn test_assumption_quick_mus_known() -> anyhow::Result<()> {
let solver = SatCore::new(create_cnf())?;
let result = solver.quick_mus(&[], &[lit![1], lit![2]], None)?;
assert!(result.is_none());
let result = solver.quick_mus(&[!lit![0]], &[lit![1], lit![2]], None)?;
assert_eq!(result, Some(vec![]));
let result = solver.quick_mus(&[], &[lit![1], lit![2]], None)?;
assert!(result.is_none());
Ok(())
}
}