Bex is a rust crate for working with binary expressions.
- ZDD (Zero-suppressed Decision Diagram) module. New
src/zdd.rsaddsZddBase, implementing both a family-of-sets API and theBasetrait for Boolean-function compatibility with the swap solver and other tooling.- Family-of-sets ops:
union,intersect,diff,product,quotient,remainder(Minato's unate cube set algebra), pluschange,onset,offset,subset0,subset1,count,complement,power_set. Basetrait:and/or/xor/itemapped to family ops with lazy universe tracking for complement semantics.ZddSetIteratorfor native family enumeration;ZddSolIteratorfor Boolean solution enumeration via BDD conversion.- Graphviz rendering via
dot.
- Family-of-sets ops:
- Direct single-threaded ITE path for
BddBase. Opt in withBddBase::set_direct_ite(true)(orbex_bdd_set_direct_ite(bdd, true)from C) to bypass swarm dispatch and recurse directly with a localFxHashMapcomputed table. Designed for workloads that build BDDs bottom-up via many small sequential ITE calls, where channel-dispatch overhead dominates. Default is unchanged (swarm).- Reduced the
bdd-benchmarkqueens N=8 runtime from "hangs" to ~580 ms in the bdd-benchmark adapter; unblocks tic-tac-toe, hamiltonian, and game-of-life too.
- Reduced the
- New FFI
bex_swap_copy_to_bdd(swap, bdd, n)so C callers can transfer a swap-solver result into a separateBddBaseand use the normalbex_bdd_node_count/bex_bdd_solution_counton it. VhlSwarmnow exposesget_done/put_done/vhl_to_nidso callers can check the shared computed table or construct nodes without dispatching a job.
-
tbl::merge_smallwrote past the end of a 5-element array before returningNoneon 6-variable ITEs, causing panics in the truth-table fast path. Thelen > 5guards are nowlen >= 5. -
tbl::nid_to_smallaccepted real-variable NIDs with an index >=MAX_VARand fed them into the combinatorial encoder, which panicked with an array bounds error. Now it returnsNoneso the caller falls back to the regular BDD path. This unblocked thebdd-benchmarkhamiltonian workload, which allocates more than 110 variables. -
Table NIDs with named variables. Functions of up to 5 input variables are now stored directly in the NID as a truth table, with the variable set encoded using a combinatorial number system (combinadic). This avoids allocating BDD nodes for small subexpressions.
- New
NID::fun_with_vars(&[u32], tbl)constructor for explicit variable sets. NidFungainsvars(),top_vid(),contains_var(),var_position().- New
src/comb.rsmodule: combinadic encode/decode for variable subsets of up to 110 variables. - New
src/tbl.rsmodule: truth table alignment, expansion, and bitwise operations (table_and,table_xor,table_or,table_ite) with zero-allocation fast path. BddBase::ite()now automatically resolves small operations via truth tables before entering the BDD swarm (zero overhead on large problems, avoids node allocation for small ones).VhlBase::tup()decomposes table NIDs into hi/lo branches transparently.- Display format
T{x3,x7:1110}for table NIDs with non-default variable sets;FromStrround-trips the new format.
- New
Older upcoming changes for 0.4.0 live in the README section titled "Changes in main branch (upcoming version)".
- ~22% speedup on BDD factoring benchmark (
small: factor 210 into 8x16-bit integers)- Replace
HiLoCacheMutexwithRwLock+ combinedget_or_insertto reduce lock contention - Increase
DashMapshard count from 16 to 128 for better concurrent access - Pre-size
HiLoCacheHashMap andDashMapto 256K entries, eliminating rehash cascades (Massif profiling showed 90% of heap allocations went to hash table resizing) - Added
bench-smallexample for quick single-run benchmarking - Added doc/optimization-ideas.md with 24 profiling-driven ideas (3 applied, 12 tested/rejected with rationale)
- Replace
- Snapshot persistence for
VhlScaffold,BddBase, andANFBase(bex#6). Resolves the long-standing request to load and save intermediate solver state. A newsql_snapmodule adds four SQLite tables (snapshot,snapshot_vid,snapshot_node,snapshot_root) alongside the existing AST schema; each snapshot captures the graph plus its variable permutation (critical forSwapSolver, whose scaffold reordering changes at everysubststep). Snapshots chain viaparent_idto form a replay trace.- New public API:
sql_snap::{write_scaffold, write_bdd, write_anf, read_scaffold, read_bdd_into, read_anf_into, list_snapshots}plus path-based wrappers andensure_snap_schema. VhlScaffold: newiter_nodes,from_raw,is_mid_regroup.SwapSolver: newdx,rv,from_partsfor resume.ANFBase: newnodes,tags,tags_mut,insert_vhl.sql: newensure_schema_pub,ensure_schema_txhelpers so callers can share a transaction with snapshot writes.- Schema is additive — existing AST-only
.sdbfiles load unchanged;list_snapshotsreturns empty for files without snapshot tables.
- New public API:
-
New binary
bex-sdb— CLI for inspecting snapshot databases. Subcommands:list,info,dump,ast,replay. -
New binary
bex-mkproblem— generates AST.sdbfiles for primorial factoring problems (bex-mkproblem -p 4 -o primorial-4.sdb). -
New binary
bex-solve— drivesSwapSolver/BddBase/ANFBasethrough the substitution solve loop, auto-committing a snapshot to the same.sdbfile after every N steps. Supports--solver swap|bdd|anf,--save-every N,--resume <snap-id>,--timeout <secs>, and-o <output.sdb>. -
solve::refine_oneis nowpubso external drivers can call it. -
Migrated
benches/bench-solve.rsfrombenchertodivan(bex#4). Benchmark output now includes median / mean / stddev on a tree-structured terminal report, plus per-iteration allocation byte and count statistics (viadivan::AllocProfiler) — useful for catching silent allocation regressions inite/and/xoronBddBase. Filtering and sample-count control work via the standardcargo bench -- <filter> --sample-count NCLI.- Factoring benches are defined via a
factor_benches!macro table: one row per size (tiny,small, ...), each with its ownsample_count. Adding a new size is a one-line addition. - New
ops::{and_chain, xor_chain, ite_chain}alloc benches sweep N ∈ {8, 16, 32} variables and report the heap-allocation cost of reducing a chain of inputs via each primitive.BddBasesetup and teardown are excluded from the measurement (viawith_inputs+bench_refs), so the reported count is the marginal per-op cost.
- Factoring benches are defined via a
- Greatly expanded and fleshed out the python integration, including support for @tulip-control/dd
- Added a variety of new functions to
BddBase:reorderfor arbitrary reorderingsreorder_by_forcefor the FORCE algorith, a fast (but not always as effective) alternative to variable siftingto_jsonandfrom_jsonto serialize and restore a set of nids
- Added a simple HTTP API for integrating with other languages.
- Added new
Funtrait andNidFunstruct, refining the idea of storing truth tables of up to 5 inputs in a NID. - Added
ASTBase::{apply,eval} naf.rs(a variation of ANF)- VhlSwarm (extracted a generic VHL swarm framework from BddSwarm, to re-use on other VHL-based mods)
- Began standardizing the formatting/parsing of NIDs (
FromStrandfmt::Displayshould now round-trip) - Many other small fixes and cleanups.
BddBase is now 100 times faster (or more, depending on your CPU count!)
-
worker threads are no longer killed and respawned for each top-level query.
-
Extract
wip:WorkStatefrombdd_swarm, introducing a shared queue and concurrent hashmaps so workers can share work without supervision from the main thread. -
the workers now use concurrent queues and hashmaps (thanks to
boxcaranddashmap) to share the cache state. -
Dropped
hashbrowncrate for non-shared hashmaps, since it is now the implementation that comes with rust standard library. -
Added
fxhashas the hasher for all hashmaps. -
Removed top level functions in
nid::. Use the correspondingnid::NID::methods instead. (ex:nid::raw(n)is nown.raw()) In particular,nid::not(n)should be written!n. -
solve::find_factorsis now a generic function rather than a macro.
Aside from the addition of the ops module, this is primarily
a benchmark release to make it easier to compare the 0.1.5
algorithms with 0.2.0.
-
Rename
BDDBasetoBddBase, and addreset()method. -
Add
BddBase::reset(&mut self)to clear bdd state. -
Cleaned up all compiler warnings.
-
Removed all debug output.
-
Fixed test failures that appeared with different threading configurations.
-
Remove
nvarsfrom allBaseimplementations. This member was only really useful when the height of a node wasn't obvious from the variable index. Because of this,Base::new()no longer takes a parameter. -
Remove obsolete "substitution" concept from
ast.rs, and replaceast::Opwith the more flexibleops::Ops.
Same as 0.1.7 except I forgot to update the readme. :D
-
Added
SwapSolver, a new substitution solver that (like anySubSolver) works by iteratively replacing virtual variables (representing AST nodes) with their definitions inside a BDD. What's new here is thatSwapSolvercontinuously re-orders the variables (rows) in the BDD at each step so that the substitution is as efficient as possible. -
Added
XVHLScaffold, a data structure for decision-diagram-like graphs, that allows accessing each row individually. This structure should be considered extremely experimental, and may change in the future (as it does not currently useNIDfor node references). -
Added
swarmmodule that contains a small framework for distributing work across threads. It is used by theSwapSolverto swap BDD rows in parallel, and follows the same design asBddSwarm, which will likely be ported over to this framework in the future. -
Added
opsmodule for representing boolean expressions in something like reverse Polish notation. TheOps::RPNconstructor will likely replaceast::Opas the representation of nodes inast::ASTBasein a future version, sinceOps::RPNcan represent arbitrary boolean functions with any number of inputs.
This version introduces the ANFBase for working with algebraic normal form using a BDD-like graph structure. This version also introduces Cursors, which provide the ability to iterate through BDD solutions and ANF terms.
It also includes a major refactoring effort: the BDD, AST, and ANF bases now all use the same NID/VID types for node and variable identifiers.
Finally, BDD and ANF graphs are now arranged so that variables with the smallest identifiers now appear at the bottom (so that subgraphs are more likely to be shared across functions with different numbers of inputs, and also so that the size of a node's truth table is immediately apparent from its topmost variable.)
vid::VID
VIDis now an explicit custom type rather than a simple usize. It accounts for both "real" variables (var()) and virtual ones (vir()), as well as the meta-constantT(true) which fills the branch variable slots for theIandOBDD nodes.
smaller variables now appear at the bottom of BDD, ANF graphs.
- A new type for
VIDcomparison was introduced -vid::VidOrdering. This lets youcmp_depthusing termsAbove,Level, andBelowrather than theLess,Equal,Greateryou get withcmp. There was no technical reason for this, but I found it much easier to reason about the code in these terms. VidOrderingis set up so that branch variables with smaller numbers move to the bottom of a BDD. There are numerous benefits to doing this - cross-function cache hits, immediate knowledge of the width of a node's truth table, and (most importantly) a much simpler time converting from ANF to BDD.- There is currently no support for the "industry standard" ordering - the plan in the future is just to make sure the graphviz output shows variable names, and then you can just re-arrange the labels.
NID as universal ID
ASTBasenow usesnid::VIDfor input variable identifiers, andnid::NIDfor node identifiers, rather than using simpleusizeindices. This means we no longer need to store explicit entries for constants and literals.- The
Basetrait no longer takes type argumentsNandV, since all implementations now usenid::NIDandvid::VID.
Reg type
Regprovides an general purpose register containing an arbitrary number of bits.- Bits in a Reg can be accessed individually either by number (with
get(ix)andput(ix,bool)), or using aVID(var_get,var_put). Indexing by virtual variables is not supported. Regalso provides a simpleincrement()method, as well as the more generalripple(start,end). These treat the register as a binary number, "add 1" at a specified location, and ripple-carry the result until a 0 is encountered, or the carry overflows the end position. This is all intended to supportCursor.
Cursor
cur::Cursorcombines aRegwith a stack ofNIDs to provide a tool for navigating through the terms or solutions in a BDD/ANF-like graph structure.
BDDBase
- You can now call
solutions()on aBDDBaseto iterate through solutions of the BDD. Each solution is presented as aRegof lengthnvars().
ANFBase
- The new
anfmodule contains the beginnings of a BDD-like structure for working with expressions in algebraic normal form (XOR-of-ANDs). These two operations plus the constantIgive a complete functional base. The implementation does not yet take advantage of multiple cores.
code cleanup
- Swarming is now the only implementation for BDDBase. (#7)
cargo testnow runs quickly, without generating diagrams (#3)- Unify the AST and BDD "Base" interfaces. (#2, #5)
basenow contains only the abstract traitBase(formerlyTBase)Basemethods now act on associated typesSelf::NandSelf::V, rather thanNIDandVIDdirectly.- The old
struct base::Baseis nowast::ASTBase. Methodssid,sub, andwhen(which might not apply to other implementations) have been moved out oftrait Baseand intostruct ASTBasedirectly. - The old
base::{Op,SID,SUB,NID,VID}types have also moved to theastmodule. bdd::BddBasenow implementsbase::Base.- Some of the tests for
astandbddhave been macro-fied and moved intobase. These macros allow re-using the same test code for eachBaseimplementation.
- The
bdd::NIDtype and associated helper functions have been moved intonidso the same scheme can be reused for otherBaseimplementations.
documentation
- Began writing/collecting more documentation in the doc/ directory.
I got most this working back in December and then put it all aside for a while. It's still pretty messy, but I'm starting to work on it again, so I figured I would ship what I have, and then aim for more frequent, small releases as I continue to tinker with it.
multi-threaded workers
- refactored
bddso that theBddStateis now owned by aBddWorker. Further, bothBddStateandBddWorkerare now traits. - Moved
BddWorkerimplementation intoSimpleBddWorker. - Provided multiple implementations for
BddState-- (so far, one with and one without array bounds checking). - Added a multi-core bdd worker:
BddSwarm. Between threading and an out-of-order execution model that results in potential short circuiting,ite()calls that once took 30 or more seconds on my low-end 2-core laptop now run in 0 seconds!
code tuning
- added
solve::sort_by_costwhich optimizes the ast→bdd conversion to take only onebdd_refine_onestep per AST node (improved my still-external benchmark script by an order of magnitude). - in
bdd,ite_normnow constructs hi/lo nodes directly from input rather than callingwhen_xx. This resulted in about a 23% speedup.
(rudimentary) example programs
examples/bdd-solve.rsdemonstrates one method of using bex to solve arbitrary problems. (Albeit very very slowly, still...)examples/bex-shell.rsis a tiny forth-like interpreter for manipulating expressions interactively.- See examples/README.md for more details.
other improvements
solve::ProgressReportcan now simply save the final result instead of showing it (asdotcan take a very long time to render it into a png). It also now shows progress as a percentage (though only currently accurate whensort_by_costwas called)
- added
Cargo.tomldocumentation link to docs.rs/bex - added this changelog
- Renamed
bex::x32tobex::int, used macros to generalize number of bits, addedtimes,lt, andeqfunctions - Added
bex::solvefor converting between ast and bdd representations. - Added distinction between
real(input) andvirtual(intermediate) variables inbdd::NID - Added graphviz (
*.dot) output forbase::Baseand improved formatting forbdd::BDDBase - Various performance enhancements for
bex::bdd. Most notably:- switched caches to use the
hashbrowncrate (for about a 40% speedup!) - added inlining hints for many functions
- re-ordered logic in bottleneck functions (
norm,ite_norm) to minimize work bdd::NIDis now a single u64 with redundant information packed into the NID itself. This way, decisions can be made looking at the NID directly, without fetching the actual node.- Disabled bounds checking for internal node lookups. (unsafe)
- switched caches to use the
- Refactored
bex::bddin preparation for multi-threading.- Grouped the internal node lists and the caches by branching variable (VID). This isn't actually an optimization, but I expect(ed?) it to make concurrent solving easier in the future.
- moved all the unsafe, data-mutating operations into a handful of
isolated functions on a single source page. These will likely be
factored out into a new
Workerstruct, eventually.
Initial public version. Work-in-progress code imported from a private repo.