1#![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
60pub 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
167pub 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
237pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
310pub enum Form {
311 SOP,
313 POS,
315}
316
317pub 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#[derive(Debug, thiserror::Error, Clone)]
325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
326pub enum Error {
327 #[error("Invalid variable count: {0} (expected 1 <= variables.len() <= {max_len})", max_len = DEFAULT_VARIABLES.len())]
329 InvalidVariableCount(usize),
330 #[error("0, 1, empty string and strings with leading or trailing whitespace are not allowed as variables.")]
332 InvalidVariable,
333 #[error("Duplicate variables are not allowed: {0:?}")]
335 DuplicateVariables(HashSet<String>),
336 #[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 #[error("Conflicting terms between term sets: {0:?}")]
344 TermConflict(HashSet<u32>),
345 #[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]
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}