use crate::cnf::VarId;
use crate::cnf::{Clause, CnfFormula, Literal};
use crate::diagnostics::diag;
use super::equivalence::EquivMapping;
use super::gates;
use super::renumber::Renumber;
mod config;
mod record;
mod strip;
pub(crate) use config::*;
pub(crate) use record::*;
use strip::*;
#[derive(Default)]
struct Layers {
preprocessed: Option<CnfFormula>,
stripped: Option<Stripped>,
equiv_reduced: Option<EquivReduction>,
telemetry: SimplifyTelemetry,
}
fn preprocess_and_reduce(
formula: &CnfFormula,
config: &SimplifyConfig,
meter: &mut super::meter::PreprocessMeter,
) -> Layers {
use std::time::Duration;
let (pipeline, strip_backbone, telemetry) = match config.prefix {
SimplifyPrefix::Disabled => return Layers::default(),
SimplifyPrefix::EqIter => (
super::pipelines::preprocess_eq_iter_with_mapping_and_meter(
formula,
config.deadline,
meter,
),
false,
SimplifyTelemetry::default(),
),
SimplifyPrefix::Backbone {
budget_ms,
equivalence_budget_ms,
} => {
let pipeline = super::backbone_pipeline::preprocess_backbone_eq_iter_with_meter(
formula,
Duration::from_millis(budget_ms),
equivalence_budget_ms.map(Duration::from_millis),
config.deadline,
meter,
);
let measured = pipeline.backbone.clone().unwrap_or_default();
(
pipeline,
true,
SimplifyTelemetry {
backbone_ms: measured.backbone_ms,
equivalence_ms: measured.equivalence_ms,
backbone_found: measured.backbone_found,
backbone_probes: measured.backbone_probes,
..SimplifyTelemetry::default()
},
)
}
};
diag!(
"[simplify] {} clauses removed, {} literals shortened, {} forced vars",
pipeline.stats.eliminated_clauses,
pipeline.stats.shortened_literals,
pipeline.stats.forced_vars,
);
let preprocessed = pipeline.formula;
let mut layers = Layers {
telemetry,
..Layers::default()
};
let mut mapping = pipeline.mapping;
if strip_backbone && let Some((stripped, bb_reduction)) = strip_backbone_vars(&preprocessed) {
diag!(
"[backbone-stripping] {} → {} vars ({} forced removed)",
preprocessed.num_vars,
stripped.num_vars,
bb_reduction.backbone.len(),
);
mapping = mapping.and_then(|m| m.remap_for_stripped(&bb_reduction));
layers.stripped = Some(Stripped {
formula: stripped,
removed: bb_reduction,
});
}
let equivalence_input = layers
.stripped
.as_ref()
.map_or(&preprocessed, |stripped| &stripped.formula);
layers.equiv_reduced = apply_equiv_reduction(
equivalence_input,
mapping,
config.stages.reduce_equivalences,
);
layers.preprocessed = Some(preprocessed);
layers
}
pub(crate) fn simplify(formula: &CnfFormula, config: &SimplifyConfig) -> SimplifiedFormula {
let started = std::time::Instant::now();
let mut meter = super::meter::PreprocessMeter::new(config.clock);
let layers = preprocess_and_reduce(formula, config, &mut meter);
let mut result = SimplifiedFormula {
original: formula.clone(),
equiv_reduced: layers.equiv_reduced,
dve_reduced: None,
preprocessed: layers.preprocessed,
stripped: layers.stripped,
telemetry: layers.telemetry,
decision_trace: None,
};
if let Some(dve_budget) = config.stages.dve
&& !result.reduced_formula().is_refuted()
&& result.reduced_formula().num_vars > 0
{
let dve = run_dve(config, dve_budget, &result, &mut meter);
result.telemetry.dve_ms = Some(dve.elapsed_ms);
result.dve_reduced = dve.reduction;
}
result.telemetry.total_ms = started.elapsed().as_millis() as u64;
result.decision_trace = meter.into_trace();
result
}
struct DveAttempt {
reduction: Option<DveReduction>,
elapsed_ms: u64,
}
fn run_dve(
config: &SimplifyConfig,
budget: DveBudget,
result: &SimplifiedFormula,
meter: &mut super::meter::PreprocessMeter,
) -> DveAttempt {
let dve_input = result.reduced_formula().clone();
let known_defined: rustc_hash::FxHashSet<VarId> = if config.stages.gates {
let mapping = gates::detect_gates(&dve_input);
if !mapping.is_empty() {
let mut by_type = [0usize; 5];
for g in &mapping.gates {
let idx = match g.gate_type {
gates::GateType::And => 0,
gates::GateType::Or => 1,
gates::GateType::Xor => 2,
gates::GateType::Xnor => 3,
gates::GateType::Ite => 4,
};
by_type[idx] += 1;
}
diag!(
"[gate-detection] {} defined outputs ({} AND, {} OR, {} XOR, {} XNOR, {} ITE) — fed as DVE short-circuits",
mapping.num_eliminated(),
by_type[0],
by_type[1],
by_type[2],
by_type[3],
by_type[4],
);
mapping.eliminated.clone()
} else {
rustc_hash::FxHashSet::default()
}
} else {
rustc_hash::FxHashSet::default()
};
let frozen_local = result.frozen_in_dve_space(&config.frozen_vars, dve_input.num_vars);
let mut dve = super::dve::preprocess_dve_with_meter(
&dve_input,
super::dve::DveConfig {
max_rounds: budget.rounds,
time_limit_ms: budget.budget_ms,
keep_original_vars: false,
known_defined: &known_defined,
frozen: &frozen_local,
frozen_equiv: super::dve::FrozenEquiv::Ignore,
},
meter,
);
super::dve::post_dve_strengthen_with_meter(&mut dve, &frozen_local, meter);
let elapsed_ms = dve.elapsed_ms;
let total_elim = dve.total_eliminated();
let meaningful =
total_elim >= 3 || total_elim as f64 / dve_input.num_vars.max(1) as f64 >= 0.05;
if !meaningful {
return DveAttempt {
reduction: None,
elapsed_ms,
};
}
let renumbering = dve
.renumbering
.unwrap_or_else(|| Renumber::keeping(dve_input.num_vars as usize, |_| true));
debug_assert_eq!(
renumbering.num_old_vars(),
dve_input.num_vars as usize,
"the DVE renumbering must be stated over the space DVE was given",
);
DveAttempt {
reduction: Some(DveReduction {
formula: dve.formula,
renumbering,
fates: dve.fates,
}),
elapsed_ms,
}
}
#[cfg(test)]
mod tests;