#![allow(dead_code)]
use std::path::{Path, PathBuf};
use num_rational::BigRational;
use vitri::bundle::{LiteralWeight, PreprocessRecord, RECORD_FORMAT_TAG};
use vitri::cnf::{Clause, CnfFormula, CnfMeta, Literal, Mode, ShowSet, VarId};
use vitri::decompose::TreeDecomposition;
use vitri::preprocess::{OriginalMap, OriginalTarget, VarMap};
use vitri::vtree::{Vtree, VtreeNode};
pub(crate) struct Scratch(PathBuf);
impl Scratch {
pub(crate) fn new(tag: &str) -> Self {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("the clock must be past the epoch")
.as_nanos();
let dir = std::env::temp_dir().join(format!("vitri-{tag}-{}-{nanos}", std::process::id()));
std::fs::create_dir_all(&dir).expect("scratch dir");
Scratch(dir)
}
pub(crate) fn path(&self) -> &Path {
&self.0
}
pub(crate) fn file(&self, name: &str, text: &str) -> PathBuf {
let p = self.0.join(name);
std::fs::write(&p, text).expect("fixture write");
p
}
pub(crate) fn out(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
pub(crate) fn lit(var: u32, positive: bool) -> Literal {
Literal::new(VarId(var), positive)
}
pub(crate) fn clause(lits: &[(u32, bool)]) -> Clause {
Clause::new(lits.iter().map(|&(v, p)| lit(v, p)).collect())
}
pub(crate) fn make_formula(num_vars: u32, clauses_raw: Vec<Vec<i32>>) -> CnfFormula {
let clauses = clauses_raw
.into_iter()
.map(|lits| {
let mut literals: Vec<Literal> = lits.into_iter().map(Literal::from).collect();
literals.sort_by_key(|l| l.var);
Clause::new(literals)
})
.collect();
CnfFormula { num_vars, clauses }
}
pub(crate) fn grid_fixture() -> CnfFormula {
parse("p cnf 4 4\n1 2 0\n2 3 0\n3 4 0\n4 1 0\n").0
}
pub(crate) fn mixed_width_fixture() -> CnfFormula {
parse("p cnf 8 4\n1 2 0\n1 2 3 0\n1 2 3 4 5 0\n1 2 3 4 5 6 7 8 0\n").0
}
pub(crate) const IRREDUCIBLE_5: &str = "p cnf 5 5\n1 2 0\n-1 3 0\n-2 -3 4 0\n2 3 -4 0\n4 5 0\n";
pub(crate) const FULLY_RESOLVED: &str = "p cnf 3 2\n1 0\n2 0\n";
pub(crate) const REFUTED: &str = "p cnf 2 2\n1 0\n-1 0\n";
pub(crate) const CLAUSE_ID_ABOVE_COUNT: &str = "p cnf 2 1\n1 5 0\n";
pub(crate) const SHOW_ID_ABOVE_COUNT: &str = "c t pmc\np cnf 2 1\nc p show 9 0\n1 2 0\n";
pub(crate) fn clause_dimacs(lits: &[i32]) -> Clause {
Clause::new(lits.iter().map(|&l| Literal::from(l)).collect())
}
pub(crate) fn parse(dimacs: &str) -> (CnfFormula, CnfMeta) {
CnfFormula::from_dimacs(std::io::Cursor::new(dimacs.to_string())).expect("test CNF must parse")
}
pub(crate) fn chain_components(sizes: &[u32]) -> CnfFormula {
let mut clauses = Vec::new();
let mut next = 0u32;
for &size in sizes {
for a in next..next + size - 1 {
clauses.push(Clause::new(vec![lit(a, true), lit(a + 1, false)]));
}
next += size;
}
CnfFormula {
num_vars: next,
clauses,
}
}
pub(crate) fn wide_component() -> CnfFormula {
let n = 60u32;
let mut formula = chain_components(&[n]);
for a in 0..n - 7 {
formula.clauses.push(Clause::new(vec![
lit(a, false),
lit(a + 5, true),
lit(a + 7, true),
]));
}
formula
}
pub(crate) fn wide_component_dimacs(track_and_show: Option<&str>) -> String {
let formula = wide_component();
let mut text = String::new();
if let Some(header) = track_and_show {
text.push_str(header);
}
text.push_str(&format!(
"p cnf {} {}\n",
formula.num_vars,
formula.clauses.len()
));
if track_and_show.is_some() {
text.push_str("c p show");
for v in (1..=formula.num_vars).step_by(2) {
text.push_str(&format!(" {v}"));
}
text.push_str(" 0\n");
}
for clause in &formula.clauses {
for literal in clause.iter() {
text.push_str(&format!("{} ", literal.to_dimacs()));
}
text.push_str("0\n");
}
text
}
pub(crate) fn tokenize_vtree_text(text: &str) -> (usize, Vec<Vec<String>>) {
let mut lines = text.lines();
let header: Vec<&str> = lines
.next()
.expect("a vtree file leads with its header")
.split_whitespace()
.collect();
assert_eq!(header[0], "vtree", "the header names the format");
let declared = header[1].parse().expect("the header states a node count");
let nodes = lines
.map(|l| l.split_whitespace().map(str::to_string).collect())
.collect();
(declared, nodes)
}
pub(crate) fn assert_covers_all_vars(vt: &Vtree, n: u32, what: &str) {
assert_eq!(vt.num_leaves(), n, "leaf count ({what})");
let mut seen = vec![false; n as usize];
for (_idx, var) in vt.leaf_bottomup() {
assert!(
!seen[var.idx()],
"variable {var:?} is on more than one leaf ({what})"
);
seen[var.idx()] = true;
}
assert!(
seen.iter().all(|&s| s),
"some variable has no leaf ({what})"
);
let mut internals = 0;
for idx in vt.bottomup() {
match vt.node(idx) {
VtreeNode::Leaf { .. } => {}
VtreeNode::Internal { left, right, .. } => {
internals += 1;
assert_ne!(left, right, "internal node with one child twice ({what})");
for child in [*left, *right] {
assert_eq!(
vt.node(child).parent(),
Some(idx),
"child does not point back at its parent ({what})"
);
}
}
}
}
assert_eq!(
internals,
n as usize - 1,
"a binary tree over {n} leaves has n-1 internal nodes ({what})"
);
assert_eq!(
vt.node(vt.root()).parent(),
None,
"the root must have no parent ({what})"
);
}
pub(crate) fn make_td(
bags: Vec<Vec<u32>>,
tree_edges: Vec<(usize, usize)>,
num_vertices: u32,
) -> TreeDecomposition {
TreeDecomposition::new(&goatd::Graph::new(num_vertices, []), bags, tree_edges)
.expect("test fixture is a valid decomposition")
}
pub(crate) fn rat(n: i64, d: i64) -> BigRational {
BigRational::new(n.into(), d.into())
}
pub(crate) struct Lcg(u64);
impl Lcg {
pub(crate) fn new(seed: u64) -> Self {
Lcg(seed)
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.0 >> 32
}
pub(crate) fn below(&mut self, n: u64) -> u64 {
self.next_u64() % n
}
}
pub(crate) fn full_record() -> PreprocessRecord {
PreprocessRecord {
format: RECORD_FORMAT_TAG.to_string(),
mode: Mode::Pwmc,
count_lift_pow2: 3,
weight_lift: "7/8".to_string(),
original_num_vars: 4,
reduced_to_original_dimacs: VarMap::from_entries(vec![Some(3), None, Some(-2)]),
original_to_reduced_dimacs: Some(OriginalMap::from_entries(vec![
OriginalTarget::Literal(-3),
OriginalTarget::Literal(3),
OriginalTarget::Constant(false),
OriginalTarget::Free,
])),
forced_literals_original_dimacs: vec![-4],
free_vars_original_dimacs: vec![2],
unsat: false,
show_vars_reduced_dimacs: Some(ShowSet::from_dimacs_ids(&[1, 3]).expect("valid ids")),
reduced_weights: Some(vec![LiteralWeight {
literal: -1,
weight: "1/2".to_string(),
}]),
}
}
pub(crate) fn sparse_record() -> PreprocessRecord {
PreprocessRecord {
format: RECORD_FORMAT_TAG.to_string(),
mode: Mode::Mc,
count_lift_pow2: 0,
weight_lift: "1/1".to_string(),
original_num_vars: 1,
reduced_to_original_dimacs: VarMap::identity(1),
original_to_reduced_dimacs: None,
forced_literals_original_dimacs: Vec::new(),
free_vars_original_dimacs: Vec::new(),
unsat: false,
show_vars_reduced_dimacs: None,
reduced_weights: None,
}
}