use super::*;
use crate::tests::common::{assert_covers_all_vars, chain_components};
#[test]
fn every_name_the_crate_advertises_validates_and_builds() {
let formula = chain_components(&[40]);
let advertised = decomposition_spec_names()
.chain(baseline_spec_names())
.chain(standalone_spec_names())
.map(str::to_string)
.chain(vtree_spec_bases());
for spec in advertised {
let spec = spec.as_str();
assert!(
validate_vtree_spec(spec).is_ok(),
"{spec} is advertised and must validate",
);
let vt = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} is advertised and must build: {e}"))
.vtree;
assert_covers_all_vars(&vt, formula.num_vars, spec);
}
}
#[test]
fn spec_dispatch_builds_the_force_specs() {
let formula = chain_components(&[40]);
for spec in [
"force",
"force:treeify=cut",
"force:treeify=mst,dim=3,feedback=2",
"force:restarts=2",
] {
let a = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
let b = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
assert_eq!(
a.num_leaves(),
formula.num_vars,
"{spec} must build a leaf-complete vtree",
);
assert_eq!(
a.to_vtree_text(),
b.to_vtree_text(),
"{spec} must build the same vtree every time",
);
}
}
#[test]
fn spec_dispatch_builds_the_guided_bisect_specs() {
let formula = chain_components(&[40]);
for spec in [
"guided-bisect",
"guided-bisect:budget=20000steps,iters=4",
"flowcutter-incidence:binarize=edge,place=shallow,root=centroid",
] {
let v = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
assert_covers_all_vars(&v, formula.num_vars, spec);
}
}
#[test]
fn an_unknown_base_fails_validation_and_build() {
let validation_error = validate_vtree_spec("nonsense")
.expect_err("the public validator must reject an unknown base")
.to_string();
assert!(
validation_error.contains("nonsense") && validation_error.contains("unknown vtree type"),
"the validation error must name the spec, got: {validation_error}",
);
let formula = CnfFormula {
num_vars: 2,
clauses: vec![Clause::new(vec![
Literal::new(VarId(0), true),
Literal::new(VarId(1), false),
])],
};
let err = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok("nonsense"),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.map(|_| ())
.expect_err("an unknown base cannot build")
.to_string();
assert!(
err.contains("nonsense") && err.contains("unknown vtree type"),
"the unknown-spec error must name the spec, got: {err}",
);
}
#[test]
fn the_minfill_spec_is_the_internal_minfill() {
let formula = chain_components(&[40]);
let from_spec = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok("minfill-primal"),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.expect("the minfill-primal spec must build")
.vtree;
let internal = crate::decompose::vtree_from_minfill(
&formula,
crate::decompose::INTERNAL_ELIMINATION_SEED,
crate::decompose::ConversionRequest::open(Reading::default(), None),
)
.expect("the internal min-fill must build");
assert_eq!(
from_spec.to_vtree_text(),
internal.vtree.to_vtree_text(),
"the minfill-primal spec and the internal min-fill entry must be one construction",
);
}
#[test]
fn spec_dispatch_builds_every_elimination_spec() {
let formula = chain_components(&[40]);
for name in crate::decompose::elimination_spec_names() {
for spec in [
format!("{name}-primal"),
format!("{name}-incidence"),
format!("{name}-primal:seed=7"),
] {
let vt = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(&spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
assert_eq!(
vt.num_leaves(),
formula.num_vars,
"{spec} must build a leaf-complete vtree",
);
}
}
}
#[test]
fn the_retired_per_order_spelling_is_rejected() {
let formula = chain_components(&[40]);
for spec in [
"goatd-elimination-MinFill",
"goatd-elimination-MinDegree-inc",
] {
let err = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.map(|_| ())
.expect_err("the retired spelling cannot build")
.to_string();
assert!(
err.contains(spec) && err.contains("minfill") && err.contains("mindegree"),
"{spec} must be refused with the replacement names, got: {err}",
);
}
}
#[test]
fn spec_dispatch_builds_all_simple_specs() {
let formula = chain_components(&[40]);
for spec in ["balanced", "linear", "reverse-linear", "random"] {
assert!(
validate_vtree_spec(spec).is_ok(),
"{spec} must validate as a simple vtree",
);
let vt = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
assert_eq!(
vt.num_leaves(),
formula.num_vars,
"{spec} must build a leaf-complete vtree",
);
}
}
#[test]
fn the_unrefined_goatd_spelling_builds_the_unrefined_construction() {
let formula = chain_components(&[40]);
let spec = "goatd-primal:refine=off";
let built = build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree;
let direct = crate::decompose::vtree_from_goatd(
&formula,
crate::decompose::GraphKind::Primal,
0,
crate::decompose::ConversionRequest::open(Reading::default(), None),
)
.expect("the unrefined construction must build");
assert_eq!(
built.to_vtree_text(),
direct.vtree.to_vtree_text(),
"{spec} must build exactly what the unrefined construction builds",
);
}
#[test]
fn the_minfill_spec_names_the_minfill_order() {
assert_eq!(
crate::spec::classify_base(crate::decompose::MINFILL_SPEC),
crate::spec::VtreeBase::Elimination {
name: "minfill",
incidence: false,
},
);
}
#[test]
fn the_primal_bisect_spec_reaches_the_primal_bisector() {
let formula = chain_components(&[40]);
let build = |spec: &str| {
build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree
};
for (spec, imbalance) in [
("primal-bisect", crate::decompose::IMBALANCE_BALANCED),
("primal-bisect:imbalance=0.4", 0.4),
] {
let vt = build(spec);
assert_covers_all_vars(&vt, formula.num_vars, spec);
let direct = crate::decompose::vtree_from_primal_bisect(
&formula,
crate::decompose::BisectDials {
imbalance,
base_seed: 0,
effort_scale: 1.0,
},
)
.expect("the primal bisector must build");
assert_eq!(
vt.to_vtree_text(),
direct.to_vtree_text(),
"{spec} did not build what the primal bisector builds at that imbalance",
);
}
}
#[test]
fn a_conversion_key_written_on_an_elimination_spec_changes_the_tree_it_builds() {
let formula = chain_components(&[40]);
let build = |spec: &str| {
build_one_vtree_artifacts(BuildRequest {
formula: &formula,
spec: &parse_ok(spec),
ctx: &SelectionCtx::plain(),
limits: &BuildLimits::default(),
})
.unwrap_or_else(|e| panic!("{spec} must build: {e}"))
.vtree
};
let searched = build("minfill-primal");
assert_covers_all_vars(&searched, formula.num_vars, "minfill-primal");
let mut trees = std::collections::HashSet::new();
for spec in [
"minfill-primal:root=first,place=deep,binarize=hypergraph",
"minfill-primal:root=first,place=shallow,binarize=balanced",
"minfill-primal:root=centroid,place=deep,binarize=edge",
] {
let vt = build(spec);
assert_covers_all_vars(&vt, formula.num_vars, spec);
assert!(
trees.insert(vt.to_vtree_text()),
"{spec} built a tree another reading had already built",
);
}
}