Expand description
§Choosing a vtree
A TDD tests its variables in the shape of a vtree, a binary tree over them,
and the same formula can give a small circuit on one vtree and a huge one on
another. This example compiles one formula on four vtrees and compares the
circuits. count chooses a vtree by itself; the steps below
are for a caller who wants to choose.
Run the complete program with cargo run --example vtree_choice.
§A formula whose numbering misleads
Two 12-bit registers x and y hold the same value, and it is not zero.
Variable i is bit i of x and variable i + 12 the same bit of y.
Each bit gives two clauses, ¬xᵢ ∨ yᵢ and xᵢ ∨ ¬yᵢ, and one more clause,
x₁ ∨ … ∨ x₁₂, says some bit of x is set:
use tididi_cnf::compile::{compile_cnf, CompileOptions};
use tididi_cnf::parse_dimacs;
use tididi_cnf::vtree::{build_vtree, VtreeOptions};let bits = 12;
let mut dimacs = format!("p cnf {} {}\n", 2 * bits, 2 * bits + 1);
for i in 1..=bits {
let j = i + bits;
dimacs += &format!("-{i} {j} 0\n{i} -{j} 0\n");
}
let nonzero: Vec<String> = (1..=bits).map(|i| i.to_string()).collect();
dimacs += &format!("{} 0\n", nonzero.join(" "));
let (formula, _header) = parse_dimacs(dimacs.as_bytes())?;Each bit of y depends only on its own bit of x, but the numbering puts
every bit of x before every bit of y.
§Compile on each vtree
build_vtree builds a vtree from a spec, the
--vtree argument of the binary, and
compile_cnf compiles the formula on it
inside the vtree’s context:
for spec in ["linear", "balanced", "minfill-primal", "portfolio"] {
let vtree = build_vtree(&formula, spec, &VtreeOptions::default())?;
let diagram = vtree
.context()
.run(|engine| compile_cnf(engine, &formula, &vtree, CompileOptions::default()))?
.diagram;
println!("{spec:>14}: {:>5} nodes, {} models", diagram.node_count(), diagram.model_count()?);
}Output:
linear: 12282 nodes, 4095 models
balanced: 8543 nodes, 4095 models
minfill-primal: 119 nodes, 4095 models
portfolio: 46 nodes, 4095 modelsEvery circuit counts the same 4095 models, one per nonzero value. linear
follows the numbering, as an OBDD’s variable order would: once it has read
all of x it must remember the whole value to check y against it, one
path for each of its 4095 values. balanced splits the numbering in half at
its root, x from y, and has the same value to carry across that split.
minfill-primal reads the clauses instead: an elimination order on the graph
of variables that share a clause puts each bit of x beside its bit of y,
and the circuit needs about ten nodes per bit. portfolio, the default, builds
several such vtrees, scores them against the formula and keeps the best.
Without the nonzero clause the formula falls apart into twelve independent pairs, and the compiler compiles each pair on its own: every vtree then gives the same small circuit.
The specs and the constructions behind them are documented with vitri.
Continue with the configuration example, which counts with the vtree chosen for you, or see the complete program.