use super::*;
#[test]
fn the_accepted_spec_set() {
for spec in [
"balanced",
"linear",
"reverse-linear",
"random",
"random-anything",
"portfolio",
"hypergraph-bisect",
"hypergraph-bisect:imbalance=0.4",
"primal-bisect",
"primal-bisect:imbalance=0.4",
"goatd-primal",
"goatd-primal:seed=7",
"goatd-incidence",
"goatd-incidence:seed=3",
"goatd-primal:refine=off",
"goatd-incidence:refine=on,seed=3",
"goatd-incidence:seed=3,binarize=edge",
"goatd-primal:candidate=1",
"goatd-incidence:candidate=7,seed=3",
"minfill-primal",
"minfill-primal:ties=jw-sample",
"mindegree-primal",
"mindegree-primal:ties=jw-sample",
"nested-dissection-primal",
"minfill-primal:seed=7",
"minfill-incidence",
"minfill-primal:ties=jw-sample,seed=7",
"mindegree-incidence:seed=3",
"nested-dissection-incidence",
"minfill-incidence:binarize=hypergraph",
"mindegree-primal:place=shallow,root=centroid,binarize=edge",
"flowcutter-primal",
"flowcutter-incidence",
"flowcutter-primal:budget=200ms",
"flowcutter-primal:budget=200ms,iters=50",
"flowcutter-primal:budget=200ms,iters=50,patience=20",
"flowcutter-primal:budget=100000steps,iters=900",
"flowcutter-primal:budget=100000steps,iters=900,place=shallow",
"flowcutter-incidence:binarize=edge,place=shallow",
"flowcutter-incidence:root=first,place=deep",
"flowcutter-primal:root=leaf",
"flowcutter-primal:root=centroid,binarize=hypergraph",
"flowcutter-incidence:binarize=hypergraph",
"flowcutter-primal:binarize=balanced",
"flowcutter-incidence:budget=200ms,place=shallow",
"flowcutter-incidence:binarize=edge,place=shallow,root=centroid",
"flowcutter-incidence:binarize=edge,place=shallow,root=leaf",
"guided-bisect",
"guided-bisect:budget=200ms",
"guided-bisect:budget=200ms,iters=50",
"guided-bisect:budget=150000steps,iters=15",
"force",
"force:treeify=mst",
"force:treeify=cut",
"force:dim=3,feedback=2",
"force:treeify=mst,root=balance,orient=small,weights=co,clause-weight=short,dim=4,\
feedback=8,restarts=16,init=force1d",
"force:treeify=cut,clause-weight=short,dim=3,restarts=4,init=force1d",
] {
assert!(
validate_vtree_spec(spec).is_ok(),
"{spec} is in the catalog and must be accepted",
);
}
for (spec, needle) in [
(
"nested-dissection-primal:ties=jw-sample",
"nested-dissection",
),
(
"nested-dissection-incidence:ties=jw-sample",
"nested-dissection",
),
("minfill-primal:refine=off", "refine"),
("goatd-incidence:ties=jw-sample", "ties"),
("hypergraph-bisect:budget=200ms", "budget"),
("guided-bisect:root=centroid", "root"),
("guided-bisect:place=shallow", "place"),
("guided-bisect:binarize=edge", "binarize"),
("flowcutter-primal:bogus", "bogus"),
("force:mst", "mst"),
("goatd-incidence:7", "7"),
("portfolio:seed=5", "portfolio"),
("portfolio:binarize=edge", "portfolio"),
("balanced:place=shallow", "balanced"),
("random:seed=7", "random"),
("minfill-primal:seed=abc", "abc"),
("goatd-primal:budget=200ms", "budget"),
("goatd-primal:seed=abc", "abc"),
("goatd-primal:candidate=abc", "abc"),
("goatd-primal:candidate=8", "8"),
("goatd-primal:candidate=1,refine=off", "candidate"),
("minfill-primal:candidate=1", "candidate"),
("hypergraph-bisect:seed=3", "seed"),
("hypergraph-bisect:imbalance=abc", "abc"),
("flowcutter-primal:budget=bogus", "bogus"),
("flowcutter-primal:budget=abcms", "abcms"),
("flowcutter-primal:budget=200", "200"),
("flowcutter-primal:budget=200ms,iters=xi", "xi"),
("flowcutter-primal:budget=200ms,patience=xp", "xp"),
("flowcutter-primal:budget=abcsteps", "abcsteps"),
("flowcutter-primal:budget=900steps,patience=10", "patience"),
("flowcutter-primal:root=deepest", "deepest"),
("flowcutter-primal:place=middle", "middle"),
("flowcutter-primal:binarize=largest-first", "largest-first"),
("flowcutter-primal:td-root=centroid", "td-root"),
("flowcutter-primal:assign=shallow", "assign"),
("flowcutter-primal:order=vars-first", "order"),
("flowcutter-primal:var-order=affinity", "var-order"),
("flowcutter-incidence:assembly=hybrid", "assembly"),
("flowcutter-primal:best=on", "best"),
("flowcutter-primal:fold=edge", "fold"),
("flowcutter-primal:root=first-bag", "first-bag"),
(
"flowcutter-primal:binarize=children-first",
"children-first",
),
("flowcutter-primal:binarize=td-edge", "td-edge"),
(
"flowcutter-primal:binarize=children-by-size",
"children-by-size",
),
(
"flowcutter-primal:binarize=hypergraph-bisect",
"hypergraph-bisect",
),
(
"flowcutter-primal:binarize=boundary-adjacent",
"boundary-adjacent",
),
("guided-bisect:budget=bogus", "bogus"),
("guided-bisect:budget=abcms", "abcms"),
("force:treeify=bogus", "bogus"),
("force:dim=9", "9"),
("force:feedback=9", "9"),
("force:restarts=0", "0"),
("force:restarts=17", "17"),
("force:init=bogus", "bogus"),
("force:clause-weight=bogus", "bogus"),
("force:root=bogus", "bogus"),
("force:orient=bogus", "bogus"),
("force:weights=bogus", "bogus"),
("force:dim=3,dim=4", "dim"),
("force:binarize=edge", "binarize"),
("force:nonsense=1", "nonsense"),
("force:refine=goatd-incidence", "refine"),
("force:refine=none", "refine"),
("force:treeify=cut,root=merge", "root"),
("force:treeify=cut,orient=x", "orient"),
("force:treeify=cut,weights=co", "weights"),
("force:treeify=cut,feedback=2", "feedback"),
] {
let err = validate_vtree_spec(spec)
.expect_err(&format!("{spec} must be rejected"))
.to_string();
assert!(
err.contains(needle),
"{spec} must be rejected naming {needle:?}, got: {err}",
);
}
}
#[test]
fn a_binarization_the_search_no_longer_selects_is_refused_by_name() {
for value in [
"clause-split",
"by-size",
"vars-first",
"left-deep",
"boundary",
"affinity",
] {
let spec = format!("flowcutter-primal:binarize={value}");
let err = validate_vtree_spec(&spec)
.expect_err(&format!(
"{spec} names a binarization the search does not select"
))
.to_string();
assert!(
err.contains("binarize") && err.contains("flowcutter-primal"),
"{spec} must be refused naming the key and the family, got: {err}",
);
}
}
#[test]
fn a_parameter_the_family_cannot_honor_is_refused_by_name() {
for (spec, key) in [
("minfill-primal:imbalance=0.4", "imbalance"),
("flowcutter-primal:imbalance=0.4", "imbalance"),
("goatd-incidence:imbalance=0.4", "imbalance"),
("force:imbalance=0.4", "imbalance"),
("hypergraph-bisect:seed=3", "seed"),
("flowcutter-primal:seed=3", "seed"),
("balanced:seed=3", "seed"),
("hypergraph-bisect:place=deep", "place"),
("primal-bisect:binarize=edge", "binarize"),
("primal-bisect:root=centroid", "root"),
] {
let err = validate_vtree_spec(spec)
.expect_err(&format!("{spec} names a parameter its family cannot honor"))
.to_string();
assert!(
err.contains(key) && err.contains(spec.split(':').next().unwrap()),
"{spec} must be refused naming {key:?} and the spec, got: {err}",
);
}
}
#[test]
fn the_retired_suffix_spelling_names_no_construction() {
for spec in [
"flowcutter-primal/best",
"flowcutter-primal/shallow",
"minfill",
"minfill-inc",
"force-primal",
"portfolio-incidence",
"minfill-sample-jw",
"mindegree-sample-jw-inc",
"nested-dissection",
"goatd",
"hybrid-flowcutter-incidence",
"flowcutter-incidence/td-edge/shallow",
"goatd-incidence/best",
"force/d=3",
"hybrid",
] {
assert_eq!(
classify_base(spec),
VtreeBase::Unknown,
"{spec} writes a parameter the retired way and names no family",
);
}
for spec in ["force:mst/root=merge", "flowcutter-primal:200ms/best"] {
assert!(
validate_vtree_spec(spec).is_err(),
"{spec} writes a parameter the retired way and must be refused",
);
}
}
#[test]
fn classify_base_covers_every_family() {
use VtreeBase::*;
assert_eq!(classify_base("balanced"), Balanced);
assert_eq!(classify_base("linear"), Linear);
assert_eq!(classify_base("reverse-linear"), ReverseLinear);
assert_eq!(classify_base("portfolio"), Portfolio);
assert_eq!(
classify_base("flowcutter-primal"),
Flowcutter { incidence: false },
);
assert_eq!(
classify_base("flowcutter-incidence"),
Flowcutter { incidence: true },
);
assert_eq!(classify_base("guided-bisect"), GuidedBisect);
assert_eq!(classify_base("hypergraph-bisect"), HypergraphBisect);
assert_eq!(classify_base("force"), Force);
assert_eq!(classify_base("random"), Random);
assert_eq!(classify_base("random-anything"), Random);
assert_eq!(classify_base("goatd-primal"), Goatd { incidence: false },);
assert_eq!(classify_base("goatd-incidence"), Goatd { incidence: true });
for name in crate::decompose::elimination_spec_names() {
for (view, incidence) in crate::decompose::VIEW_SUFFIXES {
assert_eq!(
classify_base(&format!("{name}{view}")),
Elimination { name, incidence },
"{name}{view} names the order on that graph view",
);
}
assert_eq!(classify_base(name), Unknown, "{name} names no graph view");
}
assert_eq!(classify_base("goatd-elimination-MinDegree"), Unknown);
assert_eq!(classify_base("nonsense"), Unknown);
assert_eq!(classify_base(""), Unknown);
}
#[test]
fn every_advertised_base_and_parameter_is_one_the_parser_accepts() {
for base in vtree_spec_bases() {
assert!(
validate_vtree_spec(&base).is_ok(),
"{base} is offered by the vocabulary and must parse on its own",
);
for doc in spec_param_docs(&base) {
assert!(
!doc.values.is_empty() && !doc.default.is_empty() && !doc.what.is_empty(),
"{base}:{}= must say what it takes, what it defaults to and what it changes",
doc.key,
);
}
}
}
#[test]
fn the_vtree_doc_names_every_base_and_parameter() {
let doc = include_str!("../../../docs/vtrees.md");
for base in vtree_spec_bases() {
assert!(
doc.contains(&format!("`{base}`")),
"docs/vtrees.md does not name the base {base}",
);
for p in spec_param_docs(&base) {
assert!(
doc.contains(&format!("`{}`", p.key)),
"docs/vtrees.md does not name {}=, which {base} takes",
p.key,
);
if !p.default.contains(' ') {
assert!(
doc.contains(&format!("`{}`", p.default)),
"docs/vtrees.md does not give the default of {}=",
p.key,
);
}
}
}
}