use super::*;
use crate::cnf::{DimacsHeader, Original, Reduced, ShowSet, WeightTable, Weights, write_dimacs};
use crate::dot;
use crate::vtree::Vtree;
pub(super) fn refuted(
clauses: &[Clause],
num_vars: u32,
mode: Mode,
show_vars_reduced_dimacs: Option<ShowSet<Reduced>>,
stages: StageReport,
telemetry: PreprocessTelemetry,
) -> Option<PreprocessBundle> {
crate::cnf::contains_empty_clause(clauses)
.then(|| unsat_bundle(num_vars, mode, show_vars_reduced_dimacs, stages, telemetry))
}
pub(super) fn unsat_bundle(
num_vars: u32,
mode: Mode,
show_vars_reduced_dimacs: Option<ShowSet<Reduced>>,
stages: StageReport,
telemetry: PreprocessTelemetry,
) -> PreprocessBundle {
debug_assert!(num_vars >= 1, "an UNSAT instance has at least one variable");
let x = VarId(0);
PreprocessBundle {
reduced: CnfFormula {
num_vars,
clauses: vec![
Clause::new(vec![Literal::new(x, true)]),
Clause::new(vec![Literal::new(x, false)]),
],
},
record: PreprocessRecord {
original_to_reduced_dimacs: matches!(mode, Mode::Compile)
.then(|| OriginalMap::identity(num_vars)),
unsat: true,
show_vars_reduced_dimacs,
..PreprocessRecord::new(
mode,
RecordLift::neutral(),
num_vars,
VarMap::identity(num_vars),
)
},
learnt_clauses_reduced_dimacs: Vec::new(),
stages,
count_lift: CountLift::default(),
telemetry,
decision_trace: None,
arjun_input: None,
independent_support_reduced: None,
}
}
pub(super) fn preprocess_config(
config: &RunConfig,
purpose: SimplifyPurpose,
orig_w: &Weights<Original>,
) -> SimplifyConfig {
if !config.stages.simplify {
return SimplifyConfig {
prefix: crate::preprocess::simplify::SimplifyPrefix::Disabled,
deadline: config.deadline,
clock: config.preprocess_clock,
..SimplifyConfig::for_purpose(purpose, true)
};
}
let mut resolved = SimplifyConfig {
prefix: match config.simplify.backbone_budget_ms {
Some(budget_ms) => crate::preprocess::simplify::SimplifyPrefix::Backbone {
budget_ms,
equivalence_budget_ms: config.simplify.equivalence_budget_ms,
},
None => crate::preprocess::simplify::SimplifyPrefix::EqIter,
},
deadline: config.deadline,
clock: config.preprocess_clock,
frozen_vars: if purpose == SimplifyPurpose::WeightedCount {
orig_w.unequal_vars()
} else {
rustc_hash::FxHashSet::default()
},
..SimplifyConfig::for_purpose(purpose, false)
};
if resolved.stages.gates {
resolved.stages.gates = config.simplify.detect_gates;
}
if resolved.stages.dve.is_some() {
resolved.stages.dve = config.simplify.dve.map(|dve| DveBudget {
rounds: dve.rounds,
budget_ms: dve.budget_ms,
});
}
resolved
}
pub(super) fn weight_table(meta: &CnfMeta, mode: Mode) -> Option<&WeightTable> {
mode.is_weighted()
.then(|| meta.declared_weights())
.flatten()
}
pub(super) fn original_weights(meta: &CnfMeta, num_vars: usize, mode: Mode) -> Weights<Original> {
weight_table(meta, mode).map_or_else(|| Weights::uniform(num_vars), |t| t.resolve(num_vars))
}
pub(super) fn ensure_dir(dir: &Path) -> Result<(), VitriError> {
std::fs::create_dir_all(dir).map_err(|e| VitriError::io(dir, "create", &e))
}
pub(super) fn write_file(path: &Path, contents: impl AsRef<[u8]>) -> Result<(), VitriError> {
std::fs::write(path, contents).map_err(|e| VitriError::io(path, "write", &e))
}
pub(super) fn to_json_pretty<T: Serialize>(value: &T) -> String {
serde_json::to_string_pretty(value).expect("bundle serialization is infallible")
}
impl PreprocessRecord {
pub fn to_json_string(&self) -> String {
to_json_pretty(self)
}
pub(crate) fn dimacs_header(&self) -> DimacsHeader<'_, Reduced> {
DimacsHeader {
track: (self.mode != Mode::Compile).then_some(self.mode.token()),
show: self.show_vars_reduced_dimacs.as_ref(),
weights: self.reduced_weights.as_deref(),
}
}
}
impl PreprocessBundle {
pub fn write_to_dir(&self, dir: &Path) -> Result<BundlePaths, VitriError> {
ensure_dir(dir)?;
let reduced_cnf = dir.join(REDUCED_CNF_NAME);
let record = dir.join(PREPROCESS_RECORD_NAME);
write_dimacs(&self.reduced, &self.record.dimacs_header(), &reduced_cnf)?;
write_file(&record, self.record.to_json_string())?;
Ok(BundlePaths {
reduced_cnf,
record,
})
}
}
fn check_build_belongs(build: &VtreeBuild, reduced: &CnfFormula) -> Result<(), VitriError> {
if build.vtree.num_leaves() != reduced.num_vars {
return Err(VitriError::mismatch(format!(
"vtree has {} leaves but the formula has {} variables; \
the build does not belong to this formula",
build.vtree.num_leaves(),
reduced.num_vars,
)));
}
let Some(comps) = build.components.as_deref() else {
return Ok(());
};
let mut claimed_by: Vec<Option<usize>> = vec![None; reduced.num_vars as usize];
for (index, cv) in comps.iter().enumerate() {
for &ci in &cv.clause_indices {
let Some(clause) = reduced.clauses.get(ci) else {
return Err(VitriError::mismatch(format!(
"component {index} claims clause {ci} but the formula has {} clauses; \
the build does not belong to this formula",
reduced.clauses.len(),
)));
};
for lit in &clause.literals {
let Some(slot) = claimed_by.get_mut(lit.var.idx()) else {
return Err(VitriError::mismatch(format!(
"clause {ci} names variable {} but the formula declares {} variables",
lit.var.to_dimacs(),
reduced.num_vars,
)));
};
match *slot {
Some(other) if other != index => {
return Err(VitriError::mismatch(format!(
"components {other} and {index} both claim variable {}; \
the build does not belong to this formula",
lit.var.to_dimacs(),
)));
}
_ => *slot = Some(index),
}
}
}
}
Ok(())
}
impl VtreeBuild {
pub fn write_to_dir(
&self,
dir: &Path,
reduced: &CnfFormula,
show: Option<&ShowSet<Reduced>>,
options: components::ComponentWriteOptions,
) -> Result<VtreeFiles, VitriError> {
check_build_belongs(self, reduced)?;
ensure_dir(dir)?;
let show_mask = show.map(|s| s.mask(reduced.num_vars));
let dot = DotFor::when(options.dot, reduced, show_mask.as_ref());
let (vtree, vtree_dot) = write_vtree_files(dir.join(VTREE_NAME), &self.vtree, dot)?;
let (manifest, paths) = components::write_components(dir, reduced, self, show, options)?;
assert!(
components::manifest_matches_vtree(&manifest, &self.vtree),
"the component manifest and the emitted whole-formula vtree describe different \
variable spaces",
);
Ok(VtreeFiles {
vtree,
dot: vtree_dot,
components: ComponentFiles { manifest, paths },
})
}
}
#[derive(Clone, Copy)]
pub(super) struct DotFor<'a> {
pub formula: &'a CnfFormula,
pub show_mask: Option<&'a crate::cnf::ShowMask>,
}
impl<'a> DotFor<'a> {
pub(super) fn when(
wanted: bool,
formula: &'a CnfFormula,
show_mask: Option<&'a crate::cnf::ShowMask>,
) -> Option<Self> {
wanted.then_some(DotFor { formula, show_mask })
}
}
pub(super) fn write_vtree_files(
path: PathBuf,
vtree: &Vtree,
dot: Option<DotFor<'_>>,
) -> Result<(PathBuf, Option<PathBuf>), VitriError> {
write_file(&path, vtree.to_vtree_text())?;
let dot_path = match dot {
Some(d) => {
let dot_path = path.with_extension("dot");
let ann = dot::annotate_from_cnf(vtree, d.formula, d.show_mask);
write_file(&dot_path, dot::vtree_to_dot(vtree, Some(&ann)))?;
Some(dot_path)
}
None => None,
};
Ok((path, dot_path))
}