quine_mccluskey/
lib.rs

1//! Boolean function minimizer based on [Quine-McCluskey algorithm](https://en.wikipedia.org/wiki/Quine%E2%80%93McCluskey_algorithm).
2//!
3//! # Usage
4//!
5//! ```rust
6//! use quine_mccluskey as qmc;
7//!
8//! let mut solutions = qmc::minimize(
9//!     &qmc::DEFAULT_VARIABLES[..3],
10//!     &[0, 5],        // minterms
11//!     &[1, 3, 4, 6],  // maxterms
12//!     qmc::SOP,
13//!     false,
14//!     None,
15//! )
16//! .unwrap();
17//!
18//! assert_eq!(
19//!     solutions.pop().unwrap().to_string(),
20//!     "(A ∧ C) ∨ (~A ∧ ~C)"
21//! );
22//! ```
23//!
24//! [`minimize`] is sufficient for all use cases. But also check [`minimize_minterms`] and
25//! [`minimize_maxterms`] to see if they are more suitable for your use case.
26//!
27//! # Feature flags
28//!
29//! * `serde` -- Derives the [`Serialize`] and [`Deserialize`] traits for structs and enums.
30
31#![deny(deprecated)]
32
33mod group;
34mod implicant;
35mod petrick;
36mod prime_implicant_chart;
37mod solution;
38mod timeout_signal;
39
40pub use solution::Solution;
41pub use solution::Variable;
42#[doc(hidden)]
43pub use Form::{POS, SOP};
44
45use std::collections::HashSet;
46use std::ops::Not;
47use std::sync::{mpsc, Arc};
48use std::thread;
49use std::time::Duration;
50
51#[cfg(feature = "serde")]
52use serde::{Deserialize, Serialize};
53
54use crate::group::Group;
55use crate::implicant::{Implicant, VariableSort};
56use crate::petrick::Petrick;
57use crate::prime_implicant_chart::PrimeImplicantChart;
58use crate::timeout_signal::{TTimeoutSignal, TimeoutSignalAtomicBool, TimeoutSignalNoOp};
59
60/// Minimizes the boolean function represented by the given `minterms` and `maxterms`.
61///
62/// Returns a list of equally minimal boolean expressions.
63///
64/// `minterms` represent the terms whose output is 1 and `maxterms` represent the terms whose output is 0.
65/// The rest of the terms are inferred to be don't care conditions.
66///
67/// `form` determines whether the minimized expression is of the form [`SOP`] (Sum of Products) or [`POS`] (Product of Sums).
68///
69/// If `find_all_solutions` is `true`, prime implicant chart simplification based on row/column dominance step will be skipped
70/// and all solutions will be returned. This on average makes the algorithm less efficient and more likely to get stuck.
71///
72/// If `timeout` is specified, the function will return [`Error::Timeout`] if the solution is not found within the given time.
73///
74/// # Example
75///
76/// Let's minimize the boolean function expressed by the following truth table:
77///
78/// | A | B | C | Output |
79/// |:-:|:-:|:-:|:------:|
80/// | 0 | 0 | 0 | 1      |
81/// | 0 | 0 | 1 | 0      |
82/// | 0 | 1 | 0 | X      |
83/// | 0 | 1 | 1 | 0      |
84/// | 1 | 0 | 0 | 0      |
85/// | 1 | 0 | 1 | 1      |
86/// | 1 | 1 | 0 | 0      |
87/// | 1 | 1 | 1 | X      |
88///
89/// In [`SOP`] form:
90///
91/// ```rust
92/// use quine_mccluskey as qmc;
93///
94/// let mut solutions = qmc::minimize(
95///     &qmc::DEFAULT_VARIABLES[..3],
96///     &[0, 5],
97///     &[1, 3, 4, 6],
98///     qmc::SOP,
99///     false,
100///     None,
101/// )
102/// .unwrap();
103///
104/// assert_eq!(
105///     solutions.pop().unwrap().to_string(),
106///     "(A ∧ C) ∨ (~A ∧ ~C)"
107/// );
108/// ```
109///
110/// And in [`POS`] form:
111///
112/// ```rust
113/// use quine_mccluskey as qmc;
114///
115/// let mut solutions = qmc::minimize(
116///     &qmc::DEFAULT_VARIABLES[..3],
117///     &[0, 5],
118///     &[1, 3, 4, 6],
119///     qmc::POS,
120///     false,
121///     None,
122/// )
123/// .unwrap();
124///
125/// assert_eq!(
126///     solutions.pop().unwrap().to_string(),
127///     "(A ∨ ~C) ∧ (~A ∨ C)"
128/// );
129/// ```
130pub fn minimize<T: AsRef<str>>(
131    variables: &[T],
132    minterms: &[u32],
133    maxterms: &[u32],
134    form: Form,
135    find_all_solutions: bool,
136    timeout: Option<Duration>,
137) -> Result<Vec<Solution>, Error> {
138    let variables = own_variables(variables);
139
140    let variable_count = variables.len();
141    let variable_count =
142        u32::try_from(variable_count).map_err(|_| Error::InvalidVariableCount(variable_count))?;
143
144    let minterms = minterms.iter().copied().collect();
145    let maxterms = maxterms.iter().copied().collect();
146
147    validate_input(&variables, &minterms, &maxterms)?;
148
149    let dont_cares = get_dont_cares(variable_count, &minterms, &maxterms);
150    let terms = if form == SOP { minterms } else { maxterms };
151
152    let internal_solutions = minimize_internal_with_timeout(
153        variable_count,
154        terms,
155        dont_cares,
156        form,
157        find_all_solutions,
158        timeout,
159    )?;
160
161    Ok(internal_solutions
162        .iter()
163        .map(|solution| Solution::new(solution, &variables, form))
164        .collect())
165}
166
167/// Minimizes the boolean function represented by the given `minterms` and `dont_cares`.
168///
169/// The only other difference to [`minimize`] is that it doesn't take an argument for form,
170/// instead always returns in [`SOP`] form.
171///
172/// # Example
173///
174/// Let's minimize the boolean function expressed by the following truth table:
175///
176/// | A | B | C | Output |
177/// |:-:|:-:|:-:|:------:|
178/// | 0 | 0 | 0 | 1      |
179/// | 0 | 0 | 1 | 0      |
180/// | 0 | 1 | 0 | X      |
181/// | 0 | 1 | 1 | 0      |
182/// | 1 | 0 | 0 | 0      |
183/// | 1 | 0 | 1 | 1      |
184/// | 1 | 1 | 0 | 0      |
185/// | 1 | 1 | 1 | X      |
186///
187/// ```rust
188/// use quine_mccluskey as qmc;
189///
190/// let mut solutions = qmc::minimize_minterms(
191///     &qmc::DEFAULT_VARIABLES[..3],
192///     &[0, 5],
193///     &[2, 7],
194///     false,
195///     None,
196/// )
197/// .unwrap();
198///
199/// assert_eq!(
200///     solutions.pop().unwrap().to_string(),
201///     "(A ∧ C) ∨ (~A ∧ ~C)"
202/// );
203/// ```
204pub fn minimize_minterms<T: AsRef<str>>(
205    variables: &[T],
206    minterms: &[u32],
207    dont_cares: &[u32],
208    find_all_solutions: bool,
209    timeout: Option<Duration>,
210) -> Result<Vec<Solution>, Error> {
211    let variables = own_variables(variables);
212
213    let variable_count = variables.len();
214    let variable_count =
215        u32::try_from(variable_count).map_err(|_| Error::InvalidVariableCount(variable_count))?;
216
217    let minterms = minterms.iter().copied().collect();
218    let dont_cares = dont_cares.iter().copied().collect();
219
220    validate_input(&variables, &minterms, &dont_cares)?;
221
222    let internal_solutions = minimize_internal_with_timeout(
223        variable_count,
224        minterms,
225        dont_cares,
226        SOP,
227        find_all_solutions,
228        timeout,
229    )?;
230
231    Ok(internal_solutions
232        .iter()
233        .map(|solution| Solution::new(solution, &variables, SOP))
234        .collect())
235}
236
237/// Minimizes the boolean function represented by the given `maxterms` and `dont_cares`.
238///
239/// The only other difference to [`minimize`] is that it doesn't take an argument for form,
240/// instead always returns in [`POS`] form.
241///
242/// # Example
243///
244/// Let's minimize the boolean function expressed by the following truth table:
245///
246/// | A | B | C | Output |
247/// |:-:|:-:|:-:|:------:|
248/// | 0 | 0 | 0 | 1      |
249/// | 0 | 0 | 1 | 0      |
250/// | 0 | 1 | 0 | X      |
251/// | 0 | 1 | 1 | 0      |
252/// | 1 | 0 | 0 | 0      |
253/// | 1 | 0 | 1 | 1      |
254/// | 1 | 1 | 0 | 0      |
255/// | 1 | 1 | 1 | X      |
256///
257/// ```rust
258/// use quine_mccluskey as qmc;
259///
260/// let mut solutions = qmc::minimize_maxterms(
261///     &qmc::DEFAULT_VARIABLES[..3],
262///     &[1, 3, 4, 6],
263///     &[2, 7],
264///     false,
265///     None,
266/// )
267/// .unwrap();
268///
269/// assert_eq!(
270///     solutions.pop().unwrap().to_string(),
271///     "(A ∨ ~C) ∧ (~A ∨ C)"
272/// );
273/// ```
274pub fn minimize_maxterms<T: AsRef<str>>(
275    variables: &[T],
276    maxterms: &[u32],
277    dont_cares: &[u32],
278    find_all_solutions: bool,
279    timeout: Option<Duration>,
280) -> Result<Vec<Solution>, Error> {
281    let variables = own_variables(variables);
282
283    let variable_count = variables.len();
284    let variable_count =
285        u32::try_from(variable_count).map_err(|_| Error::InvalidVariableCount(variable_count))?;
286
287    let maxterms = maxterms.iter().copied().collect();
288    let dont_cares = dont_cares.iter().copied().collect();
289
290    validate_input(&variables, &maxterms, &dont_cares)?;
291
292    let internal_solutions = minimize_internal_with_timeout(
293        variable_count,
294        maxterms,
295        dont_cares,
296        POS,
297        find_all_solutions,
298        timeout,
299    )?;
300
301    Ok(internal_solutions
302        .iter()
303        .map(|solution| Solution::new(solution, &variables, POS))
304        .collect())
305}
306
307/// The form of a boolean expression.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
310pub enum Form {
311    /// Sum of Products
312    SOP,
313    /// Product of Sums
314    POS,
315}
316
317/// All letters of the English alphabet in uppercase.
318pub static DEFAULT_VARIABLES: [&str; 26] = [
319    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
320    "T", "U", "V", "W", "X", "Y", "Z",
321];
322
323/// Error types for bad input and timeout.
324#[derive(Debug, thiserror::Error, Clone)]
325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
326pub enum Error {
327    /// The number of variables was less than 1 or greater than `DEFAULT_VARIABLES.len()`.
328    #[error("Invalid variable count: {0} (expected 1 <= variables.len() <= {max_len})", max_len = DEFAULT_VARIABLES.len())]
329    InvalidVariableCount(usize),
330    /// Variable was 0, 1, empty string or string with leading or trailing whitespace.
331    #[error("0, 1, empty string and strings with leading or trailing whitespace are not allowed as variables.")]
332    InvalidVariable,
333    /// There were duplicate variables.
334    #[error("Duplicate variables are not allowed: {0:?}")]
335    DuplicateVariables(HashSet<String>),
336    /// There were terms out of bounds for the given number of variables.
337    #[error("Terms out of bounds: {:?} (expected < {} for {} variables)", offending_terms, 1 << variable_count, variable_count)]
338    TermOutOfBounds {
339        offending_terms: HashSet<u32>,
340        variable_count: usize,
341    },
342    /// There were conflicting terms between the given term sets.
343    #[error("Conflicting terms between term sets: {0:?}")]
344    TermConflict(HashSet<u32>),
345    /// Could not find the solution in time.
346    #[error("Could not find the solution in time.")]
347    Timeout,
348}
349
350fn minimize_internal_with_timeout(
351    variable_count: u32,
352    terms: HashSet<u32>,
353    dont_cares: HashSet<u32>,
354    form: Form,
355    find_all_solutions: bool,
356    timeout: Option<Duration>,
357) -> Result<Vec<Vec<Implicant>>, Error> {
358    let timeout = match timeout {
359        Some(timeout) => timeout,
360        None => {
361            return minimize_internal(
362                variable_count,
363                &terms,
364                &dont_cares,
365                form,
366                find_all_solutions,
367                &TimeoutSignalNoOp,
368            )
369        }
370    };
371
372    let (sender, receiver) = mpsc::channel();
373
374    let outer_timeout_signal = Arc::new(TimeoutSignalAtomicBool::default());
375    let timeout_signal = outer_timeout_signal.clone();
376
377    let mut _worker_thread_builder = thread::Builder::new();
378    #[cfg(debug_assertions)]
379    {
380        _worker_thread_builder =
381            _worker_thread_builder.name("quine-mccluskey worker thread".into());
382    }
383
384    let worker_thread = _worker_thread_builder
385        .spawn(move || {
386            sender
387                .send(minimize_internal(
388                    variable_count,
389                    &terms,
390                    &dont_cares,
391                    form,
392                    find_all_solutions,
393                    timeout_signal.as_ref(),
394                ))
395                .unwrap();
396        })
397        .expect("failed to spawn quine-mccluskey worker thread");
398
399    let result = receiver.recv_timeout(timeout);
400
401    outer_timeout_signal.signal();
402    worker_thread
403        .join()
404        .expect("failed to join quine-mccluskey worker thread");
405
406    result.unwrap()
407}
408
409fn minimize_internal(
410    variable_count: u32,
411    terms: &HashSet<u32>,
412    dont_cares: &HashSet<u32>,
413    form: Form,
414    find_all_solutions: bool,
415    timeout_signal: &impl TTimeoutSignal,
416) -> Result<Vec<Vec<Implicant>>, Error> {
417    let prime_implicants =
418        find_prime_implicants(variable_count, terms, dont_cares, form, timeout_signal)?;
419    let mut prime_implicant_chart = PrimeImplicantChart::new(prime_implicants, dont_cares);
420    let essential_prime_implicants =
421        prime_implicant_chart.simplify(find_all_solutions, timeout_signal)?;
422    let petrick_solutions = Petrick::solve(&prime_implicant_chart, timeout_signal)?;
423
424    let mut solutions = petrick_solutions
425        .iter()
426        .map(|solution| [essential_prime_implicants.as_slice(), solution].concat())
427        .collect::<Vec<_>>();
428
429    for solution in &mut solutions {
430        if timeout_signal.is_signaled() {
431            return Err(Error::Timeout);
432        }
433
434        solution.variable_sort(form);
435        assert!(check_solution(terms, dont_cares, solution));
436    }
437
438    Ok(solutions)
439}
440
441fn find_prime_implicants(
442    variable_count: u32,
443    terms: &HashSet<u32>,
444    dont_cares: &HashSet<u32>,
445    form: Form,
446    timeout_signal: &impl TTimeoutSignal,
447) -> Result<Vec<Implicant>, Error> {
448    let terms = terms.union(dont_cares).copied().collect();
449    let mut groups = Group::group_terms(variable_count, &terms, form);
450    let mut prime_implicants = vec![];
451
452    while timeout_signal.is_not_signaled() {
453        let next_groups = (0..groups.len() - 1)
454            .map(|i| groups[i].combine(&groups[i + 1]))
455            .collect();
456
457        let mut abort = false;
458
459        let next_prime_implicants = groups
460            .iter()
461            .map_while(|group| {
462                abort = timeout_signal.is_signaled();
463                abort.not().then(|| group.get_prime_implicants(dont_cares))
464            })
465            .flatten();
466
467        prime_implicants.extend(next_prime_implicants);
468
469        if abort {
470            return Err(Error::Timeout);
471        }
472
473        if groups.iter().all(|group| !group.was_combined()) {
474            break;
475        }
476
477        groups = next_groups;
478    }
479
480    if timeout_signal.is_signaled() {
481        Err(Error::Timeout)
482    } else {
483        Ok(prime_implicants)
484    }
485}
486
487fn get_dont_cares(
488    variable_count: u32,
489    minterms: &HashSet<u32>,
490    maxterms: &HashSet<u32>,
491) -> HashSet<u32> {
492    let all_terms = (0..1 << variable_count).collect::<HashSet<_>>();
493    let cares = minterms.union(maxterms).copied().collect();
494
495    all_terms.difference(&cares).copied().collect()
496}
497
498fn check_solution(terms: &HashSet<u32>, dont_cares: &HashSet<u32>, solution: &[Implicant]) -> bool {
499    let covered_terms = solution.iter().flat_map(Implicant::get_terms).collect();
500    let terms_with_dont_cares = terms.union(dont_cares).copied().collect();
501
502    terms.is_subset(&covered_terms) && covered_terms.is_subset(&terms_with_dont_cares)
503}
504
505fn own_variables<T: AsRef<str>>(variables: &[T]) -> Vec<String> {
506    variables
507        .iter()
508        .map(|variable| variable.as_ref().to_owned())
509        .collect()
510}
511
512fn validate_input(
513    variables: &[String],
514    terms1: &HashSet<u32>,
515    terms2: &HashSet<u32>,
516) -> Result<(), Error> {
517    if variables.is_empty() || variables.len() > DEFAULT_VARIABLES.len() {
518        return Err(Error::InvalidVariableCount(variables.len()));
519    }
520
521    for variable in variables {
522        if variable == "0" || variable == "1" || variable.is_empty() || variable != variable.trim()
523        {
524            return Err(Error::InvalidVariable);
525        }
526    }
527
528    let mut duplicates = HashSet::new();
529
530    for i in 0..variables.len() {
531        for j in i + 1..variables.len() {
532            if variables[i] == variables[j] {
533                duplicates.insert(variables[i].clone());
534            }
535        }
536    }
537
538    if !duplicates.is_empty() {
539        return Err(Error::DuplicateVariables(duplicates));
540    }
541
542    let all_terms: HashSet<u32> = terms1.union(terms2).copied().collect();
543    let terms_out_of_bounds: HashSet<u32> = all_terms
544        .into_iter()
545        .filter(|&term| term >= 1 << variables.len())
546        .collect();
547
548    if !terms_out_of_bounds.is_empty() {
549        return Err(Error::TermOutOfBounds {
550            offending_terms: terms_out_of_bounds,
551            variable_count: variables.len(),
552        });
553    }
554
555    let conflicts: HashSet<u32> = terms1.intersection(terms2).copied().collect();
556
557    if !conflicts.is_empty() {
558        return Err(Error::TermConflict(conflicts));
559    }
560
561    Ok(())
562}
563
564#[cfg(test)]
565mod tests {
566    use itertools::Itertools;
567    use rand::Rng;
568
569    use super::*;
570
571    #[test]
572    fn test_minimize_exhaustive() {
573        for variable_count in 1..=3 {
574            let term_combinations = generate_terms_exhaustive(variable_count);
575
576            for terms in &term_combinations {
577                for form in [SOP, POS] {
578                    for find_all_solutions in [true, false] {
579                        minimize_and_print_solutions(
580                            variable_count,
581                            &terms.0,
582                            &terms.1,
583                            form,
584                            find_all_solutions,
585                        );
586                    }
587                }
588            }
589        }
590    }
591
592    #[test]
593    fn test_minimize_random() {
594        for variable_count in 4..=5 {
595            let term_combinations = generate_terms_random(variable_count, 1000);
596
597            for terms in &term_combinations {
598                for form in [SOP, POS] {
599                    for find_all_solutions in [true, false] {
600                        minimize_and_print_solutions(
601                            variable_count,
602                            &terms.0,
603                            &terms.1,
604                            form,
605                            find_all_solutions,
606                        );
607                    }
608                }
609            }
610        }
611    }
612
613    // #[test]
614    // fn test_minimize_specific() {
615    //     let variable_count = 1;
616    //     let form = SOP;
617    //     let find_all_solutions = true;
618    //     let minterms = [];
619    //     let maxterms = [];
620
621    //     minimize_and_print_solutions(
622    //         variable_count,
623    //         &minterms,
624    //         &maxterms,
625    //         form,
626    //         find_all_solutions,
627    //     );
628    // }
629
630    #[test]
631    fn test_find_prime_implicants() {
632        fn test(
633            variable_count: u32,
634            minterms: &[u32],
635            maxterms: &[u32],
636            form: Form,
637            expected: &[&str],
638        ) {
639            let minterms = minterms.iter().copied().collect();
640            let maxterms = maxterms.iter().copied().collect();
641
642            let dont_cares = get_dont_cares(variable_count, &minterms, &maxterms);
643            let terms = if form == SOP { minterms } else { maxterms };
644
645            let result = find_prime_implicants(
646                variable_count,
647                &terms,
648                &dont_cares,
649                form,
650                &TimeoutSignalNoOp,
651            )
652            .unwrap();
653
654            assert_eq!(
655                result.into_iter().collect::<HashSet<_>>(),
656                expected
657                    .iter()
658                    .map(|str| Implicant::from_str(str))
659                    .collect()
660            );
661        }
662
663        test(1, &[], &[0, 1], SOP, &[]);
664        test(1, &[0], &[1], SOP, &["0"]);
665        test(1, &[1], &[0], SOP, &["1"]);
666        test(1, &[0, 1], &[], SOP, &["-"]);
667        test(1, &[], &[], SOP, &[]);
668        test(1, &[], &[0], SOP, &[]);
669        test(1, &[], &[1], SOP, &[]);
670        test(1, &[0], &[], SOP, &["-"]);
671        test(1, &[1], &[], SOP, &["-"]);
672
673        test(1, &[0, 1], &[], POS, &[]);
674        test(1, &[1], &[0], POS, &["0"]);
675        test(1, &[0], &[1], POS, &["1"]);
676        test(1, &[], &[0, 1], POS, &["-"]);
677        test(1, &[], &[], POS, &[]);
678        test(1, &[0], &[], POS, &[]);
679        test(1, &[1], &[], POS, &[]);
680        test(1, &[], &[0], POS, &["-"]);
681        test(1, &[], &[1], POS, &["-"]);
682
683        test(2, &[0, 3], &[2], SOP, &["0-", "-1"]);
684
685        test(
686            3,
687            &[1, 2, 5],
688            &[3, 4, 7],
689            SOP,
690            &["00-", "0-0", "-01", "-10"],
691        );
692
693        test(
694            4,
695            &[2, 4, 5, 7, 9],
696            &[3, 6, 10, 12, 15],
697            SOP,
698            &["00-0", "01-1", "10-1", "0-0-", "-00-", "--01"],
699        );
700    }
701
702    fn minimize_and_print_solutions(
703        variable_count: u32,
704        minterms: &[u32],
705        maxterms: &[u32],
706        form: Form,
707        find_all_solutions: bool,
708    ) {
709        let dont_cares = Vec::from_iter(get_dont_cares(
710            variable_count,
711            &minterms.iter().copied().collect(),
712            &maxterms.iter().copied().collect(),
713        ));
714
715        println!(
716            "form: {:?}, find_all_solutions: {}, variable_count: {}, minterms: {:?}, maxterms: {:?}, dont_cares: {:?}",
717            form, find_all_solutions, variable_count, minterms, maxterms, dont_cares
718        );
719
720        let solutions = minimize(
721            &DEFAULT_VARIABLES[..variable_count as usize],
722            minterms,
723            maxterms,
724            form,
725            find_all_solutions,
726            None,
727        )
728        .unwrap();
729
730        println!(
731            "{:#?}",
732            solutions
733                .iter()
734                .map(ToString::to_string)
735                .collect::<Vec<_>>()
736        );
737    }
738
739    fn generate_terms_exhaustive(variable_count: u32) -> Vec<(Vec<u32>, Vec<u32>)> {
740        let mut generated_terms = vec![];
741        let all_terms = (0..1 << variable_count).collect::<HashSet<_>>();
742
743        for i in 0..=all_terms.len() {
744            let minterm_combinations = all_terms
745                .iter()
746                .copied()
747                .combinations(i)
748                .map(HashSet::from_iter);
749
750            for minterms in minterm_combinations {
751                let other_terms: HashSet<u32> = all_terms.difference(&minterms).copied().collect();
752
753                for j in 0..=other_terms.len() {
754                    let maxterm_combinations = other_terms
755                        .iter()
756                        .copied()
757                        .combinations(j)
758                        .map(HashSet::<u32>::from_iter);
759
760                    generated_terms.extend(maxterm_combinations.map(|maxterms| {
761                        (Vec::from_iter(minterms.clone()), Vec::from_iter(maxterms))
762                    }));
763                }
764            }
765        }
766
767        generated_terms
768    }
769
770    fn generate_terms_random(variable_count: u32, count: u32) -> Vec<(Vec<u32>, Vec<u32>)> {
771        let mut generated_terms = vec![];
772        let mut rng = rand::rng();
773
774        for _ in 0..count {
775            let mut all_terms = (0..1 << variable_count).collect::<Vec<_>>();
776            let mut minterms = vec![];
777            let mut maxterms = vec![];
778
779            for _ in 0..all_terms.len() {
780                let term = all_terms.swap_remove(rng.random_range(0..all_terms.len()));
781                let choice = rng.random_range(1..=3);
782
783                if choice == 1 {
784                    minterms.push(term);
785                } else if choice == 2 {
786                    maxterms.push(term);
787                }
788            }
789
790            generated_terms.push((minterms, maxterms));
791        }
792
793        generated_terms
794    }
795}