use super::cadical;
use super::equivalence;
use crate::cnf::{Clause, CnfFormula};
use crate::diagnostics::diag;
#[derive(Clone, Debug, Default)]
pub(crate) struct PreprocessStats {
pub original_clauses: usize,
pub eliminated_clauses: usize,
pub shortened_literals: usize,
pub forced_vars: usize,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ClauseCounts {
pub clauses: usize,
pub literals: usize,
}
impl ClauseCounts {
pub(super) fn of(clauses: &[Clause]) -> Self {
ClauseCounts {
clauses: clauses.len(),
literals: clauses.iter().map(|c| c.literals.len()).sum(),
}
}
}
pub(super) fn diff_stats(
before: ClauseCounts,
after: ClauseCounts,
forced_vars: usize,
) -> PreprocessStats {
PreprocessStats {
original_clauses: before.clauses,
eliminated_clauses: before.clauses.saturating_sub(after.clauses),
shortened_literals: before.literals.saturating_sub(after.literals),
forced_vars,
}
}
pub(super) fn unsat_stats(before: ClauseCounts, forced_vars: usize) -> PreprocessStats {
PreprocessStats {
original_clauses: before.clauses,
eliminated_clauses: before.clauses,
shortened_literals: before.literals,
forced_vars,
}
}
pub(super) enum Stage {
Tarjan,
CadicalSimplify,
Probe {
backbone: std::time::Duration,
equiv: Option<std::time::Duration>,
},
}
pub(super) struct StageOutcome {
pub formula: CnfFormula,
pub stats: PreprocessStats,
pub unsat: bool,
pub mapping: Option<equivalence::EquivMapping>,
pub backbone: Option<super::BackboneStats>,
}
impl StageOutcome {
pub(super) fn refuted(formula: CnfFormula, stats: PreprocessStats) -> Self {
StageOutcome {
formula,
stats,
unsat: true,
mapping: None,
backbone: None,
}
}
pub(super) fn with_backbone(mut self, backbone: super::BackboneStats) -> Self {
self.backbone = Some(backbone);
self
}
}
pub(crate) struct PipelineOutput {
pub formula: CnfFormula,
pub stats: PreprocessStats,
pub mapping: Option<equivalence::EquivMapping>,
pub backbone: Option<super::BackboneStats>,
}
fn merge_stats(acc: Option<PreprocessStats>, next: PreprocessStats) -> PreprocessStats {
match acc {
None => next,
Some(acc) => PreprocessStats {
original_clauses: acc.original_clauses,
eliminated_clauses: acc.eliminated_clauses + next.eliminated_clauses,
shortened_literals: acc.shortened_literals + next.shortened_literals,
forced_vars: acc.forced_vars + next.forced_vars,
},
}
}
fn run_stage(
stage: &Stage,
formula: &CnfFormula,
deadline: Option<std::time::Instant>,
meter: &mut super::meter::PreprocessMeter,
) -> StageOutcome {
match stage {
Stage::Tarjan => stage_tarjan(formula),
Stage::CadicalSimplify => stage_cadical_simplify(formula, deadline, meter),
Stage::Probe { backbone, equiv } => {
super::backbone_pipeline::stage_probe(formula, *backbone, *equiv, deadline, meter)
}
}
}
fn stage_tarjan(formula: &CnfFormula) -> StageOutcome {
let (eq_result, mapping) = equivalence::extract_equivalences_with_mapping(formula);
if eq_result.is_unsat {
return StageOutcome::refuted(
eq_result.formula,
unsat_stats(ClauseCounts::of(&formula.clauses), 0),
);
}
if eq_result.num_equivalences > 0 {
diag!(
"[tarjan] {} classes, {} → {} clauses",
eq_result.num_equivalences,
formula.clauses.len(),
eq_result.formula.clauses.len()
);
}
let stats = PreprocessStats {
original_clauses: eq_result.formula.clauses.len(),
..Default::default()
};
StageOutcome {
formula: eq_result.formula,
stats,
unsat: false,
mapping,
backbone: None,
}
}
fn stage_cadical_simplify(
formula: &CnfFormula,
deadline: Option<std::time::Instant>,
meter: &mut super::meter::PreprocessMeter,
) -> StageOutcome {
let before = ClauseCounts::of(&formula.clauses);
let (result, forced_count) =
cadical::preprocess_cadical_with_meter(formula, 3, deadline, meter);
if result.is_refuted() {
return StageOutcome::refuted(
CnfFormula::contradiction(formula.num_vars),
unsat_stats(before, forced_count),
);
}
let stats = diff_stats(before, ClauseCounts::of(&result.clauses), forced_count);
StageOutcome {
formula: result,
stats,
unsat: false,
mapping: None,
backbone: None,
}
}
#[cfg(test)]
pub(super) fn run_pipeline(
formula: &CnfFormula,
stages: &[Stage],
deadline: Option<std::time::Instant>,
) -> PipelineOutput {
let mut meter = super::meter::PreprocessMeter::new(crate::config::PreprocessClock::WallClock);
run_pipeline_with_meter(formula, stages, deadline, &mut meter)
}
pub(super) fn run_pipeline_with_meter(
formula: &CnfFormula,
stages: &[Stage],
deadline: Option<std::time::Instant>,
meter: &mut super::meter::PreprocessMeter,
) -> PipelineOutput {
let mut current: Option<CnfFormula> = None;
let mut merged: Option<PreprocessStats> = None;
let mut mapping: Option<equivalence::EquivMapping> = None;
let mut backbone: Option<super::BackboneStats> = None;
for stage in stages {
let input: &CnfFormula = current.as_ref().unwrap_or(formula);
let outcome = run_stage(stage, input, deadline, meter);
merged = Some(merge_stats(merged, outcome.stats));
if outcome.mapping.is_some() {
mapping = outcome.mapping;
}
if outcome.backbone.is_some() {
backbone = outcome.backbone;
}
let unsat = outcome.unsat;
current = Some(outcome.formula);
if unsat {
break;
}
}
PipelineOutput {
formula: current.unwrap_or_else(|| formula.clone()),
stats: merged.unwrap_or_default(),
mapping,
backbone,
}
}
#[cfg(test)]
pub(super) fn preprocess_eq_iter_with_mapping(
formula: &CnfFormula,
deadline: Option<std::time::Instant>,
) -> PipelineOutput {
let mut meter = super::meter::PreprocessMeter::new(crate::config::PreprocessClock::WallClock);
preprocess_eq_iter_with_mapping_and_meter(formula, deadline, &mut meter)
}
pub(super) fn preprocess_eq_iter_with_mapping_and_meter(
formula: &CnfFormula,
deadline: Option<std::time::Instant>,
meter: &mut super::meter::PreprocessMeter,
) -> PipelineOutput {
let p1 = run_pipeline_with_meter(
formula,
&[Stage::Tarjan, Stage::CadicalSimplify],
deadline,
meter,
);
if p1.formula.is_refuted() {
return p1;
}
let (eq2, _m2) = equivalence::extract_equivalences_with_mapping(&p1.formula);
if eq2.num_equivalences == 0 {
return p1;
}
diag!(
"[iter-equiv-pass-2] {} new classes, {} → {} clauses",
eq2.num_equivalences,
p1.formula.clauses.len(),
eq2.formula.clauses.len()
);
let p2 = run_pipeline_with_meter(&eq2.formula, &[Stage::CadicalSimplify], deadline, meter);
let combined = merge_stats(Some(p1.stats), p2.stats);
let pf = run_pipeline_with_meter(&p2.formula, &[Stage::Tarjan], deadline, meter);
PipelineOutput {
stats: combined,
..pf
}
}