tididi_cnf/
lib.rs

1//! A knowledge compiler and model counter for CNF.
2//!
3//! tididi-cnf compiles a DIMACS CNF formula into a Tree Decision Diagram
4//! (TDD), a canonical circuit that holds all of the formula's models, which
5//! the [`tididi`] library saves, combines with other circuits and queries as
6//! evidence or weights change. The same compiler is a model counter: it
7//! answers the model count, the weighted model count and their projections
8//! onto a set of variables, the model count and the projected counts exactly,
9//! the weighted model count in a bounded-precision log domain. It preprocesses
10//! the formula, chooses a vtree for what is left and compiles it bottom-up,
11//! with fallbacks when a compile runs out of time or memory. It is the library
12//! behind the `tididi` binary, and [`count`] makes the binary's whole run on
13//! one instance.
14//!
15//! # Compile a formula
16//!
17//! [`count`] takes an instance, the task, and the [`Limits`] to run under, and
18//! returns an [`Answer`]: the verdict, the [`Count`], the run's [`Stats`] and,
19//! for a compile, the [`diagram`](Answer::diagram). Here the instance is the
20//! eight queens puzzle, read from a DIMACS file whose variable 1 is a corner
21//! square, and the compiled circuit then counts the placements with a queen
22//! there without compiling again:
23//!
24//! ```
25//! use tididi_cnf::tididi::Literal;
26//! use tididi_cnf::{count, Limits, Mode};
27//!
28//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
29//! let answer = count("tests/data/queens8.cnf", Some(Mode::Compile), &Limits::default())?;
30//! let board = answer.diagram().expect("a compile keeps its circuit");
31//! println!("Placements: {}", board.model_count()?);
32//! let mut counter = board.counter()?;
33//! counter.observe([Literal::try_from(1)?])?;
34//! println!("With a queen in a corner: {}", counter.model_count()?);
35//! # assert_eq!(board.model_count()?, 92u32.into());
36//! # assert_eq!(counter.model_count()?, 4u32.into());
37//! # Ok(())
38//! # }
39//! ```
40//!
41//! Output:
42//!
43//! ```text
44//! Placements: 92
45//! With a queen in a corner: 4
46//! ```
47//!
48//! # Count its models
49//!
50//! A counting task returns the count without keeping a circuit. Here the
51//! instance is (x1 ∨ x2) ∧ (¬x1 ∨ x3) ∧ (¬x2 ∨ ¬x3), parsed from DIMACS text
52//! with [`parse_dimacs`], and the task is its model count:
53//!
54//! ```
55//! use tididi_cnf::{count, parse_dimacs, Limits, Mode};
56//!
57//! let formula = parse_dimacs("p cnf 3 3\n1 2 0\n-1 3 0\n-2 -3 0\n".as_bytes())?;
58//! let answer = count(formula, Some(Mode::Mc), &Limits::default())?;
59//! println!("Models: {}", answer.count());
60//! # assert_eq!(answer.count(), &tididi_cnf::Count::Integer(2u32.into()));
61//! # assert!(answer.satisfiable());
62//! # Ok::<(), tididi_cnf::Error>(())
63//! ```
64//!
65//! Output:
66//!
67//! ```text
68//! Models: 2
69//! ```
70//!
71//! With `None` for the mode, the instance's header chooses the task, as it does
72//! for the binary.
73//!
74//! A program that builds its formula holds it as data rather than text:
75//! [`formula_from_clauses`] takes the clauses as DIMACS integers, with a show
76//! set and literal weights for the tasks that read them, and returns what
77//! [`parse_dimacs`] returns for the same formula.
78//!
79//! ```
80//! use tididi_cnf::{count, formula_from_clauses, Limits, Mode};
81//!
82//! let formula = formula_from_clauses(3, [[1, 2], [-1, 3], [-2, -3]], None, &[])?;
83//! let answer = count(formula, Some(Mode::Mc), &Limits::default())?;
84//! println!("Models: {}", answer.count());
85//! # assert_eq!(answer.count(), &tididi_cnf::Count::Integer(2u32.into()));
86//! # Ok::<(), tididi_cnf::Error>(())
87//! ```
88//!
89//! Output:
90//!
91//! ```text
92//! Models: 2
93//! ```
94//!
95//! # Tasks and input
96//!
97//! The instance is DIMACS CNF. Its `c t mc|wmc|pmc|pwmc` line names the task
98//! ([`Mode`]): the model count, the weighted count, and their projected forms,
99//! with `c p weight` lines for the literal weights and a `c p show` line for
100//! the variables projected onto; a formula that declares neither a task nor a
101//! show set is compiled.
102//! The [user guide](https://github.com/Tractables/tididi-cnf/blob/main/docs/getting-started.md)
103//! describes the input format, the answers and the binary.
104//!
105//! # One step at a time
106//!
107//! Below [`count`] are the steps a compile is made of, for a caller that wants
108//! the diagram and its own choices at each: [`parse_dimacs`] reads the
109//! formula, [`vtree::build_vtree`] builds a vtree for it from a spec with its
110//! [`vtree::VtreeOptions`], and [`compile::compile_cnf`] compiles it on that vtree
111//! under its [`compile::CompileOptions`] into a [`tididi::Tdd`], the
112//! [`diagram`](compile::Compiled::diagram) of what it returns, whose queries
113//! the [`tididi`] crate provides. This path runs no preprocessing and no
114//! fallback.
115//!
116//! ```
117//! use std::fs::File;
118//! use std::io::BufReader;
119//!
120//! use tididi_cnf::compile::{compile_cnf, CompileOptions};
121//! use tididi_cnf::vtree::{build_vtree, VtreeOptions};
122//!
123//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
124//! // Five queens on a 5×5 board, none attacking another.
125//! let file = BufReader::new(File::open("tests/data/queens5.cnf")?);
126//! let (formula, _header) = tididi_cnf::parse_dimacs(file)?;
127//! let vtree = build_vtree(&formula, "portfolio", &VtreeOptions::default())?;
128//! let diagram = vtree
129//!     .context()
130//!     .run(|engine| compile_cnf(engine, &formula, &vtree, CompileOptions::default()))?
131//!     .diagram;
132//! println!("{} placements", diagram.model_count()?);
133//! # assert_eq!(diagram.model_count()?, 10u32.into());
134//! # Ok(())
135//! # }
136//! ```
137//!
138//! Output:
139//!
140//! ```text
141//! 10 placements
142//! ```
143//!
144//! # Applications
145//!
146//! The [configuration example](guide::examples::catalog) counts a
147//! product's configurations, projects them onto some of the options, and
148//! queries the compiled rules as a customer chooses.
149//! The [reliability example](guide::examples::reliability) weighs links by
150//! their probabilities to find how likely two sites stay connected.
151//! The [circuit example](guide::examples::circuits) counts the distinct
152//! products a multiplier outputs.
153//! The [vtree example](guide::examples::vtree_choice) compiles one formula on
154//! several vtrees and compares the circuits.
155//!
156//! The [guide] holds these worked examples and the architecture reference.
157//!
158//! # Limits
159//!
160//! [`Limits`] are the binary's `--time-limit-ms` and `--mem-limit-mb`. The
161//! time limit bounds the whole run: every stage gets a share of it, and a run
162//! that reaches it returns [`Error::Incomplete`], never a partial count. The
163//! memory limit lowers the process's address-space limit while the run lasts,
164//! and a model count that is refused memory retries on smaller pieces of the
165//! formula; the call puts the limit back when it returns. The default sets
166//! neither. A run that nears the address-space limit switches the allocator to
167//! handing freed pages back to the system at once, and the process keeps that
168//! setting after the run returns.
169//!
170//! Calls from several threads run one at a time: a [`count`] call waits for
171//! the one in progress to return. [`count`] prints its progress to stderr, as
172//! the binary does.
173//!
174//! # Platform
175//!
176//! Linux, and WebAssembly through Emscripten (`wasm32-unknown-emscripten`).
177//! Preprocessing and vtree construction come from the
178//! [vitri](https://github.com/Tractables/vitri) crate, which builds CaDiCaL,
179//! Arjun and their dependencies from source and links them natively; its
180//! [building guide](https://github.com/Tractables/vitri/blob/main/docs/building.md)
181//! lists the C++ toolchain it needs, for either target. Natively the crate
182//! installs jemalloc as the global allocator, so a program that links it
183//! cannot choose its own, and it exports jemalloc's startup configuration,
184//! which then holds for the whole process. Recovering from a refused
185//! allocation needs `panic = "unwind"`, the default. WebAssembly keeps the
186//! target's own allocator and has no address-space limit to lower, so it
187//! refuses [`Limits::mem_limit_mb`].
188//!
189//! # Module reference
190//!
191//! - The crate root: [`count`] and what it takes and returns ([`Input`],
192//!   [`Mode`], [`Limits`], [`Answer`], [`Count`], [`Stats`], [`Error`]), and
193//!   the two ways a formula comes in, [`parse_dimacs`] and
194//!   [`formula_from_clauses`].
195//! - [`vtree`]: vtree construction from a spec, whole or one tree per
196//!   independent component.
197//! - [`compile`]: the compiler, [`compile::compile_cnf`], and what it takes and
198//!   returns.
199//! - [`tididi`]: the diagram library, re-exported at the version this crate
200//!   compiles against.
201//! - [`vitri`]: the preprocessing and vtree library, re-exported for the
202//!   formula types [`parse_dimacs`] and [`formula_from_clauses`] return.
203//! - [`guide`]: the worked examples and the architecture reference.
204
205#![warn(missing_docs)]
206#![warn(rustdoc::broken_intra_doc_links)]
207#![warn(rustdoc::private_intra_doc_links)]
208#![warn(unreachable_pub)]
209
210pub mod compile;
211pub mod guide;
212pub mod vtree;
213
214mod budget;
215mod cli;
216mod config;
217mod convert;
218mod driver;
219mod enumeration;
220mod error;
221mod formula;
222mod frontend;
223#[cfg_attr(target_family = "wasm", path = "mem_wasm.rs")]
224mod mem;
225mod output;
226mod problem;
227mod run;
228mod stage;
229
230#[cfg(test)]
231#[path = "tests/support.rs"]
232pub(crate) mod test_support;
233
234/// One mebibyte, the unit memory limits are given in and the memory reports
235/// print in. Here rather than in `mem`, whose WebAssembly stand-in the reports
236/// build against too.
237const MIB: u64 = 1024 * 1024;
238
239/// The diagram library this crate compiles into: vtrees, diagrams, the engine
240/// and its queries.
241pub use tididi;
242
243/// The library this crate reads formulas and builds vtrees with. Its
244/// [`CnfFormula`](vitri::cnf::CnfFormula) and [`CnfMeta`](vitri::cnf::CnfMeta)
245/// are the formula types [`parse_dimacs`] and [`formula_from_clauses`] return
246/// and [`compile`] takes.
247pub use vitri;
248
249pub use config::{Limits, Mode};
250pub use error::Error;
251pub use formula::{formula_from_clauses, parse_dimacs};
252pub use output::Stats;
253pub use run::{Answer, Count, Input, count};
254
255#[doc(hidden)]
256pub use cli::run_cli;