espresso_logic/lib.rs
1//! # Espresso Logic Minimizer
2//!
3//! This crate provides Rust bindings to the Espresso heuristic logic minimizer
4//! (Version 2.3), a classic tool from UC Berkeley for minimizing Boolean functions.
5//!
6//! ## Overview
7//!
8//! Espresso takes a Boolean function represented as a sum-of-products (cover) and
9//! produces a minimal or near-minimal equivalent representation. It's particularly
10//! useful for:
11//!
12//! - Digital logic synthesis
13//! - PLA (Programmable Logic Array) minimization
14//! - Boolean function simplification
15//! - Logic optimization in CAD tools
16//!
17//! ## Three Ways to Use Espresso
18//!
19//! ### 1. Boolean Expressions (Recommended for most use cases)
20//!
21//! Build expressions programmatically and minimize them:
22//!
23//! ```
24//! use espresso_logic::{BoolExpr, expr};
25//!
26//! # fn main() -> std::io::Result<()> {
27//! let a = BoolExpr::variable("a");
28//! let b = BoolExpr::variable("b");
29//! let c = BoolExpr::variable("c");
30//!
31//! // Build a redundant expression: a*b + a*b*c
32//! let redundant = expr!(a * b + a * b * c);
33//!
34//! // Minimize it (returns a new minimized expression)
35//! let minimized = redundant.minimize()?;
36//!
37//! println!("Minimized: {}", minimized); // Output: a*b
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! Parse expressions from strings:
43//!
44//! ```
45//! use espresso_logic::BoolExpr;
46//!
47//! # fn main() -> Result<(), String> {
48//! // Parse using standard operators: +, *, ~, !
49//! let expr = BoolExpr::parse("a * b + ~a * ~b")?;
50//!
51//! // Minimize
52//! let minimized = expr.minimize().map_err(|e| e.to_string())?;
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! ### 2. Cover Builder (Static dimensions with compile-time checking)
58//!
59//! Build covers with fixed dimensions known at compile time:
60//!
61//! ```
62//! use espresso_logic::{Cover, CoverBuilder};
63//!
64//! # fn main() -> std::io::Result<()> {
65//! // Create a cover for a 2-input, 1-output function
66//! let mut cover = CoverBuilder::<2, 1>::new();
67//!
68//! // Build the ON-set (truth table)
69//! cover.add_cube(&[Some(false), Some(true)], &[Some(true)]); // 01 -> 1
70//! cover.add_cube(&[Some(true), Some(false)], &[Some(true)]); // 10 -> 1
71//!
72//! // Minimize in-place
73//! cover.minimize()?;
74//!
75//! // Iterate over minimized cubes
76//! for (inputs, outputs) in cover.cubes_iter() {
77//! println!("Cube: {:?} -> {:?}", inputs, outputs);
78//! }
79//! # Ok(())
80//! # }
81//! ```
82//!
83//! ### 3. PLA Files (Dynamic dimensions from files)
84//!
85//! Load and minimize PLA files with dynamic dimensions:
86//!
87//! ```
88//! use espresso_logic::{Cover, PLACover, PLAType};
89//! # use std::io::Write;
90//!
91//! # fn main() -> std::io::Result<()> {
92//! # let mut temp = tempfile::NamedTempFile::new()?;
93//! # temp.write_all(b".i 2\n.o 1\n.p 1\n01 1\n.e\n")?;
94//! # temp.flush()?;
95//! # let input_path = temp.path();
96//! // Read from PLA file
97//! let mut cover = PLACover::from_pla_file(input_path)?;
98//!
99//! // Minimize
100//! cover.minimize()?;
101//!
102//! # let output_file = tempfile::NamedTempFile::new()?;
103//! # let output_path = output_file.path();
104//! // Write to PLA file
105//! cover.to_pla_file(output_path, PLAType::F)?;
106//! # Ok(())
107//! # }
108//! ```
109//!
110//! ## Cover Types
111//!
112//! The library supports different cover types for representing Boolean functions:
113//!
114//! - **F Type** - ON-set only (specifies where output is 1)
115//! - **FD Type** - ON-set + Don't-cares (default, most flexible)
116//! - **FR Type** - ON-set + OFF-set (specifies both 1s and 0s)
117//! - **FDR Type** - ON-set + Don't-cares + OFF-set (complete specification)
118//!
119//! ```
120//! use espresso_logic::{CoverBuilder, FType, FDType, Cover};
121//!
122//! # fn main() -> std::io::Result<()> {
123//! // F type (ON-set only)
124//! let mut f_cover = CoverBuilder::<2, 1, FType>::new();
125//! f_cover.add_cube(&[Some(true), Some(true)], &[Some(true)]);
126//!
127//! // FD type (ON-set + Don't-cares) - default
128//! let mut fd_cover = CoverBuilder::<2, 1, FDType>::new(); // or just CoverBuilder::<2, 1>::new()
129//! fd_cover.add_cube(&[Some(true), Some(true)], &[Some(true)]); // ON
130//! fd_cover.add_cube(&[Some(false), Some(false)], &[None]); // Don't-care
131//! # Ok(())
132//! # }
133//! ```
134//!
135//! ## Thread Safety and Concurrency
136//!
137//! **This library IS thread-safe!** The underlying C library uses **C11 thread-local storage**
138//! (`_Thread_local`) for all global state. Each thread gets its own independent copy of all
139//! global variables, making concurrent use completely safe without any synchronization.
140//!
141//! ### Multi-threaded Applications
142//!
143//! Just use `CoverBuilder` directly - each thread executes Espresso independently:
144//!
145//! ```
146//! use espresso_logic::{Cover, CoverBuilder};
147//! use std::thread;
148//!
149//! # fn main() -> std::io::Result<()> {
150//! // Spawn threads - no synchronization needed!
151//! let handles: Vec<_> = (0..4).map(|_| {
152//! thread::spawn(move || {
153//! let mut cover = CoverBuilder::<2, 1>::new();
154//! cover.add_cube(&[Some(false), Some(true)], &[Some(true)]);
155//! cover.add_cube(&[Some(true), Some(false)], &[Some(true)]);
156//!
157//! // Thread-safe - each thread executes with independent global state
158//! cover.minimize()?;
159//! Ok(cover.num_cubes())
160//! })
161//! }).collect();
162//!
163//! for handle in handles {
164//! let result: std::io::Result<usize> = handle.join().unwrap();
165//! println!("Result: {} cubes", result?);
166//! }
167//! # Ok(())
168//! # }
169//! ```
170//!
171//! **How it works:**
172//! - **Thread-local storage**: All C global variables use `_Thread_local`
173//! - **Independent state**: Each thread has its own copy of all globals
174//! - **Direct calls**: No process spawning or IPC overhead
175//! - **Native safety**: Uses standard C11 thread safety features
176
177// Public modules
178pub mod expression;
179pub mod sys;
180
181// Private modules
182mod cover;
183mod pla;
184
185// Internal unsafe bindings (not exposed)
186#[path = "unsafe.rs"]
187mod r#unsafe;
188
189// Re-export high-level public API
190pub use cover::{Cover, CoverBuilder, CoverTypeMarker, FDRType, FDType, FRType, FType, PLAType};
191pub use expression::{BoolExpr, ExprCover};
192pub use pla::PLACover;
193
194/// Configuration for the Espresso algorithm
195#[derive(Debug, Clone)]
196pub struct EspressoConfig {
197 /// Enable debugging output
198 pub debug: bool,
199 /// Verbose debugging
200 pub verbose_debug: bool,
201 /// Print trace information
202 pub trace: bool,
203 /// Print summary information
204 pub summary: bool,
205 /// Remove essential primes
206 pub remove_essential: bool,
207 /// Force irredundant
208 pub force_irredundant: bool,
209 /// Unwrap onset
210 pub unwrap_onset: bool,
211 /// Single expand mode (fast)
212 pub single_expand: bool,
213 /// Use super gasp
214 pub use_super_gasp: bool,
215 /// Use random order
216 pub use_random_order: bool,
217}
218
219impl Default for EspressoConfig {
220 fn default() -> Self {
221 // Match C defaults from main.c lines 51-72
222 EspressoConfig {
223 debug: false,
224 verbose_debug: false,
225 trace: false,
226 summary: false,
227 remove_essential: true,
228 force_irredundant: true,
229 unwrap_onset: true,
230 single_expand: false,
231 use_super_gasp: false,
232 use_random_order: false,
233 }
234 }
235}
236
237impl EspressoConfig {
238 /// Create a new configuration with defaults
239 pub fn new() -> Self {
240 Self::default()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_cover_creation() {
250 let cover = CoverBuilder::<2, 1>::new();
251 // Just verify the cover was created successfully
252 assert_eq!(cover.num_cubes(), 0);
253 }
254
255 #[test]
256 fn test_cover_with_cubes() {
257 let mut cover = CoverBuilder::<3, 1>::new();
258 cover.add_cube(&[Some(true), Some(false), None], &[Some(true)]);
259 assert_eq!(cover.num_cubes(), 1);
260 }
261}