diff --git a/src/main/java/de/neemann/digital/analyse/expression/ComplexityInclNotVisitor.java b/src/main/java/de/neemann/digital/analyse/expression/ComplexityInclNotVisitor.java new file mode 100644 index 000000000..389ec8d31 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/expression/ComplexityInclNotVisitor.java @@ -0,0 +1,18 @@ +package de.neemann.digital.analyse.expression; + +/** + * @author hneemann + */ +public class ComplexityInclNotVisitor implements ExpressionVisitor { + private int counter = 0; + + @Override + public boolean visit(Expression expression) { + counter++; + return true; + } + + public int getComplexity() { + return counter; + } +} diff --git a/src/main/java/de/neemann/digital/analyse/expression/ComplexityVisitor.java b/src/main/java/de/neemann/digital/analyse/expression/ComplexityVisitor.java new file mode 100644 index 000000000..27e7341a2 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/expression/ComplexityVisitor.java @@ -0,0 +1,19 @@ +package de.neemann.digital.analyse.expression; + +/** + * @author hneemann + */ +public class ComplexityVisitor implements ExpressionVisitor { + private int counter = 0; + + @Override + public boolean visit(Expression expression) { + if (!(expression instanceof Not)) + counter++; + return true; + } + + public int getComplexity() { + return counter; + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/BoolTable.java b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTable.java new file mode 100644 index 000000000..79e9d3946 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTable.java @@ -0,0 +1,24 @@ +package de.neemann.digital.analyse.quinemc; + +import de.neemann.digital.analyse.expression.ExpressionException; + +/** + * A simple bool table + * + * @author hneemann + */ +public interface BoolTable { + /** + * @return the table row count + */ + int size(); + + /** + * returns the value at the given row + * + * @param i the index + * @return the value + * @throws ExpressionException ExpressionException + */ + ThreeStateValue get(int i) throws ExpressionException; +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableBoolArray.java b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableBoolArray.java new file mode 100644 index 000000000..227ac83b1 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableBoolArray.java @@ -0,0 +1,33 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.ExpressionException; + +/** + * A simple boolean array + * + * @author hneemann + */ +public class BoolTableBoolArray implements BoolTable { + + private final boolean[] table; + + /** + * Creates a new instance + * + * @param table the bool values + */ + public BoolTableBoolArray(boolean[] table) { + this.table = table; + } + + @Override + public int size() { + return table.length; + } + + @Override + public ThreeStateValue get(int i) throws ExpressionException { + return ThreeStateValue.value(table[i]); + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableExpression.java b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableExpression.java new file mode 100644 index 000000000..a530ae1c2 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableExpression.java @@ -0,0 +1,30 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.ContextFiller; +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.ExpressionException; + +/** + * @author hneemann + */ +public class BoolTableExpression implements BoolTable { + private final Expression expression; + private final ContextFiller context; + + public BoolTableExpression(Expression expression, ContextFiller context) { + this.expression = expression; + this.context = context; + } + + @Override + public int size() { + return 1 << context.getVarCount(); + } + + @Override + public ThreeStateValue get(int i) throws ExpressionException { + context.setContextTo(i); + return ThreeStateValue.value(expression.calculate(context)); + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableIntArray.java b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableIntArray.java new file mode 100644 index 000000000..965ad381a --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/BoolTableIntArray.java @@ -0,0 +1,34 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.ExpressionException; + +/** + * A int array. + * Zero and one behave as expected, any other value represents "don't care" + * + * @author hneemann + */ +public class BoolTableIntArray implements BoolTable { + + private final int[] table; + + /** + * Creates a new instace + * + * @param table the int values + */ + public BoolTableIntArray(int[] table) { + this.table = table; + } + + @Override + public int size() { + return table.length; + } + + @Override + public ThreeStateValue get(int i) throws ExpressionException { + return ThreeStateValue.value(table[i]); + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/QuineMcClusky.java b/src/main/java/de/neemann/digital/analyse/quinemc/QuineMcClusky.java new file mode 100644 index 000000000..8709369eb --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/QuineMcClusky.java @@ -0,0 +1,317 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.*; +import de.neemann.digital.analyse.quinemc.primeselector.PrimeSelector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeSet; + +import static de.neemann.digital.analyse.expression.Operation.or; + +/** + * The algorithm from Quine and McClusky + * + * @author hneemann + */ +public class QuineMcClusky { + + private final ArrayList rows; + private final ArrayList variables; + private final ArrayList primes; + + /** + * Creates a new instance + * + * @param variables the variables to use + */ + public QuineMcClusky(ArrayList variables) { + this.variables = variables; + this.rows = new ArrayList<>(); + this.primes = new ArrayList<>(); + } + + private QuineMcClusky(ArrayList variables, ArrayList rows, ArrayList primes) { + this.variables = variables; + this.rows = rows; + this.primes = primes; + } + + /** + * Creates a new instance. + * The Bool table is build using the given expression + * + * @param expression the expression used to build the table + * @throws ExpressionException ExpressionException + */ + public QuineMcClusky(Expression expression) throws ExpressionException { + ContextFiller context = new ContextFiller(expression); + variables = context.getVariables(); + rows = new ArrayList<>(); + fillTableWith(new BoolTableExpression(expression, context)); + primes = new ArrayList<>(); + } + + /** + * Fills the instance with the given values + * + * @param values the values + * @return this for chained calls + * @throws ExpressionException ExpressionException + */ + public QuineMcClusky fillTableWith(BoolTable values) throws ExpressionException { + int n = 1 << variables.size(); + if (n != values.size()) + throw new ExpressionException("exact " + n + " values necessary, not " + values.size()); + for (int i = 0; i < n; i++) { + ThreeStateValue value = values.get(i); + if (!value.equals(ThreeStateValue.zero)) { + add(i, value.equals(ThreeStateValue.dontCare)); + } + } + Collections.sort(rows); + return this; + } + + + private void add(int i, boolean dontCare) { + rows.add(new TableRow(variables.size(), i, rows.size() + 1, dontCare)); + } + + /** + * Simplifies the given expression + * + * @param expression the expression to simplify + * @return the simplified expression + * @throws ExpressionException ExpressionException + */ + public static Expression simplify(Expression expression) throws ExpressionException { + int initialCplx = expression.traverse(new ComplexityInclNotVisitor()).getComplexity(); + + Expression newExp = new QuineMcClusky(expression) + .simplify() + .getExpression(); + + int newCplx = newExp.traverse(new ComplexityInclNotVisitor()).getComplexity(); + + if (newCplx < initialCplx) + return newExp; + else + return null; + } + + /** + * Simplifies the table the the default {@link PrimeSelector} + * + * @return the simplified QMC instance + */ + public QuineMcClusky simplify() { + return simplify(PrimeSelector.DEFAULT); + } + + /** + * Simplifies the table the the given {@link PrimeSelector} + * + * @param ps the prome selector + * @return the simplified QMC instance + */ + public QuineMcClusky simplify(PrimeSelector ps) { + QuineMcClusky t = this; + while (!t.isFinished()) + t = t.simplifyStep().removeDuplicates(); + return t.simplifyPrimes(ps); + } + + + QuineMcClusky simplifyStep() { + ArrayList newRows = new ArrayList<>(); + for (int i = 0; i < rows.size() - 1; i++) + for (int j = i + 1; j < rows.size(); j++) { + + TableRow r1 = rows.get(i); + TableRow r2 = rows.get(j); + + int index = checkCompatible(r1, r2); + if (index >= 0) { + // can optimize; + TableRow newRow = new TableRow(r1); + newRow.setToOptimized(index); + newRow.addSource(r1.getSource()); + newRow.addSource(r2.getSource()); + + r1.setUsed(); + r2.setUsed(); + + newRows.add(newRow); + } + } + + ArrayList np = new ArrayList(); + np.addAll(primes); + for (TableRow row : rows) + if (!row.isUsed() && row.getSource().size() > 0) + np.add(row); + + return new QuineMcClusky(variables, newRows, np); + } + + QuineMcClusky removeDuplicates() { + ArrayList newRows = new ArrayList(); + for (TableRow r : rows) { + int i = newRows.indexOf(r); + if (i < 0) { + newRows.add(r); + } else { + newRows.get(i).addSource(r.getSource()); + } + } + + rows.clear(); + rows.addAll(newRows); + + return this; + } + + /** + * @return true id simplification is complete + */ + public boolean isFinished() { + return rows.isEmpty(); + } + + private int checkCompatible(TableRow r1, TableRow r2) { + for (int i = 0; i < r1.size(); i++) { + if (r1.get(i) == TableItem.optimized || r2.get(i) == TableItem.optimized) { + if (!r1.get(i).equals(r2.get(i))) + return -1; + } + } + + int difIndex = -1; + for (int i = 0; i < r1.size(); i++) { + if (!r1.get(i).equals(r2.get(i))) { + if (difIndex >= 0) + return -1; + difIndex = i; + } + } + return difIndex; + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (TableRow r : rows) { + sb.append(r.toString()); + sb.append("\n"); + } + return sb.toString(); + } + + /** + * @return the final primes + */ + public ArrayList getPrimes() { + return primes; + } + + /** + * @return the simplified expression which represent this table + */ + public Expression getExpression() { + if (primes.isEmpty() && rows.isEmpty()) + return Constant.ZERO; + + Expression e = addAnd(null, primes, variables); + return addAnd(e, rows, variables); + } + + /** + * Creates the final expression + * + * @param e the expression to complete + * @param rows the rows to add + * @param variables the variables to use to build the expression + * @return the expression + */ + public static Expression addAnd(Expression e, ArrayList rows, ArrayList variables) { + for (TableRow r : rows) { + Expression n = r.getExpression(variables); + if (e == null) + e = n; + else + e = or(e, n); + } + return e; + } + + /** + * Simplify the primes + * + * @param primeSelector the prome selector to use + * @return this for call chaning + */ + public QuineMcClusky simplifyPrimes(PrimeSelector primeSelector) { + ArrayList primesAvail = new ArrayList(primes); + primes.clear(); + + TreeSet termIndices = new TreeSet<>(); + for (TableRow r : primesAvail) + termIndices.addAll(r.getSource()); + + // Nach primtermen suchen, welche einen index exclusiv enthalten + // Diese müssen in jedem Falle enthalten sein! + for (int pr : termIndices) { + + TableRow foundPrime = null; + for (TableRow tr : primesAvail) { + if (tr.getSource().contains(pr)) { + if (foundPrime == null) { + foundPrime = tr; + } else { + foundPrime = null; + break; + } + } + } + + if (foundPrime != null) { + if (!primes.contains(foundPrime)) + primes.add(foundPrime); + } + } + primesAvail.removeAll(primes); + + // Die, Indices die wir schon haben können raus; + for (TableRow pr : primes) { + termIndices.removeAll(pr.getSource()); + } + + if (!termIndices.isEmpty()) { + + //Die noch übrigen Terme durchsuchen ob sie schon komplett dabei sind; + Iterator it = primesAvail.iterator(); + while (it.hasNext()) { + TableRow tr = it.next(); + boolean needed = false; + for (int i : tr.getSource()) { + if (termIndices.contains(i)) { + needed = true; + break; + } + } + if (!needed) { + it.remove(); + } + } + + primeSelector.select(primes, primesAvail, termIndices); + } + + return this; + } + +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/TableItem.java b/src/main/java/de/neemann/digital/analyse/quinemc/TableItem.java new file mode 100644 index 000000000..170d87891 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/TableItem.java @@ -0,0 +1,12 @@ +package de.neemann.digital.analyse.quinemc; + +/** + * A QMC tables entry + * + * @author hneemann + */ +public enum TableItem { + zero, + one, + optimized +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/TableRow.java b/src/main/java/de/neemann/digital/analyse/quinemc/TableRow.java new file mode 100644 index 000000000..8535e1de8 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/TableRow.java @@ -0,0 +1,211 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.BitSetter; +import de.neemann.digital.analyse.expression.Constant; +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.Variable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.TreeSet; + +import static de.neemann.digital.analyse.expression.Not.not; +import static de.neemann.digital.analyse.expression.Operation.and; + +/** + * Represents a row in a QMC table + * + * @author hneemann + */ +public class TableRow implements Comparable { + + private final TableItem[] items; + private boolean used = false; + private final TreeSet source; + + /** + * Copies the given table row + * + * @param tr the row to copy + */ + public TableRow(TableRow tr) { + this(tr.size()); + for (int i = 0; i < size(); i++) + items[i] = tr.get(i); + } + + /** + * Creates a new tyble row + * + * @param cols number of columns + */ + public TableRow(int cols) { + items = new TableItem[cols]; + source = new TreeSet<>(); + } + + /** + * Creates a new row + * + * @param cols the number of columns + * @param bitValue the value representing the bits in the row + * @param index the index of the original source row + * @param dontCare dont care + */ + public TableRow(int cols, int bitValue, int index, boolean dontCare) { + this(cols); + if (!dontCare) + source.add(index); + new BitSetter(cols) { + @Override + public void setBit(int row, int bit, boolean value) { + if (value) + items[bit] = TableItem.one; + else + items[bit] = TableItem.zero; + } + }.fill(bitValue); + } + + /** + * The item at the given indes + * + * @param index the comumns index + * @return the value + */ + public TableItem get(int index) { + return items[index]; + } + + /** + * Sets the given idex to optimized + * + * @param index the columns index + */ + public void setToOptimized(int index) { + items[index] = TableItem.optimized; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int c = 0; c < items.length; c++) + switch (items[c]) { + case zero: + sb.append('0'); + break; + case one: + sb.append('1'); + break; + case optimized: + sb.append('-'); + break; + } + for (Integer i : source) + sb.append(",").append(i); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TableRow tableRow = (TableRow) o; + + // Probably incorrect - comparing Object[] arrays with Arrays.equals + return Arrays.equals(items, tableRow.items); + } + + @Override + public int hashCode() { + return Arrays.hashCode(items); + } + + /** + * Set the used flag + */ + public void setUsed() { + this.used = true; + } + + /** + * @return the used flag + */ + public boolean isUsed() { + return used; + } + + /** + * @return the number of one values in this row + */ + public int countOnes() { + int c = 0; + for (int i = 0; i < items.length; i++) + if (items[i] == TableItem.one) + c++; + return c; + } + + @Override + public int compareTo(TableRow tableRow) { + return Integer.compare(countOnes(), tableRow.countOnes()); + } + + /** + * @return the number of columns + */ + public int size() { + return items.length; + } + + /** + * @return the source line numbers + */ + public Collection getSource() { + return source; + } + + /** + * Adds some sources to this line + * + * @param s the sources to add + */ + public void addSource(Collection s) { + source.addAll(s); + } + + /** + * Returns an expression build with the given variables + * + * @param vars the variables to use + * @return the expression + */ + public Expression getExpression(ArrayList vars) { + Expression e = null; + for (int i = 0; i < size(); i++) { + Expression term = null; + switch (items[i]) { + case one: + term = vars.get(i); + break; + case zero: + term = not(vars.get(i)); + break; + } + if (term != null) { + if (e == null) + e = term; + else + e = and(e, term); + } + } + if (e == null) + return Constant.ONE; + else + return e; + } + +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/ThreeStateValue.java b/src/main/java/de/neemann/digital/analyse/quinemc/ThreeStateValue.java new file mode 100644 index 000000000..52b6675f8 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/ThreeStateValue.java @@ -0,0 +1,30 @@ +package de.neemann.digital.analyse.quinemc; + +/** + * @author hneemann + */ +public enum ThreeStateValue { + one, + zero, + dontCare; + + + public static ThreeStateValue value(boolean bool) { + if (bool) { + return one; + } else { + return zero; + } + } + + public static ThreeStateValue value(int value) { + switch (value) { + case 0: + return ThreeStateValue.zero; + case 1: + return ThreeStateValue.one; + default: + return ThreeStateValue.dontCare; + } + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/package-info.java b/src/main/java/de/neemann/digital/analyse/quinemc/package-info.java new file mode 100644 index 000000000..045043622 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/package-info.java @@ -0,0 +1,6 @@ +/** + * Implementation of the algorithm from Quine and McClusky + * + * @author hneemann + */ +package de.neemann.digital.analyse.quinemc; diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForce.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForce.java new file mode 100644 index 000000000..7737a628b --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForce.java @@ -0,0 +1,54 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.quinemc.TableRow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.TreeSet; + +/** + * @author hneemann + */ +public class BruteForce implements PrimeSelector { + @Override + public void select(ArrayList primes, ArrayList primesAvail, TreeSet termIndices) { + int comb = 1 << primesAvail.size(); + ArrayList list = new ArrayList<>(comb); + for (int i = 1; i < comb; i++) { + list.add(i); + } + Collections.sort(list, new Comparator() { + @Override + public int compare(Integer i1, Integer i2) { + return Integer.bitCount(i1) - Integer.bitCount(i2); + } + }); + + ArrayList l = new ArrayList<>(); + for (int mask : list) { + l.addAll(termIndices); + int m = mask; + for (TableRow aPrimesAvail : primesAvail) { + if ((m & 1) > 0) { + l.removeAll(aPrimesAvail.getSource()); + } + m >>= 1; + } + if (l.isEmpty()) { + m = mask; + for (TableRow aPrime : primesAvail) { + if ((m & 1) > 0) { + primes.add(aPrime); + } + m >>= 1; + } + return; + } else { + l.clear(); + } + } + throw new RuntimeException("BruteForce Error!"); + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAll.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAll.java new file mode 100644 index 000000000..f37b1f304 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAll.java @@ -0,0 +1,73 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.quinemc.TableRow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.TreeSet; + +/** + * @author hneemann + */ +public class BruteForceGetAll implements PrimeSelector, PrimeSelectorGetAll { + + private ArrayList> foundSolutions; + + @Override + public void select(ArrayList primes, ArrayList primesAvail, TreeSet termIndices) { + int comb = 1 << primesAvail.size(); + ArrayList list = new ArrayList<>(comb); + for (int i = 1; i < comb; i++) { + list.add(i); + } + Collections.sort(list, new Comparator() { + @Override + public int compare(Integer i1, Integer i2) { + return Integer.bitCount(i1) - Integer.bitCount(i2); + } + }); + + int primesUsed = 0; + + foundSolutions = new ArrayList<>(); + + ArrayList indicesOpen = new ArrayList<>(); + for (int mask : list) { + + if (primesUsed != 0 && Integer.bitCount(mask) > primesUsed) + break; + + indicesOpen.clear(); + indicesOpen.addAll(termIndices); + int m = mask; + for (TableRow aPrimesAvail : primesAvail) { + if ((m & 1) > 0) { + indicesOpen.removeAll(aPrimesAvail.getSource()); + } + m >>= 1; + } + if (indicesOpen.isEmpty()) { + primesUsed = Integer.bitCount(mask); + + ArrayList singleSolution = new ArrayList<>(primes); + m = mask; + for (TableRow aPrime : primesAvail) { + if ((m & 1) > 0) { + singleSolution.add(aPrime); + } + m >>= 1; + } + foundSolutions.add(singleSolution); + } + } + primes.clear(); + primes.addAll(foundSolutions.get(0)); + } + + @Override + public ArrayList> getAllSolutions() { + return foundSolutions; + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/LargestFirst.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/LargestFirst.java new file mode 100644 index 000000000..12de5a64f --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/LargestFirst.java @@ -0,0 +1,36 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.quinemc.TableRow; + +import java.util.ArrayList; +import java.util.TreeSet; + +/** + * Tries at first the primes containing the most indices + * + * @author hneemann + */ +public class LargestFirst implements PrimeSelector { + @Override + public void select(ArrayList primes, ArrayList primesAvail, TreeSet termIndices) { + while (!termIndices.isEmpty()) { + TableRow bestRow = null; + int maxCount = 0; + for (TableRow tr : primesAvail) { + int count = 0; + for (int i : tr.getSource()) { + if (termIndices.contains(i)) + count++; + } + if (count > maxCount) { + maxCount = count; + bestRow = tr; + } + } + primes.add(bestRow); + primesAvail.remove(bestRow); + termIndices.removeAll(bestRow.getSource()); + } + } +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelector.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelector.java new file mode 100644 index 000000000..632df185c --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelector.java @@ -0,0 +1,44 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.quinemc.TableRow; + +import java.util.ArrayList; +import java.util.TreeSet; + +/** + * Represents an algorithm which chooses the final primes + * + * @author hneemann + */ +public interface PrimeSelector { + + /** + * The default prime selector + */ + PrimeSelector DEFAULT = new PrimeSelector() { + + private final PrimeSelector bruteForce = new BruteForceGetAll(); + private final PrimeSelector largestFirst = new LargestFirst(); + + @Override + public void select(ArrayList primes, ArrayList primesAvail, TreeSet termIndices) { + int count = primesAvail.size(); + + if (count <= 12) { + bruteForce.select(primes, primesAvail, termIndices); + } else { + largestFirst.select(primes, primesAvail, termIndices); + } + } + }; + + /** + * Selects the primes to use + * + * @param primes the list to add the primes to + * @param primesAvail the available promes + * @param termIndices the indices + */ + void select(ArrayList primes, ArrayList primesAvail, TreeSet termIndices); +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelectorGetAll.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelectorGetAll.java new file mode 100644 index 000000000..36f25932f --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/PrimeSelectorGetAll.java @@ -0,0 +1,19 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.quinemc.TableRow; + +import java.util.ArrayList; + +/** + * Used to create all possible sollutions + * + * @author hneemann + */ +public interface PrimeSelectorGetAll { + + /** + * @return all possible solutions + */ + ArrayList> getAllSolutions(); +} diff --git a/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/package-info.java b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/package-info.java new file mode 100644 index 000000000..87ac9376f --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/quinemc/primeselector/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes used to select the necessary primes + * + * @author hneemann + */ +package de.neemann.digital.analyse.quinemc.primeselector; diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/FullVariantDontCareCreator.java b/src/test/java/de/neemann/digital/analyse/quinemc/FullVariantDontCareCreator.java new file mode 100644 index 000000000..b5d940022 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/FullVariantDontCareCreator.java @@ -0,0 +1,54 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.expression.format.FormatterException; + +/** + * @author hneemann + */ +public abstract class FullVariantDontCareCreator { + + private final int nmax; + private final int step; + + public FullVariantDontCareCreator() { + this(3, 1); + } + + public FullVariantDontCareCreator(int nmax) { + this(nmax, 1); + } + + public FullVariantDontCareCreator(int nmax, int step) { + this.nmax = nmax; + this.step = step; + } + + public void create() throws ExpressionException, FormatterException { + for (int n = 1; n <= nmax; n++) { + int tables = 1; + int c = 1 << n; + for (int i = 0; i < c; i++) tables *= 3; + + int count = 0; + int[] tab = new int[1 << n]; + for (int i = 0; i < tables; i += step) { + int value = i; + for (int j = 0; j < tab.length; j++) { + tab[j] = value % 3; + value /= 3; + } + handleTable(n, tab); + + if (count++ > 10000) { + System.out.println(i + "/" + tables); + count = 0; + } + + } + } + } + + public abstract void handleTable(int n, int[] tab) throws ExpressionException, FormatterException; +} diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyDontCareTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyDontCareTest.java new file mode 100644 index 000000000..22885b23f --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyDontCareTest.java @@ -0,0 +1,124 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.*; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatToTable; +import de.neemann.digital.analyse.expression.format.FormatterException; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; + +import static de.neemann.digital.analyse.expression.Variable.vars; +import static de.neemann.digital.analyse.expression.format.FormatToExpression.FORMATTER_UNICODE; + +/** + * @author hneemann + */ +public class QuineMcCluskyDontCareTest extends TestCase { + + public void testDontCare() throws Exception, FormatterException { + ArrayList v = vars("A", "B", "C"); + Expression e = new QuineMcClusky(v) + .fillTableWith(new BoolTableIntArray(new int[]{1, 1, 0, 0, 1, 2, 2, 0})) + .simplify() + .getExpression(); + + System.out.println(new FormatToTable().format("y", e)); + assertEquals("!B", FormatToExpression.FORMATTER_JAVA.format(e)); + } + + /** + * up to 3 variables we can calculate all tables possible! + * + * @throws ExpressionException + */ + public void testFull() throws ExpressionException, FormatterException { + new FullVariantDontCareCreator() { + @Override + public void handleTable(int n, int[] tab) throws ExpressionException { + performTestCalculation(n, tab); + } + }.create(); + } + + + /** + * for more the 3 variables we only test some random tables + * + * @throws ExpressionException + */ + public void testRegression() throws ExpressionException { + for (int n = 4; n < 8; n++) { + for (int i = 0; i < 200; i++) { + performTestCalculationRandom(n); + } + } + } + + static private void performTestCalculationRandom(int n) throws ExpressionException { + int[] tab = new int[1 << n]; + for (int i = 0; i < tab.length; i++) + tab[i] = (int) Math.round(Math.random() * 3); // half of the values are don't care + + performTestCalculation(n, tab); + } + + static private void performTestCalculation(int n, int[] tab) throws ExpressionException { + ArrayList v = vars(n); + Expression e = new QuineMcClusky(v) + .fillTableWith(new BoolTableIntArray(tab)) + .simplify() + .getExpression(); + + assertNotNull(e); + + ContextFiller context = new ContextFiller(v); + for (int i = 0; i < tab.length; i++) { + if (tab[i] <= 1) + assertEquals(tab[i] == 1, e.calculate(context.setContextTo(i))); + } + } + + public void testComplexity() throws Exception, FormatterException { + new FullVariantDontCareCreator() { + @Override + public void handleTable(int n, int[] tab) throws ExpressionException, FormatterException { + Expression e = createExpression(n, tab); + + int[] tabZero = Arrays.copyOf(tab, tab.length); + for (int i = 0; i < tabZero.length; i++) + if (tabZero[i] > 1) tabZero[i] = 0; + Expression eZero = createExpression(n, tabZero); + + int[] tabOne = Arrays.copyOf(tab, tab.length); + for (int i = 0; i < tabOne.length; i++) + if (tabOne[i] > 1) tabOne[i] = 1; + + Expression eOne = createExpression(n, tabOne); + + int c = e.traverse(new ComplexityVisitor()).getComplexity(); + int cOne = eOne.traverse(new ComplexityVisitor()).getComplexity(); + int cZero = eZero.traverse(new ComplexityVisitor()).getComplexity(); + + boolean ok = (c <= cOne) && (c <= cZero); + if (!ok) { + System.out.println("\nX: " + FORMATTER_UNICODE.format(e) + ", " + c); + System.out.println("0: " + FORMATTER_UNICODE.format(eZero) + ", " + cZero); + System.out.println("1: " + FORMATTER_UNICODE.format(eOne) + ", " + cOne); + + assertTrue(false); + } + } + }.create(); + } + + private Expression createExpression(int n, int[] tab) throws ExpressionException { + ArrayList v = vars(n); + return new QuineMcClusky(v) + .fillTableWith(new BoolTableIntArray(tab)) + .simplify() + .getExpression(); + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRegressionTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRegressionTest.java new file mode 100644 index 000000000..9d42389e0 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRegressionTest.java @@ -0,0 +1,87 @@ +package de.neemann.digital.analyse.quinemc; + +import de.neemann.digital.analyse.expression.ContextFiller; +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatterException; +import de.neemann.digital.analyse.quinemc.primeselector.PrimeSelector; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Collections; + +import static de.neemann.digital.analyse.expression.Operation.or; + +/** + * @author hneemann + */ +public class QuineMcCluskyRegressionTest extends TestCase { + + public void testRegression() throws Exception, FormatterException { + testRegression(8, 128); + testRegression(8, 16); + testRegression(4, 8); + testRegression(4, 4); + } + + public void testRegression2() throws Exception, FormatterException { + for (int i = 0; i < 100; i++) { + testRegression(5, 16); + testRegression(5, 8); + testRegression(5, 4); + } + } + + public void testRegression3() throws Exception, FormatterException { + Variable a = new Variable("A"); + Variable b = new Variable("B"); + Variable c = new Variable("C"); + Variable d = new Variable("D"); + ArrayList vars = new ArrayList<>(); + vars.add(a); + vars.add(b); + vars.add(c); + vars.add(d); + QuineMcClusky t = new QuineMcClusky(vars); + + Expression ex = or(a, c); + t.fillTableWith(new BoolTableExpression(ex, new ContextFiller(vars))); + + System.out.println("--"); + while (!t.isFinished()) { + System.out.println(FormatToExpression.FORMATTER_JAVA.format(t.getExpression())); + t = t.simplifyStep().removeDuplicates(); + } + t.simplifyPrimes(PrimeSelector.DEFAULT); + assertEquals("A || C", FormatToExpression.FORMATTER_JAVA.format(t.getExpression())); + System.out.println("--"); + } + + + public static void testRegression(int n, int j) throws Exception, FormatterException { + int size = 1 << n; + boolean[] table = new boolean[size]; + + ArrayList index = new ArrayList<>(); + for (int i = 0; i < size; i++) index.add(i); + Collections.shuffle(index); + + for (int i = 0; i < j; i++) + table[index.get(i)] = true; + + ArrayList var = Variable.vars(n); + + Expression expression = + new QuineMcClusky(var) + .fillTableWith(new BoolTableBoolArray(table)) + .simplify() + .getExpression(); + + ContextFiller cf = new ContextFiller(var); + + for (int i = 0; i < table.length; i++) + assertEquals(table[i], expression.calculate(cf.setContextTo(i))); + + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRowTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRowTest.java new file mode 100644 index 000000000..2888ba3c6 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyRowTest.java @@ -0,0 +1,34 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatterException; +import junit.framework.TestCase; + +import java.util.ArrayList; + +/** + * @author hneemann + */ +public class QuineMcCluskyRowTest extends TestCase { + + public void testSimple() throws FormatterException { + ArrayList vars = Variable.vars("A", "B", "C", "D"); + + TableRow tr = new TableRow(4, 15, 0, false); + assertEquals("A && B && C && D", FormatToExpression.FORMATTER_JAVA.format(tr.getExpression(vars))); + + tr = new TableRow(4, 5, 0, false); + assertEquals("!A && B && !C && D", FormatToExpression.FORMATTER_JAVA.format(tr.getExpression(vars))); + tr = new TableRow(4, 10, 0, false); + assertEquals("A && !B && C && !D", FormatToExpression.FORMATTER_JAVA.format(tr.getExpression(vars))); + tr = new TableRow(4, 10, 0, false); + tr.setToOptimized(2); + assertEquals("A && !B && !D", FormatToExpression.FORMATTER_JAVA.format(tr.getExpression(vars))); + tr = new TableRow(4, 10, 0, false); + tr.setToOptimized(0); + assertEquals("!B && C && !D", FormatToExpression.FORMATTER_JAVA.format(tr.getExpression(vars))); + + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyTest.java new file mode 100644 index 000000000..0eb7f5f24 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/QuineMcCluskyTest.java @@ -0,0 +1,161 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.Constant; +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatterException; +import de.neemann.digital.analyse.quinemc.primeselector.PrimeSelector; +import junit.framework.TestCase; + +import java.util.ArrayList; + +import static de.neemann.digital.analyse.expression.Not.not; +import static de.neemann.digital.analyse.expression.Operation.and; +import static de.neemann.digital.analyse.expression.Operation.or; + +/** + * @author hneemann + */ +public class QuineMcCluskyTest extends TestCase { + + + public void testGenerator() throws ExpressionException { + Variable a = new Variable("A");// Vorlesung + Variable b = new Variable("B"); + Variable c = new Variable("C"); + Variable d = new Variable("D"); + + Expression e = or(and(a, and(c, d)), or(and(not(c), not(d)), and(not(b), c))); + QuineMcClusky t = new QuineMcClusky(e); + + assertEquals("0000,1\n" + + "0010,2\n" + + "0100,4\n" + + "1000,5\n" + + "0011,3\n" + + "1010,6\n" + + "1100,8\n" + + "1011,7\n" + + "1111,9\n", t.toString()); + + } + + public void testSimplify() throws ExpressionException, FormatterException { + Variable a = new Variable("A");// Vorlesung + Variable b = new Variable("B"); + Variable c = new Variable("C"); + Variable d = new Variable("D"); + + Expression e = or(and(a, and(c, d)), or(and(not(c), not(d)), and(not(b), c))); + QuineMcClusky t = new QuineMcClusky(e).simplifyStep(); + assertFalse(t.isFinished()); + + assertEquals("00-0,1,2\n" + + "0-00,1,4\n" + + "-000,1,5\n" + + "001-,2,3\n" + + "-010,2,6\n" + + "-100,4,8\n" + + "10-0,5,6\n" + + "1-00,5,8\n" + + "-011,3,7\n" + + "101-,6,7\n" + + "1-11,7,9\n", t.toString()); + + t = t.simplifyStep(); + assertFalse(t.isFinished()); + + assertEquals("-0-0,1,2,5,6\n" + + "--00,1,4,5,8\n" + + "-0-0,1,2,5,6\n" + + "--00,1,4,5,8\n" + + "-01-,2,3,6,7\n" + + "-01-,2,3,6,7\n", t.toString()); + + ArrayList primes = t.getPrimes(); + assertEquals(1, primes.size()); + assertEquals("1-11,7,9", primes.get(0).toString()); + + t = t.removeDuplicates(); + assertFalse(t.isFinished()); + + assertEquals("-0-0,1,2,5,6\n" + + "--00,1,4,5,8\n" + + "-01-,2,3,6,7\n", t.toString()); + + t = t.simplifyStep(); + assertTrue(t.isFinished()); + + assertEquals("", t.toString()); + + primes = t.getPrimes(); + assertEquals(4, primes.size()); + assertEquals("1-11,7,9", primes.get(0).toString()); + assertEquals("-0-0,1,2,5,6", primes.get(1).toString()); + assertEquals("--00,1,4,5,8", primes.get(2).toString()); + assertEquals("-01-,2,3,6,7", primes.get(3).toString()); + + Expression exp = t.getExpression(); + assertEquals("(A && C && D) || (!B && !D) || (!B && C) || (!C && !D)", FormatToExpression.FORMATTER_JAVA.format(exp)); + + t.simplifyPrimes(PrimeSelector.DEFAULT); + + exp = t.getExpression(); + assertEquals("(A && C && D) || (!B && C) || (!C && !D)", FormatToExpression.FORMATTER_JAVA.format(exp)); + } + + public void testSimplify2() throws ExpressionException, FormatterException { + QuineMcClusky t = new QuineMcClusky(Variable.vars("A", "B", "C", "D")); + t.fillTableWith(new BoolTableIntArray(new int[]{1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1})); + t = t.simplify(); + + assertEquals("(!A && !C) || (B && D) || (B && !C)", FormatToExpression.FORMATTER_JAVA.format(t.getExpression())); + } + + public void testZero() throws Exception { + QuineMcClusky t = new QuineMcClusky(Variable.vars("A", "B", "C")); + t.fillTableWith(new BoolTableIntArray(new int[]{0, 0, 0, 0, 0, 0, 0, 0})); + t = t.simplify(); + Expression e = t.getExpression(); + assertNotNull(e); + + assertTrue(e instanceof Constant); + assertFalse(((Constant) e).getValue()); + } + + public void testZero2() throws Exception { + QuineMcClusky t = new QuineMcClusky(Variable.vars("A", "B", "C")); + t.fillTableWith(new BoolTableIntArray(new int[]{0, 0, 0, 0, 0, 0, 2, 2})); + t = t.simplify(); + Expression e = t.getExpression(); + assertNotNull(e); + + assertTrue(e instanceof Constant); + assertFalse(((Constant) e).getValue()); + } + + public void testOne() throws Exception { + QuineMcClusky t = new QuineMcClusky(Variable.vars("A", "B", "C")); + t.fillTableWith(new BoolTableIntArray(new int[]{1, 1, 1, 1, 1, 1, 1, 1})); + t = t.simplify(); + Expression e = t.getExpression(); + assertNotNull(e); + + assertTrue(e instanceof Constant); + assertTrue(((Constant) e).getValue()); + } + + public void testOne2() throws Exception { + QuineMcClusky t = new QuineMcClusky(Variable.vars("A", "B", "C")); + t.fillTableWith(new BoolTableIntArray(new int[]{1, 1, 1, 1, 1, 1, 2, 2})); + t = t.simplify(); + Expression e = t.getExpression(); + assertNotNull(e); + + assertTrue(e instanceof Constant); + assertTrue(((Constant) e).getValue()); + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/SimplifyTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/SimplifyTest.java new file mode 100644 index 000000000..1d7525727 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/SimplifyTest.java @@ -0,0 +1,46 @@ +package de.neemann.digital.analyse.quinemc; + + +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatterException; +import junit.framework.TestCase; + +import static de.neemann.digital.analyse.expression.Operation.and; +import static de.neemann.digital.analyse.expression.Operation.or; +import static de.neemann.digital.analyse.expression.Variable.v; + +/** + * @author hneemann + */ +public class SimplifyTest extends TestCase { + + public void testSimplify() throws Exception, FormatterException { + Variable a = v("a"); + Variable b = v("b"); + Expression e = or(and(a, b), a); + Expression s = QuineMcClusky.simplify(e); + + assertEquals("a", FormatToExpression.FORMATTER_UNICODE.format(s)); + } + + public void testSimplify2() throws Exception, FormatterException { + Variable a = v("a"); + Variable b = v("b"); + Expression e = and(or(a, b), a); + Expression s = QuineMcClusky.simplify(e); + + assertEquals("a", FormatToExpression.FORMATTER_UNICODE.format(s)); + } + + public void testSimplify3() throws Exception { + Variable a = v("a"); + Variable b = v("b"); + Variable c = v("c"); + Expression e = and(or(a, b), c); + Expression s = QuineMcClusky.simplify(e); + + assertNull(s); + } +} diff --git a/src/test/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAllTest.java b/src/test/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAllTest.java new file mode 100644 index 000000000..61253b20f --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/quinemc/primeselector/BruteForceGetAllTest.java @@ -0,0 +1,80 @@ +package de.neemann.digital.analyse.quinemc.primeselector; + + +import de.neemann.digital.analyse.expression.ContextFiller; +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.expression.format.FormatterException; +import de.neemann.digital.analyse.quinemc.BoolTableIntArray; +import de.neemann.digital.analyse.quinemc.FullVariantDontCareCreator; +import de.neemann.digital.analyse.quinemc.QuineMcClusky; +import de.neemann.digital.analyse.quinemc.TableRow; +import junit.framework.TestCase; + +import java.util.ArrayList; + +import static de.neemann.digital.analyse.expression.Variable.vars; + +/** + * @author hneemann + */ +public class BruteForceGetAllTest extends TestCase { + + /** + * up to 3 variables we can calculate all tables possible! + * + * @throws ExpressionException + */ + public void testFullRegression() throws ExpressionException, FormatterException { + new FullVariantDontCareCreator() { + @Override + public void handleTable(int n, int[] tab) throws ExpressionException { + performTestCalculation(n, tab); + } + }.create(); + new FullVariantDontCareCreator(4, 241) { + @Override + public void handleTable(int n, int[] tab) throws ExpressionException { + performTestCalculation(n, tab); + } + }.create(); + } + + /* + public void testFull() throws ExpressionException, FormatterException { + new FullVariantDontCareCreator(4) { + @Override + public void handleTable(int n, int[] tab) throws ExpressionException { + performTestCalculation(n, tab); + } + }.create(); + } /**/ + + static private void performTestCalculation(int n, int[] tab) throws ExpressionException { + + BruteForceGetAll ps = new BruteForceGetAll(); + + ArrayList v = vars(n); + new QuineMcClusky(v) + .fillTableWith(new BoolTableIntArray(tab)) + .simplify(ps); + + ArrayList> solutions = ps.getAllSolutions(); + if (solutions != null) { + + for (ArrayList sol : solutions) { + Expression e = QuineMcClusky.addAnd(null, sol, v); + ContextFiller context = new ContextFiller(v); + for (int i = 0; i < tab.length; i++) { + if (tab[i] <= 1) { + assertEquals(tab[i] == 1, e.calculate(context.setContextTo(i))); + + } + } + } + } + } + + +} \ No newline at end of file