espresso_logic/
cover.rs

1//! Cover types and traits for Boolean function minimization
2//!
3//! This module provides the high-level API for working with covers (sum-of-products representations
4//! of Boolean functions). It includes compile-time checked builders and dynamic covers loaded from files.
5
6use std::fmt;
7use std::io;
8use std::path::Path;
9use std::sync::Arc;
10
11use crate::pla::PLASerializable;
12use crate::EspressoConfig;
13
14/// Type alias for complex cube iterator return type
15pub type CubeIterator<'a> = Box<dyn Iterator<Item = (Vec<Option<bool>>, Vec<Option<bool>>)> + 'a>;
16
17/// Represents the type of PLA output format (also used as cover type)
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PLAType {
20    /// On-set only (F)
21    F = 1,
22    /// On-set and don't-care set (FD)
23    FD = 3,
24    /// On-set and off-set (FR)
25    FR = 5,
26    /// On-set, don't-care set, and off-set (FDR)
27    FDR = 7,
28}
29
30impl PLAType {
31    /// Check if this type includes F (ON-set)
32    pub fn has_f(&self) -> bool {
33        matches!(self, PLAType::F | PLAType::FD | PLAType::FR | PLAType::FDR)
34    }
35
36    /// Check if this type includes D (don't-care set)
37    pub fn has_d(&self) -> bool {
38        matches!(self, PLAType::FD | PLAType::FDR)
39    }
40
41    /// Check if this type includes R (OFF-set)
42    pub fn has_r(&self) -> bool {
43        matches!(self, PLAType::FR | PLAType::FDR)
44    }
45}
46
47/// Type of a cube (ON-set, DC-set, or OFF-set)
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub(crate) enum CubeType {
50    F, // ON-set cube
51    D, // Don't-care set cube
52    R, // OFF-set cube
53}
54
55/// A cube in a PLA cover
56#[derive(Clone, Debug)]
57pub struct Cube {
58    pub(crate) inputs: Arc<[Option<bool>]>,
59    pub(crate) outputs: Arc<[bool]>, // Simplified: true = bit set, false = bit not set
60    pub(crate) cube_type: CubeType,
61}
62
63impl Cube {
64    pub(crate) fn new(inputs: Vec<Option<bool>>, outputs: Vec<bool>, cube_type: CubeType) -> Self {
65        Cube {
66            inputs: inputs.into(),
67            outputs: outputs.into(),
68            cube_type,
69        }
70    }
71}
72
73/// Internal trait for types that can be minimized
74/// Contains implementation details needed by the minimization algorithm
75pub(crate) trait Minimizable: Send + Sync {
76    /// Get the number of inputs (required for minimization)
77    fn num_inputs(&self) -> usize;
78
79    /// Get the number of outputs (required for minimization)
80    fn num_outputs(&self) -> usize;
81
82    /// Get the cover type (required for minimization)
83    fn cover_type(&self) -> PLAType;
84
85    /// Iterate over typed cubes (internal use only)
86    fn internal_cubes_iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Cube> + 'a>;
87
88    /// Set cubes after minimization (internal use)
89    fn set_cubes(&mut self, cubes: Vec<Cube>);
90}
91
92/// Public trait for all cover types (static and dynamic dimensions)
93pub trait Cover: Send + Sync {
94    /// Get the number of inputs
95    fn num_inputs(&self) -> usize;
96
97    /// Get the number of outputs  
98    fn num_outputs(&self) -> usize;
99
100    /// Get the number of cubes (for F/FD types, only counts F cubes; for FR/FDR, counts all)
101    fn num_cubes(&self) -> usize;
102
103    /// Get the cover type (F, FD, FR, or FDR)
104    fn cover_type(&self) -> PLAType;
105
106    /// Iterate over cubes (inputs, outputs)
107    /// Returns cubes in same format as add_cube takes (owned vecs for easy use)
108    fn cubes_iter<'a>(&'a self) -> CubeIterator<'a>;
109
110    /// Minimize this cover in-place using default configuration
111    fn minimize(&mut self) -> io::Result<()>;
112
113    /// Minimize this cover in-place with custom configuration
114    fn minimize_with_config(&mut self, config: &EspressoConfig) -> io::Result<()>;
115
116    /// Write this cover to PLA format string
117    fn to_pla_string(&self, pla_type: PLAType) -> io::Result<String>;
118
119    /// Write this cover to a PLA file
120    fn to_pla_file<P: AsRef<Path>>(&self, path: P, pla_type: PLAType) -> io::Result<()>;
121}
122
123/// Blanket implementation: Cover for all Minimizable types
124impl<T: Minimizable + PLASerializable> Cover for T {
125    fn num_inputs(&self) -> usize {
126        Minimizable::num_inputs(self)
127    }
128
129    fn num_outputs(&self) -> usize {
130        Minimizable::num_outputs(self)
131    }
132
133    fn num_cubes(&self) -> usize {
134        let cover_type = self.cover_type();
135        if cover_type.has_r() {
136            self.internal_cubes_iter().count()
137        } else {
138            // F/FD: only count F cubes
139            self.internal_cubes_iter()
140                .filter(|cube| cube.cube_type == CubeType::F)
141                .count()
142        }
143    }
144
145    fn cover_type(&self) -> PLAType {
146        Minimizable::cover_type(self)
147    }
148
149    fn cubes_iter<'a>(&'a self) -> CubeIterator<'a> {
150        // Convert internal Cube structs to public API format
151        // Only return F cubes for F-type covers, all cubes for FD/FR/FDR
152        let cover_type = self.cover_type();
153        Box::new(
154            self.internal_cubes_iter()
155                .filter(move |cube| {
156                    // For F-type, only return F cubes; for FD/FR/FDR, return all
157                    cover_type != PLAType::F || cube.cube_type == CubeType::F
158                })
159                .map(|cube| {
160                    // Convert bool outputs back to Option<bool> for public API
161                    let inputs = cube.inputs.to_vec();
162                    let outputs: Vec<Option<bool>> =
163                        cube.outputs.iter().map(|&b| Some(b)).collect();
164                    (inputs, outputs)
165                }),
166        )
167    }
168
169    fn minimize(&mut self) -> io::Result<()> {
170        let config = EspressoConfig::default();
171        self.minimize_with_config(&config)
172    }
173
174    fn minimize_with_config(&mut self, config: &EspressoConfig) -> io::Result<()> {
175        use crate::r#unsafe::{UnsafeCover, UnsafeEspresso};
176
177        // Split cubes into F, D, R sets based on cube type
178        let mut f_cubes = Vec::new();
179        let mut d_cubes = Vec::new();
180        let mut r_cubes = Vec::new();
181
182        for cube in self.internal_cubes_iter() {
183            let input_vec: Vec<u8> = cube
184                .inputs
185                .iter()
186                .map(|&opt| match opt {
187                    Some(false) => 0,
188                    Some(true) => 1,
189                    None => 2,
190                })
191                .collect();
192
193            // Convert outputs: true → 1, false → 0
194            let output_vec: Vec<u8> = cube
195                .outputs
196                .iter()
197                .map(|&b| if b { 1 } else { 0 })
198                .collect();
199
200            // Send to appropriate set based on cube type
201            match cube.cube_type {
202                CubeType::F => f_cubes.push((input_vec, output_vec)),
203                CubeType::D => d_cubes.push((input_vec, output_vec)),
204                CubeType::R => r_cubes.push((input_vec, output_vec)),
205            }
206        }
207
208        // Direct C calls - thread-safe via thread-local storage
209        let mut esp =
210            UnsafeEspresso::new_with_config(self.num_inputs(), self.num_outputs(), config);
211
212        // Build covers from cube data
213        let f_cover = UnsafeCover::build_from_cubes(f_cubes, self.num_inputs(), self.num_outputs());
214        let d_cover = if !d_cubes.is_empty() {
215            Some(UnsafeCover::build_from_cubes(
216                d_cubes,
217                self.num_inputs(),
218                self.num_outputs(),
219            ))
220        } else {
221            None
222        };
223        let r_cover = if !r_cubes.is_empty() {
224            Some(UnsafeCover::build_from_cubes(
225                r_cubes,
226                self.num_inputs(),
227                self.num_outputs(),
228            ))
229        } else {
230            None
231        };
232
233        // Minimize
234        let (f_result, d_result, r_result) = esp.minimize(f_cover, d_cover, r_cover);
235
236        // Direct conversion to typed Cubes - no serialization needed!
237        let mut all_cubes = Vec::new();
238        all_cubes.extend(f_result.to_cubes(self.num_inputs(), self.num_outputs(), CubeType::F));
239        all_cubes.extend(d_result.to_cubes(self.num_inputs(), self.num_outputs(), CubeType::D));
240        all_cubes.extend(r_result.to_cubes(self.num_inputs(), self.num_outputs(), CubeType::R));
241
242        // Update cubes with type information preserved
243        self.set_cubes(all_cubes);
244        Ok(())
245    }
246
247    fn to_pla_string(&self, pla_type: PLAType) -> io::Result<String> {
248        PLASerializable::to_pla_string(self, pla_type)
249    }
250
251    fn to_pla_file<P: AsRef<Path>>(&self, path: P, pla_type: PLAType) -> io::Result<()> {
252        PLASerializable::to_pla_file(self, path, pla_type)
253    }
254}
255
256/// Marker trait for cover type specification
257pub trait CoverTypeMarker: Send + Sync + Clone {
258    const PLA_TYPE: PLAType;
259}
260
261/// Marker type for F (ON-set only) covers
262#[derive(Clone, Copy, Debug)]
263pub struct FType;
264impl CoverTypeMarker for FType {
265    const PLA_TYPE: PLAType = PLAType::F;
266}
267
268/// Marker type for FD (ON-set + Don't-care) covers (default)
269#[derive(Clone, Copy, Debug)]
270pub struct FDType;
271impl CoverTypeMarker for FDType {
272    const PLA_TYPE: PLAType = PLAType::FD;
273}
274
275/// Marker type for FR (ON-set + OFF-set) covers
276#[derive(Clone, Copy, Debug)]
277pub struct FRType;
278impl CoverTypeMarker for FRType {
279    const PLA_TYPE: PLAType = PLAType::FR;
280}
281
282/// Marker type for FDR (ON-set + Don't-care + OFF-set) covers
283#[derive(Clone, Copy, Debug)]
284pub struct FDRType;
285impl CoverTypeMarker for FDRType {
286    const PLA_TYPE: PLAType = PLAType::FDR;
287}
288
289/// A cover builder with compile-time dimension checking
290///
291/// Uses const generics for ergonomic hand-construction of covers.
292/// For loading from PLA files with dynamic dimensions, use `PLACover::from_pla_*`.
293///
294/// The `T` type parameter specifies the cover type and defaults to `FDType` (ON-set + Don't-care).
295///
296/// # Examples
297///
298/// ```
299/// use espresso_logic::{CoverBuilder, Cover, FType, FDType};
300///
301/// # fn main() -> std::io::Result<()> {
302/// // Create a cover for a 2-input, 1-output function (FD type by default)
303/// let mut cover = CoverBuilder::<2, 1>::new();
304///
305/// // Build the function (XOR)
306/// cover.add_cube(&[Some(false), Some(true)], &[Some(true)]);  // 01 -> 1
307/// cover.add_cube(&[Some(true), Some(false)], &[Some(true)]);  // 10 -> 1
308///
309/// // Or create an F-type cover explicitly
310/// let mut f_cover = CoverBuilder::<2, 1, FType>::new();
311/// f_cover.add_cube(&[Some(false), Some(true)], &[Some(true)]);
312///
313/// // Minimize it
314/// cover.minimize()?;
315///
316/// // Read the result
317/// println!("Minimized to {} cubes", cover.num_cubes());
318/// # Ok(())
319/// # }
320/// ```
321#[derive(Clone)]
322pub struct CoverBuilder<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker = FDType> {
323    /// Cube data stored internally as typed cubes
324    cubes: Vec<Cube>,
325    _marker: std::marker::PhantomData<T>,
326}
327
328impl<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker>
329    CoverBuilder<INPUTS, OUTPUTS, T>
330{
331    /// Create a new empty cover
332    pub fn new() -> Self {
333        CoverBuilder {
334            cubes: Vec::new(),
335            _marker: std::marker::PhantomData,
336        }
337    }
338
339    /// Add a cube to the cover
340    ///
341    /// Outputs can use PLA-style notation:
342    /// - `Some(true)` or `'1'` → bit set in F cube (ON-set)
343    /// - `Some(false)` or `'0'` → bit set in R cube (OFF-set, only if cover type includes R)
344    /// - `None` or `'-'` → bit set in D cube (Don't-care, only if cover type includes D)
345    ///
346    /// # Arguments
347    ///
348    /// * `inputs` - Input values: `Some(false)` = 0, `Some(true)` = 1, `None` = don't care
349    /// * `outputs` - Output values following PLA conventions based on cover type
350    pub fn add_cube(
351        &mut self,
352        inputs: &[Option<bool>; INPUTS],
353        outputs: &[Option<bool>; OUTPUTS],
354    ) -> &mut Self {
355        // Parse outputs following Espresso C convention (cvrin.c lines 176-199)
356        // Create separate F, D, R cubes from a single line based on output values
357        let mut f_outputs = Vec::with_capacity(OUTPUTS);
358        let mut d_outputs = Vec::with_capacity(OUTPUTS);
359        let mut r_outputs = Vec::with_capacity(OUTPUTS);
360        let mut has_f = false;
361        let mut has_d = false;
362        let mut has_r = false;
363
364        let pla_type = T::PLA_TYPE;
365        for &out in outputs.iter() {
366            match out {
367                Some(true) if pla_type.has_f() => {
368                    // '1' → bit set in F cube
369                    f_outputs.push(true);
370                    d_outputs.push(false);
371                    r_outputs.push(false);
372                    has_f = true;
373                }
374                Some(false) if pla_type.has_r() => {
375                    // '0' → bit set in R cube
376                    f_outputs.push(false);
377                    d_outputs.push(false);
378                    r_outputs.push(true);
379                    has_r = true;
380                }
381                None if pla_type.has_d() => {
382                    // None/'-' → bit set in D cube
383                    f_outputs.push(false);
384                    d_outputs.push(true);
385                    r_outputs.push(false);
386                    has_d = true;
387                }
388                _ => {
389                    // Type not supported or unset bit
390                    f_outputs.push(false);
391                    d_outputs.push(false);
392                    r_outputs.push(false);
393                }
394            }
395        }
396
397        // Add cubes only if they have meaningful outputs
398        let inputs_vec = inputs.to_vec();
399        if has_f {
400            self.cubes
401                .push(Cube::new(inputs_vec.clone(), f_outputs, CubeType::F));
402        }
403        if has_d {
404            self.cubes
405                .push(Cube::new(inputs_vec.clone(), d_outputs, CubeType::D));
406        }
407        if has_r {
408            self.cubes
409                .push(Cube::new(inputs_vec, r_outputs, CubeType::R));
410        }
411
412        self
413    }
414}
415
416// Implement Minimizable for CoverBuilder (Cover trait is auto-implemented via blanket impl)
417impl<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker> Minimizable
418    for CoverBuilder<INPUTS, OUTPUTS, T>
419{
420    fn num_inputs(&self) -> usize {
421        INPUTS
422    }
423
424    fn num_outputs(&self) -> usize {
425        OUTPUTS
426    }
427
428    fn cover_type(&self) -> PLAType {
429        T::PLA_TYPE
430    }
431
432    fn internal_cubes_iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Cube> + 'a> {
433        Box::new(self.cubes.iter())
434    }
435
436    fn set_cubes(&mut self, cubes: Vec<Cube>) {
437        // Filter cubes based on the cover type
438        let pla_type = T::PLA_TYPE;
439        self.cubes = cubes
440            .into_iter()
441            .filter(|cube| match cube.cube_type {
442                CubeType::F => pla_type.has_f(),
443                CubeType::D => pla_type.has_d(),
444                CubeType::R => pla_type.has_r(),
445            })
446            .collect();
447    }
448}
449
450// Implement PLASerializable for CoverBuilder
451impl<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker> PLASerializable
452    for CoverBuilder<INPUTS, OUTPUTS, T>
453{
454}
455
456impl<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker> Default
457    for CoverBuilder<INPUTS, OUTPUTS, T>
458{
459    fn default() -> Self {
460        Self::new()
461    }
462}
463
464impl<const INPUTS: usize, const OUTPUTS: usize, T: CoverTypeMarker> fmt::Debug
465    for CoverBuilder<INPUTS, OUTPUTS, T>
466{
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        f.debug_struct("CoverBuilder")
469            .field("inputs", &INPUTS)
470            .field("outputs", &OUTPUTS)
471            .field("cover_type", &T::PLA_TYPE)
472            .field("num_cubes", &self.num_cubes())
473            .finish()
474    }
475}