quinemccluskey_rs/
lib.rs

1use itertools::{Itertools, fold};
2use std::collections::{HashMap, HashSet};
3
4/// This function computes the hamming distance between two numbers with an optional mask
5/// # Arguments
6/// * `x` - First input
7/// * `y` - Second input
8/// * `mask` - Optional mask
9///
10/// # Output
11/// The hamming distance of `mask & x` and `mask & y`
12/// 
13/// # Examples
14/// ```
15/// use quinemccluskey_rs::hamming;
16/// 
17/// assert_eq!(hamming(0b01, 0b11, None), 1);
18/// assert_eq!(hamming(0b101, 0b001, Some(0b011)), 0);
19/// ```
20/// 
21pub fn hamming(x: u16, y: u16, mask: Option<u16>) -> u32 {
22    if let Some(mask) = mask {
23        return (mask & (x ^ y)).count_ones();
24    }
25    return (x ^ y).count_ones();
26}
27
28/// This function returns the index of the first bit difference between two numbers with an optional mask
29/// # Arguments
30/// * `x` - First input
31/// * `y` - Second input
32/// * `mask` - Optional mask
33///
34/// # Output
35/// The position of the first different bit between `mask & x` and `mask & y`.
36/// The position is counted from the least significant bit which itself has a position of `0`.
37/// 
38/// # Examples
39/// ```
40/// use quinemccluskey_rs::find_first_difference;
41/// 
42/// assert_eq!(find_first_difference(0b01, 0b11, None), 1);
43/// assert_eq!(find_first_difference(0b0101, 0b1001, Some(0b1011)), 3);
44/// 
45/// ```
46pub fn find_first_difference(x: u16, y: u16, mask: Option<u16>) -> usize {
47    debug_assert!(hamming(x, y, mask) >= 1, "Make sure that there is at least one different bit between mask & (x ^ y), otherwise this function will panic in optimized builds");
48
49    let to_check = match mask {
50        Some(m) => m & (x ^ y),
51        None => (x ^ y),
52    };
53
54    for i in 0..u16::BITS as usize {
55        if to_check & (1 << i) != 0 {
56            return i;
57        }
58    }
59    panic!("There is no bit set in {:0b}", to_check);
60}
61
62type MinTerm = u16;
63type GroupTable = HashMap<usize, Vec<(Vec<MinTerm>, MinTerm, MinTerm)>>;
64type VariableNumber = u16;
65
66
67/// This function simplifies a given list of minterms
68/// 
69/// # Arguments
70/// * `minterms` - The list of minterms that evaluate to true, for example if the binary function is
71/// 
72/// | A | B || C |
73/// |---|---||---|
74/// | 0 | 0 || 1 |
75/// | 0 | 1 || 0 |
76/// | 1 | 0 || 1 |
77/// | 1 | 1 || 0 |
78/// 
79/// The canonical form can be written as `A'B' + AB'`.
80/// The input for minterms would be `[0, 2]`, because the rows which have a 1 in the C column have these binary numbers
81/// 
82/// * `n_variables` - Optional argument for the number of variables, in the example above it would be `Some(2)`.
83/// If nothing is given here, the number of variables inferred is `log2(max(minterms) + 1)` which is not always correct
84/// 
85/// # Output
86/// A list of tuples
87/// Each of the tuples describe a minterm from the solution expression
88/// The first number in the tuple describes all set variables and
89/// the second number in the tuple describes all unset variables.
90/// 
91/// For example if the output is `[(0b11, 0b00), (0b00, 0b01)]`,
92/// the solution is `AB + B'` if `A` is the first variable and `B` is the second.
93/// 
94/// # Examples
95/// ```
96/// // use the boolean expression from above
97/// // | A | B || C |
98/// // |---|---||---|
99/// // | 0 | 0 || 1 |
100/// // | 0 | 1 || 0 |
101/// // | 1 | 0 || 1 |
102/// // | 1 | 1 || 0 |
103/// 
104/// use quinemccluskey_rs::simplify_bool_term;
105/// 
106/// let minterms = vec![0, 2];
107/// let n_variables = Some(2);
108/// 
109/// let simplified = simplify_bool_term(&minterms, n_variables);
110/// assert_eq!(simplified, [(0b00, 0b01)]);
111/// ```
112/// 
113pub fn simplify_bool_term(minterms: &Vec<MinTerm>, n_variables: Option<u16>) -> Vec<(u32, u32)> {
114    // apply the quine-mccluskey algorithm
115    let (prime_implicants, n_variable_cnd) = compute_prime_implicants(minterms).unwrap();
116    
117    let n_variables = match n_variables {
118        Some(n) => n,
119        None => n_variable_cnd,
120    };
121
122
123    // simplify using petrick's method
124    let simplification = compute_simplified_term(&prime_implicants, n_variables);
125
126    // now compute the bit flag vector
127    let mut result = Vec::new();
128    for idx in simplification {
129        let (_, assignment, mask) = &prime_implicants[idx];
130        let mut set_states: u32 = 0;
131        let mut unset_states: u32 = 0;
132
133        for var in 0..n_variables {
134            if (mask >> var) & 0b1 == 1 {
135                if (assignment >> var) &0b1 == 1 {
136                    set_states |= 0b1 << var
137                } else {
138                    unset_states |= 0b1 << var
139                }
140            }
141        }
142        result.push((set_states, unset_states))
143    }
144
145    return result;
146}
147
148fn compute_prime_implicants(minterms: &Vec<MinTerm>) -> Result<(Vec<(Vec<MinTerm>, MinTerm, MinTerm)>, VariableNumber), &'static str> {
149    // sort into groups
150    let mut group_table: GroupTable = HashMap::new();
151    let mut max_minterm = 0;
152
153    // Find the max index minterm and initialize the hashmap
154    for minterm in minterms {
155        if *minterm > max_minterm {
156            max_minterm = *minterm;
157        }
158        let number_of_ones = hamming(*minterm, 0, None);
159        let group = group_table.entry(number_of_ones as usize).or_insert(vec![]);
160
161        // push the minterm descriptor, the current assignment and the bit mask
162        group.push((vec![*minterm], *minterm, MinTerm::MAX));
163    }
164
165    let mut original_table: GroupTable = group_table;
166
167    // Reduce into groups if the hamming distance of consecutive groups is 1
168    // until no longer possible
169    loop {
170        let mut next_table: GroupTable = HashMap::new();
171        let mut group: usize = 0;
172
173        for (g1, g2) in original_table.keys().sorted().tuple_windows() {
174            let g1_entry = &original_table[g1];
175            let g2_entry = &original_table[g2];
176
177            let mut temp_hm: HashMap<Vec<MinTerm>, (usize, MinTerm, MinTerm)> = HashMap::new();
178
179            for (cnd1, minterm1, mask1) in g1_entry {
180                for (cnd2, minterm2, mask2) in g2_entry {
181                    // compute the combined mask
182                    if mask1 == mask2 {
183                        let mask = mask1 & mask2;
184                        if hamming(*minterm1, *minterm2, Some(mask)) == 1 {
185                            // merge the two minterms
186                            let different_bit_pos =
187                                find_first_difference(*minterm1, *minterm2, Some(mask));
188
189                            let new_mask = mask & !(1 << different_bit_pos);
190                            let new_assignment = *minterm1;
191                            let mut new_descriptors = vec![];
192
193                            new_descriptors.extend(cnd1);
194                            new_descriptors.extend(cnd2);
195                            new_descriptors.sort();
196
197                            // add new merged entry (removes duplicates)
198                            temp_hm.insert(new_descriptors, (group, new_assignment, new_mask));
199                        }
200                    }
201                }
202            }
203            if !temp_hm.is_empty() {
204                group += 1;
205            }
206
207            for (descriptor, (group, assignment, mask)) in temp_hm.drain() {
208                let entry = next_table.entry(group).or_insert(vec![]);
209                entry.push((descriptor, assignment, mask));
210            }
211        }
212        if group == 0 {
213            // reduction complete
214            break;
215        }
216        original_table = next_table;
217    }
218
219    let mut prime_implicants = Vec::new();
220    for (_, prime) in original_table.drain() {
221        prime_implicants.extend(prime);
222    }
223
224    return Ok((prime_implicants, (max_minterm as f32 + 1.).log2().floor() as u16));
225}
226
227fn needed_variables(n_variables: u16, assignment: u16, mask: u16) -> Vec<String> {
228    let mut variables: Vec<String> = vec!();
229
230    for cnd in 0..n_variables {
231        if mask >> cnd & 0b1 == 1 {
232            let offset = n_variables - cnd -1;
233            let current_char: char = char::from_u32(('A' as u16 + offset) as u32).expect("Not a char");
234            
235            if assignment >> cnd & 0b1 == 1 {
236                variables.push(format!("{}", current_char));
237            } else {
238                variables.push(format!("{}'", current_char));
239            }
240        }
241    }
242    return variables;
243}
244
245fn compute_simplified_term(prime_implicants: &Vec<(Vec<MinTerm>, u16, u16)>, n_variables: u16) -> Vec<usize> {
246    // 1. compute essential minterms
247    // compute a u8 matrix where each column corresponds to a minterm and each row to a prime implicant
248
249    let mut bool_matrix: HashMap<MinTerm, (u16, Vec<usize>)> = HashMap::new();
250    let mut prime_terms: HashSet<usize> = HashSet::new();
251
252    for (i, (descriptors, _, _)) in prime_implicants.iter().enumerate()  {
253        for desc in descriptors {
254            let (counter, idx) = bool_matrix.entry(*desc).or_insert((0, Vec::new()));
255            *counter += 1;
256            idx.push(i);
257            prime_terms.insert(i);
258        }
259    }
260
261    let mut essential_prime_terms = HashSet::new();
262    for (counter, term_idx) in bool_matrix.values() {
263        if *counter == 1 {
264            // The vector must contain exactly one element if counter == 1
265            
266            essential_prime_terms.insert(term_idx[0]);
267            prime_terms.remove(&term_idx[0]);
268        }
269    }
270
271    // If there is no nonessential prime term, we can terminate because
272    // all prime terms are essential
273    if prime_terms.len() == 0 {
274        return essential_prime_terms.into_iter().collect();
275    }
276
277    // now rebuild the matrix without essential primes
278    let mut bool_matrix: HashMap<MinTerm, Vec<usize>> = HashMap::new();
279    for i in prime_terms  {
280        let (descriptors, _, _) = &prime_implicants[i];
281        for desc in descriptors {
282            let idx = bool_matrix.entry(*desc).or_insert(Vec::new());
283            idx.push(i);
284        }
285    }
286
287    let mut distributives: Vec<Vec<Vec<usize>>> = bool_matrix
288    .drain()
289    .map(|(_, b)| 
290        b
291            .iter()
292            .map(|&a| vec!(a))
293            .collect()
294    )
295    .collect::<Vec<Vec<Vec<usize>>>>();
296
297
298    let first = distributives.swap_remove(0);
299    let mut distributed = fold(distributives, first, |a, b| distribute(a,b));
300
301
302    // Now select shortest minterms
303    let short_len = distributed[0].len();
304    let candidates = distributed.drain(..).take_while(|cnd| cnd.len() <= short_len).collect::<Vec<Vec<usize>>>();
305
306    if candidates.len() == 0 {
307        // only one entry it must be minimal
308        for term in &candidates[0] {
309            essential_prime_terms.insert(*term);
310        }
311        return essential_prime_terms.drain().collect();
312        // return candidates.swap_remove(0);
313    }
314
315    // else select the entry with fewest literals
316    let mut best_entry = 0;
317    let mut best_literal_count = usize::MAX;
318
319    for (i, cnd) in candidates.iter().enumerate() {
320        let count = cnd
321            .iter()
322            .map(|&idx| &prime_implicants[idx])
323            .map(|(_, a, b)| needed_variables(n_variables, *a, *b).len())
324            .sum::<usize>();
325        
326        if count < best_literal_count {
327            best_literal_count = count;
328            best_entry = i;
329        }
330    }
331
332    for term in &candidates[best_entry] {
333        essential_prime_terms.insert(*term);
334    }
335    return essential_prime_terms.drain().collect();
336}
337
338// computes the product (A + B) * (X + Y) = AX + AY + BX + BY
339// and simplifies it
340fn distribute<T>(mut left: Vec<Vec<T>>, right: Vec<Vec<T>>) -> Vec<Vec<T>>
341    where T:std::clone::Clone + std::cmp::PartialEq + std::cmp::Ord + std::fmt::Debug
342{
343    // compute product
344    let product: Vec<Vec<T>> = left
345        .drain(..)
346        .cartesian_product(right)
347        .map(|(mut a, b)| {a.extend(b); a})
348        .dedup() // X + X = X
349        .map(|mut a | {
350            a.sort();
351            a.dedup(); // XX = X
352            a
353        })
354        .sorted_by(|a, b| Ord::cmp(&a.len(), &b.len()))
355        .collect();
356    
357    // simplify X + XY = X
358    let mut del = Vec::new();
359    for (i, term1) in product.iter().enumerate() {
360        for (j, term2) in product[i+1..].iter().enumerate() {
361            // term1 is always shorter than term2 because
362            // the the entries are sorted_by(len)
363            
364            // is true if all entries of term1 are also in term2
365            // ONLY works if term1 and term2 are sorted and term1.len() <= term2.len()
366            debug_assert!(term1.len() <= term2.len(), "The length of term1 is greater than of term2");
367            debug_assert!(term1.iter().tuple_windows().map(|(a,b)| a < b).all(|a| a == true), "Term1 is not sorted");
368            debug_assert!(term2.iter().tuple_windows().map(|(a,b)| a < b).all(|a| a == true), "Term2 is not sorted");
369
370            // check with binary search if all elements of term1 are in term2
371            let term1_in_term2 = term1.iter().map(|a| binary_contains(&term2, &a)).all(|a| a.is_ok());
372            if term1_in_term2 {
373                // delete XY
374                del.push(i+j+1)
375            }
376        }
377    }
378    del.sort();
379    del.dedup();
380
381    let mut product = product;
382    for entry in del.into_iter().rev() {
383        product.swap_remove(entry);
384    }
385    return product;
386}
387
388fn binary_contains<T>(xs: &Vec<T>, x: &T) -> Result<usize, usize>
389where T: std::cmp::PartialOrd
390{
391    let mut size = xs.len();
392    if size == 0 {
393        return Err(0);
394    }
395
396    let mut base: usize = 0;
397    while size > 1 {
398        let half = size / 2;
399        let mid = base + half;
400        if xs[mid] <= *x {
401            base = mid;
402        }
403        size = size >> 1; // halve the interval
404    }
405
406    if xs[base] == *x {
407        return Ok(base);
408    }
409    Err(base + (xs[base] < *x) as usize)
410}
411
412
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417        #[test]
418    fn simplifies_if_all_one() {
419        let minterms = (0..8).collect::<Vec<u16>>();
420        assert_eq!(simplify_bool_term(&minterms, None), [(0,0)]);
421    }
422    #[test]
423    fn simplifies_if_all_zero() {
424        let minterms = vec!();
425        assert_eq!(simplify_bool_term(&minterms, None), []);
426    }
427    #[test]
428    fn simplify_1() {
429        //  0 0 | 1
430        //  0 1 | 1
431        //  1 0 | 0
432        //  1 1 | 0
433        //  A' is the minimal solution
434        let minterms = vec!(0, 1);
435        assert_eq!(simplify_bool_term(&minterms, Some(2)), [(0, 2)]);
436    }
437    #[test]
438    fn simplify_2() {
439        //  0 0 | 1
440        //  0 1 | 0
441        //  1 0 | 1
442        //  1 1 | 0
443        //  B' is the minimal solution
444        let minterms = vec!(0, 2);
445        assert_eq!(simplify_bool_term(&minterms, Some(2)), [(0, 1)]);
446    }
447    #[test]
448    fn simplify_3() {
449        //  0 0 | 0
450        //  0 1 | 1
451        //  1 0 | 1
452        //  1 1 | 0
453        //  A'B + AB' is the minimal solution
454        let minterms = vec!(1, 2);
455        let test_solution = simplify_bool_term(&minterms, Some(2));
456        let solution = [(1, 2), (2, 1)];
457        assert!(test_solution.iter().all(|x| solution.contains(x)) && solution.iter().all(|x| test_solution.contains(x)));
458    }
459    #[test]
460    fn test_order() {
461        let minterms = vec!(1, 2, 10, 30, 16, 24, 30);
462        let solution1 = simplify_bool_term(&minterms, None);
463        let solution2 = simplify_bool_term(&minterms.into_iter().rev().collect(), None);
464
465        assert!(solution1.iter().all(|x| solution2.contains(x)) && solution2.iter().all(|x| solution1.contains(x)));
466    }
467
468    #[test]
469    fn test_doubles() {
470        let minterms = vec!(1, 2, 3, 3, 2, 1, 2, 3);
471        let test_solution = simplify_bool_term(&minterms, Some(2));
472        let solution = [(1,0), (2,0)];
473
474        assert!(solution.iter().all(|x| test_solution.contains(x)) && test_solution.iter().all(|x| solution.contains(x)));
475    }
476}