/* Copyright 2014 Julia s.r.l. This file is part of BeeDeeDee. BeeDeeDee is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. BeeDeeDee is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BeeDeeDee. If not, see <http://www.gnu.org/licenses/>. */ package com.juliasoft.beedeedee.factories; import static com.juliasoft.julia.checkers.nullness.assertions.NullnessAssertions.assertNonNull; import java.io.Closeable; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantLock; import com.juliasoft.beedeedee.bdd.Assignment; import com.juliasoft.beedeedee.bdd.BDD; import com.juliasoft.beedeedee.bdd.ReplacementWithExistingVarException; import com.juliasoft.beedeedee.bdd.UnsatException; import com.juliasoft.julia.checkers.nullness.Inner0NonNull; import com.juliasoft.utils.concurrent.Executors; /** * A factory for Binary Decision Diagrams with automatic resizing and garbage * collection. */ public class Factory { /** * Constructs a factory with automatic resizing and garbage collection. * * @param utSize the initial size of the node table * @param cacheSize the size of the caches * @return an instance of the factory */ public static Factory mk(int utSize, int cacheSize) { return new Factory(utSize, cacheSize); } /** * Constructs a factory with automatic resizing and garbage collection. * * @param utSize the initial size of the node table * @param cacheSize the size of the caches * @param numberOfPreallocatedVars the number of single variable BDDs to preallocate * @return an instance of the factory */ public static Factory mk(int utSize, int cacheSize, int numberOfPreallocatedVars) { return new Factory(utSize, cacheSize, numberOfPreallocatedVars); } /** * Constructs a factory with automatic resizing and garbage collection, and * using the ER representation, that separates information on equivalent * variables from the bdd. * * @param utSize * the initial size of the node table * @param cacheSize * the size of the caches * @return an instance of the factory */ public static Factory mkER(int utSize, int cacheSize) { return new ERFactory(utSize, cacheSize); } public static interface GarbageCollectionListener { /** * Called when a garbage collection operation is about to start. * * @param num the progressive number of the garbage collection operation * @param size the number of nodes in the garbage collected table * @param free the number of free nodes after the operation * @param totalTime the cumulative garbage collection time up to now */ public void onStart(int num, int size, int free, long totalTime); /** * Called when a garbage collection operation has been performed. * * @param num the progressive number of the garbage collection operation * @param size the number of nodes in the garbage collected table * @param free the number of free nodes after the operation * @param time the time required for the garbage collection * @param totalTime the cumulative garbage collection time up to now */ public void onStop(int num, int size, int free, long time, long totalTime); } public static interface ResizeListener { /** * Called when a resize operation is about to start. * * @param num the progressive number of the resize operation * @param oldSize the old size of the table * @param newSize the new size of the table * @param totalTime the cumulative resize time up to now */ public void onStart(int num, int oldSize, int newSize, long totalTime); /** * Called when a resize operation has been performed. * * @param num the progressive number of the resize operation * @param oldSize the old size of the table * @param newSize the new size of the table * @param time the time required for the resize * @param totalTime the cumulative resize time up to now */ public void onStop(int num, int oldSize, int newSize, long time, long totalTime); } protected final static int FIRST_NODE_NUM = 2; protected final int NUMBER_OF_PREALLOCATED_VARS; protected final static int DEFAULT_NUMBER_OF_PREALLOCATED_VARS = 1000; protected final int NUMBER_OF_PREALLOCATED_NODES; protected ResizingAndGarbageCollectedUniqueTable ut; private final ArrayList<BDDImpl> allBDDsCreatedSoFar = new ArrayList<BDDImpl>(); protected int ZERO; protected int ONE; protected final int[] vars; protected final int[] notVars; private int maxVar; protected class GCLock implements Closeable { private final ReentrantLock lock; public GCLock() { this.lock = ut.getGCLock(); this.lock.lock(); } @Override public void close() { lock.unlock(); } } protected Factory(int utSize, int cacheSize) { this(utSize, cacheSize, DEFAULT_NUMBER_OF_PREALLOCATED_VARS); } Factory(int utSize, int cacheSize, int numberOfPreallocatedVars) { NUMBER_OF_PREALLOCATED_VARS = numberOfPreallocatedVars; NUMBER_OF_PREALLOCATED_NODES = FIRST_NODE_NUM + 2 * NUMBER_OF_PREALLOCATED_VARS; vars = new int[NUMBER_OF_PREALLOCATED_VARS]; notVars = new int[NUMBER_OF_PREALLOCATED_VARS]; utSize = Math.max(utSize, NUMBER_OF_PREALLOCATED_NODES); setUT(new ResizingAndGarbageCollectedUniqueTable(utSize, cacheSize, this)); } protected void setUT(ResizingAndGarbageCollectedUniqueTable uniqueTable) { ut = uniqueTable; // insert 0 and 1 ZERO = ut.get(Integer.MAX_VALUE - 1, -1, -1); ONE = ut.get(Integer.MAX_VALUE, -1, -1); // insert lower variables for (int var = 0; var < NUMBER_OF_PREALLOCATED_VARS; var++) vars[var] = ut.get(var, ZERO, ONE); // and their negation for (int var = 0; var < NUMBER_OF_PREALLOCATED_VARS; var++) notVars[var] = ut.get(var, ONE, ZERO); } /** * Call this method when the factory is no longer needed. */ public void done() { Executors.shutdown(); } protected final int MK(int var, int low, int high) { return low == high ? low : ut.get(var, low, high); } private void updateMaxVar(int var) { if (var > maxVar) // track maximum variable index (for satCount) synchronized (this) { if (var > maxVar) maxVar = var; } } /* * used only by replace() * Precondition: * low and high are Ordered BDD, * var does not appear in low. */ private int MKInOrder(int var, int low, int high) { int varLow = ut.var(low); int varHigh = ut.var(high); if (var == varLow || var == varHigh) throw new ReplacementWithExistingVarException(var); if (var < varLow && var < varHigh) return MK(var, low, high); if (varLow == varHigh) return MK(varLow, MKInOrder(var, ut.low(low), ut.low(high)), MKInOrder(var, ut.high(low), ut.high(high))); if (varLow < varHigh) return MK(varLow, MKInOrder(var, ut.low(low), high), MKInOrder(var, ut.high(low), high)); /* * since var cannot appear in low and high * we have: varHigh < varLow && varHigh < var) */ return MK(varHigh, MKInOrder(var, low, ut.low(high)), MKInOrder(var, low, ut.high(high))); } /** * @return the current number of nodes in the factory */ public int nodesCount() { try (GCLock lock = new GCLock()) { return ut.nodesCount(); } } public void printStatistics() { try (GCLock lock = new GCLock()) { ut.printStatistics(); } } /** * @return a BDD object representing the constant zero */ public BDD makeZero() { try (GCLock lock = new GCLock()) { return new BDDImpl(ZERO); } } /** * @return a BDD object representing the constant one */ public BDD makeOne() { try (GCLock lock = new GCLock()) { return new BDDImpl(ONE); } } /** * Constructs a BDD representing a single variable. * * @param v the number of the variable * @return the variable as a BDD object */ public BDD makeVar(int v) { try (GCLock lock = new GCLock()) { return new BDDImpl(innerMakeVar(v)); } } /** * Constructs a BDD representing the negation of a single variable. * * @param v the number of the variable * @return the negation of the variable as a BDD object */ public BDD makeNotVar(int v) { try (GCLock lock = new GCLock()) { return new BDDImpl(innerMakeNotVar(v)); } } protected final int innerMakeVar(int v) { updateMaxVar(v); if (v >= NUMBER_OF_PREALLOCATED_VARS) return MK(v, ZERO, ONE); else return vars[v]; } protected final int innerMakeNotVar(int v) { updateMaxVar(v); if (v >= NUMBER_OF_PREALLOCATED_VARS) return MK(v, ONE, ZERO); else return notVars[v]; } /** * Recursive version of the apply() function. */ protected final int innerAnd(int bdd1, int bdd2) { if (bdd1 == bdd2) return bdd1; if (bdd1 == ZERO || bdd2 == ZERO) return ZERO; if (bdd1 == ONE) return bdd2; if (bdd2 == ONE) return bdd1; int result; if ((result = ut.getFromCache(Operator.AND, bdd1, bdd2)) < 0) { int v1 = ut.var(bdd1), v2 = ut.var(bdd2); if (v1 == v2) ut.putIntoCache(Operator.AND, bdd1, bdd2, result = MK(v1, innerAnd(ut.low(bdd1), ut.low(bdd2)), innerAnd(ut.high(bdd1), ut.high(bdd2)))); else if (v1 < v2) ut.putIntoCache(Operator.AND, bdd1, bdd2, result = MK(v1, innerAnd(ut.low(bdd1), bdd2), innerAnd(ut.high(bdd1), bdd2))); else ut.putIntoCache(Operator.AND, bdd1, bdd2, result = MK(v2, innerAnd(bdd1, ut.low(bdd2)), innerAnd(bdd1, ut.high(bdd2)))); } return result; } protected final int innerOr(int bdd1, int bdd2) { if (bdd1 == bdd2) return bdd1; if (bdd1 == ONE || bdd2 == ONE) return ONE; if (bdd1 == ZERO) return bdd2; if (bdd2 == ZERO) return bdd1; int result; if ((result = ut.getFromCache(Operator.OR, bdd1, bdd2)) < 0) { int v1 = ut.var(bdd1), v2 = ut.var(bdd2); if (v1 == v2) ut.putIntoCache(Operator.OR, bdd1, bdd2, result = MK(v1, innerOr(ut.low(bdd1), ut.low(bdd2)), innerOr(ut.high(bdd1), ut.high(bdd2)))); else if (v1 < v2) ut.putIntoCache(Operator.OR, bdd1, bdd2, result = MK(v1, innerOr(ut.low(bdd1), bdd2), innerOr(ut.high(bdd1), bdd2))); else ut.putIntoCache(Operator.OR, bdd1, bdd2, result = MK(v2, innerOr(bdd1, ut.low(bdd2)), innerOr(bdd1, ut.high(bdd2)))); } return result; } protected final int innerBiimp(int bdd1, int bdd2) { if (bdd1 == bdd2) return ONE; if (bdd1 == ZERO && bdd2 == ONE) return ZERO; if (bdd1 == ONE && bdd2 == ZERO) return ZERO; if (bdd1 == ONE) return bdd2; if (bdd2 == ONE) return bdd1; int result; if ((result = ut.getFromCache(Operator.BIIMP, bdd1, bdd2)) < 0) { int v1 = ut.var(bdd1), v2 = ut.var(bdd2); if (v1 == v2) ut.putIntoCache(Operator.BIIMP, bdd1, bdd2, result = MK(v1, innerBiimp(ut.low(bdd1), ut.low(bdd2)), innerBiimp(ut.high(bdd1), ut.high(bdd2)))); else if (v1 < v2) ut.putIntoCache(Operator.BIIMP, bdd1, bdd2, result = MK(v1, innerBiimp(ut.low(bdd1), bdd2), innerBiimp(ut.high(bdd1), bdd2))); else ut.putIntoCache(Operator.BIIMP, bdd1, bdd2, result = MK(v2, innerBiimp(bdd1, ut.low(bdd2)), innerBiimp(bdd1, ut.high(bdd2)))); } return result; } /** * Recursive version of the apply() function. */ protected final int innerImp(int bdd1, int bdd2) { if (bdd1 == bdd2 || bdd1 == ZERO) return ONE; else if (bdd1 == ONE) return bdd2; int result; if ((result = ut.getFromCache(Operator.IMP, bdd1, bdd2)) < 0) { int v1 = ut.var(bdd1), v2 = ut.var(bdd2); if (v1 == v2) ut.putIntoCache(Operator.IMP, bdd1, bdd2, result = MK(v1, innerImp(ut.low(bdd1), ut.low(bdd2)), innerImp(ut.high(bdd1), ut.high(bdd2)))); else if (v1 < v2) ut.putIntoCache(Operator.IMP, bdd1, bdd2, result = MK(v1, innerImp(ut.low(bdd1), bdd2), innerImp(ut.high(bdd1), bdd2))); else ut.putIntoCache(Operator.IMP, bdd1, bdd2, result = MK(v2, innerImp(bdd1, ut.low(bdd2)), innerImp(bdd1, ut.high(bdd2)))); } return result; } protected final int innerXor(int bdd1, int bdd2) { if (bdd1 == bdd2 || (bdd1 == ONE && bdd2 == ONE) || (bdd1 == ZERO && bdd2 == ZERO)) return ZERO; if ((bdd1 == ONE && bdd2 == ZERO) || (bdd1 == ZERO && bdd2 == ONE)) return ONE; if (bdd1 == ZERO) return bdd2; if (bdd2 == ZERO) return bdd1; int result; if ((result = ut.getFromCache(Operator.XOR, bdd1, bdd2)) < 0) { int v1 = ut.var(bdd1), v2 = ut.var(bdd2); if (v1 == v2) ut.putIntoCache(Operator.XOR, bdd1, bdd2, result = MK(v1, innerXor(ut.low(bdd1), ut.low(bdd2)), innerXor(ut.high(bdd1), ut.high(bdd2)))); else if (v1 < v2) ut.putIntoCache(Operator.XOR, bdd1, bdd2, result = MK(v1, innerXor(ut.low(bdd1), bdd2), innerXor(ut.high(bdd1), bdd2))); else ut.putIntoCache(Operator.XOR, bdd1, bdd2, result = MK(v2, innerXor(bdd1, ut.low(bdd2)), innerXor(bdd1, ut.high(bdd2)))); } return result; } protected final int innerNot(int id) { return innerImp(id, ZERO); } protected final int innerRestrict(int id, int var, boolean value) { int result; result = ut.getRestrictCache().get(id, var, value); if (result >= 0) return result; int diff = ut.var(id) - var; if (diff > 0) return id; else if (diff < 0) { result = MK(ut.var(id), innerRestrict(ut.low(id), var, value), innerRestrict(ut.high(id), var, value)); ut.getRestrictCache().put(id, var, value, result); return result; } else if (value) return innerRestrict(ut.high(id), var, value); else return innerRestrict(ut.low(id), var, value); } protected final int innerExist(int id, int var) { return innerOr(innerRestrict(id, var, false), innerRestrict(id, var, true)); } protected final int innerReplace(int bdd, Map<Integer, Integer> renaming, int hashOfRenaming) { assertNonNull(renaming); if (bdd < FIRST_NODE_NUM) // terminal node return bdd; int result = ut.getReplaceCache().get(bdd, renaming, hashOfRenaming); if (result >= 0) return result; int oldLow = ut.low(bdd), oldHigh = ut.high(bdd); int lowRenamed = innerReplace(oldLow, renaming, hashOfRenaming); int highRenamed = innerReplace(oldHigh, renaming, hashOfRenaming); int var = ut.var(bdd); Integer newVar = renaming.get(var); if (newVar == null) newVar = var; if (var == newVar && lowRenamed == oldLow && highRenamed == oldHigh) result = bdd; else result = MKInOrder(newVar, lowRenamed, highRenamed); ut.getReplaceCache().put(bdd, renaming, result, hashOfRenaming); return result; } protected final int innerQuantify(int id, BitSet vars, boolean exist, int hashCodeOfVars) { if (id < FIRST_NODE_NUM) // terminal node return id; int result = ut.getQuantCache().get(exist, id, vars, hashCodeOfVars); if (result >= 0) return result; int oldA = ut.low(id), oldB = ut.high(id); int a = innerQuantify(oldA, vars, exist, hashCodeOfVars); int b = innerQuantify(oldB, vars, exist, hashCodeOfVars); int var = ut.var(id); if (vars.get(var)) if (exist) result = innerOr(a, b); else result = innerAnd(a, b); else if (a == oldA && b == oldB) result = id; else result = MK(var, a, b); ut.getQuantCache().put(exist, id, vars, hashCodeOfVars, result); return result; } private int freedBDDsCounter; public class BDDImpl implements BDD { /** * The position of this BDD inside the table of BDD nodes. */ protected int id; /** * A unique identifier of the node where this BDD starts. */ private int hashCode; /** * A cache for the nodeCount() method. -1 means that it is not valid. */ private int nodeCount; protected BDDImpl(int id) { setId(id); synchronized (allBDDsCreatedSoFar) { allBDDsCreatedSoFar.add(this); } } protected final void setId(int id) { this.id = id; this.hashCode = ut.hashCodeAux(id); this.nodeCount = -1; } public final int getId() { return id; } @Override public void free() { if (id >= NUMBER_OF_PREALLOCATED_NODES) { id = -1; ut.scheduleGC(); ut.gcIfAlmostFull(); } else if (id >= 0) // already freed? id = -1; shrinkTheListOfAllBDDsIfTooLarge(); } private void shrinkTheListOfAllBDDsIfTooLarge() { if (++freedBDDsCounter > 100000) try (GCLock lock = new GCLock()) { synchronized (allBDDsCreatedSoFar) { if (freedBDDsCounter > 100000) { @SuppressWarnings("unchecked") List<BDDImpl> copy = (List<BDDImpl>) allBDDsCreatedSoFar.clone(); allBDDsCreatedSoFar.clear(); for (BDDImpl bdd: copy) if (bdd.id >= 0) allBDDsCreatedSoFar.add(bdd); freedBDDsCounter = 0; } } } } @Override public String toString() { if (id < 0) return "[freed zombie BDD]"; try (GCLock lock = new GCLock()) { return "digraph G {\n" + toDot(id) + "}\n"; } } private String toDot(int currentId) { String s = ""; boolean terminal = false; int var = ut.var(currentId); if (currentId < 2) { var = currentId == 0 ? 0 : 1; terminal = true; } int low = ut.low(currentId); int high = ut.high(currentId); s += currentId + " [label=" + var + (terminal ? ",shape=box]\n" : "]\n"); if (terminal) { return s; } s += currentId + " -> " + low + " [style=dotted];\n"; s += currentId + " -> " + high + ";\n"; s += toDot(low); s += toDot(high); return s; } @Override public BDD or(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerOr(id, ((BDDImpl) other).id)); } } @Override public BDD orWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerOr(id, ((BDDImpl) other).id)); } other.free(); return this; } @Override public BDD and(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerAnd(id, ((BDDImpl) other).id)); } } @Override public BDD andWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerAnd(id, ((BDDImpl) other).id)); } other.free(); return this; } @Override public BDD xor(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerXor(id, ((BDDImpl) other).id)); } } @Override public BDD xorWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerXor(id, ((BDDImpl) other).id)); } other.free(); return this; } @Override public BDD nand(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerImp(innerAnd(id, ((BDDImpl) other).id), ZERO)); } } @Override public BDD nandWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerImp(innerAnd(id, ((BDDImpl) other).id), ZERO)); } other.free(); return this; } @Override public BDD not() { ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerNot(id)); } } @Override public BDD notWith() { try (GCLock lock = new GCLock()) { setId(innerNot(id)); } return this; } @Override public BDD imp(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerImp(id, ((BDDImpl) other).id)); } } @Override public BDD impWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerImp(id, ((BDDImpl) other).id)); } other.free(); return this; } @Override public BDD biimp(BDD other) { assertNonNull(other); ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerBiimp(id, ((BDDImpl) other).id)); } } @Override public BDD biimpWith(BDD other) { assertNonNull(other); try (GCLock lock = new GCLock()) { setId(innerBiimp(id, ((BDDImpl) other).id)); } other.free(); return this; } @Override public BDD copy() { ut.gcIfAlmostFull(); try (GCLock lock = new GCLock()) { return new BDDImpl(id); } } @Override public Assignment anySat() throws UnsatException { AssignmentImpl assignment = new AssignmentImpl(); try (GCLock lock = new GCLock()) { anySat(id, assignment); } return assignment; } private void anySat(int bdd, AssignmentImpl assignment) throws UnsatException { if (bdd == ZERO) throw new UnsatException(); else if (bdd != ONE) { if (ut.low(bdd) == ZERO) { assignment.put(ut.var(bdd), true); anySat(ut.high(bdd), assignment); } else { assignment.put(ut.var(bdd), false); anySat(ut.low(bdd), assignment); } } } @Override public List<Assignment> allSat() { try (GCLock lock = new GCLock()) { return allSat(id); } } private @Inner0NonNull List<Assignment> allSat(int bdd) { List<Assignment> list = new ArrayList<Assignment>(); if (bdd != ZERO) if (bdd == ONE) list.add(new AssignmentImpl()); else { int var = ut.var(bdd); List<Assignment> lowList = allSat(ut.low(bdd)); for (Assignment assignment: lowList) ((AssignmentImpl) assignment).put(var, false); List<Assignment> highList = allSat(ut.high(bdd)); for (Assignment assignment: highList) ((AssignmentImpl) assignment).put(var, true); // join lists list.addAll(lowList); list.addAll(highList); } return list; } //TODO this should probably return a BigInteger @Override public long satCount() { return satCount(maxVar); } //TODO this should probably return a BigInteger @Override public long satCount(int maxVar) { try (GCLock lock = new GCLock()) { return (long) (Math.pow(2, ut.var(id)) * count(id, maxVar)); } } private double count(int bdd, int maxVar) { if (bdd < FIRST_NODE_NUM) // terminal node return bdd; int low = ut.low(bdd); int high = ut.high(bdd); int varLow = low < FIRST_NODE_NUM ? maxVar + 1 : ut.var(low); int varHigh = high < FIRST_NODE_NUM ? maxVar + 1 : ut.var(high); return Math.pow(2, varLow - ut.var(bdd) - 1) * count(low, maxVar) + Math.pow(2, varHigh - ut.var(bdd) - 1) * count(high, maxVar); } @Override public long pathCount() { try (GCLock lock = new GCLock()) { return pathCount(id); } } private long pathCount(int id) { if (id < FIRST_NODE_NUM) return id; else return pathCount(ut.low(id)) + pathCount(ut.high(id)); } @Override public BDD restrict(BDD var) { assertNonNull(var); try (GCLock lock = new GCLock()) { int res = id; for (int varId = ((BDDImpl) var).id; varId >= FIRST_NODE_NUM; varId = ut.high(varId)) if (ut.low(varId) == ZERO) res = innerRestrict(res, ut.var(varId), true); else if (ut.low(varId) == ONE) res = innerRestrict(res, ut.var(varId), false); return new BDDImpl(res); } } @Override public BDD restrictWith(BDD var) { assertNonNull(var); try (GCLock lock = new GCLock()) { int res = id; for (int varId = ((BDDImpl) var).id; varId >= FIRST_NODE_NUM; varId = ut.high(varId)) if (ut.low(varId) == ZERO) res = innerRestrict(res, ut.var(varId), true); else if (ut.low(varId) == ONE) res = innerRestrict(res, ut.var(varId), false); setId(res); } return this; } @Override public BDD restrict(int var, boolean value) { try (GCLock lock = new GCLock()) { return new BDDImpl(innerRestrict(id, var, value)); } } @Override public BDD exist(int var) { try (GCLock lock = new GCLock()) { return new BDDImpl(innerExist(id, var)); } } @Override public BDD exist(BDD vars) { assertNonNull(vars); BitSet varsAsBitSet = vars.vars(); int hashCode = varsAsBitSet.hashCode(); try (GCLock lock = new GCLock()) { return quantify(varsAsBitSet, true, hashCode); } } @Override public BDD exist(BitSet vars) { assertNonNull(vars); int hashCodeVars = vars.hashCode(); try (GCLock lock = new GCLock()) { return quantify(vars, true, hashCodeVars); } } @Override public BDD forAll(int var) { BDD falseOp = restrict(var, false); BDD trueOp = restrict(var, true); return falseOp.andWith(trueOp); } @Override public BDD forAll(BDD var) { assertNonNull(var); BitSet varsAsBitSet = var.vars(); int hashCodeVars = varsAsBitSet.hashCode(); try (GCLock lock = new GCLock()) { return quantify(varsAsBitSet, false, hashCodeVars); } } private BDD quantify(BitSet vars, boolean exist, int hashCodeVars) { return new BDDImpl(innerQuantify(id, vars, exist, hashCodeVars)); } @Override public BDD simplify(BDD d) { assertNonNull(d); try (GCLock lock = new GCLock()) { return new BDDImpl(simplify(((BDDImpl) d).id, id)); } } private int simplify(int d, int u) { if (d == ZERO || u == ZERO) return ZERO; else if (u == ONE) return ONE; int vu = ut.var(u), vd = ut.var(d); if (d == ONE) return MK(vu, simplify(d, ut.low(u)), simplify(d, ut.high(u))); else if (vd == vu) if (ut.low(d) == ZERO) return simplify(ut.high(d), ut.high(u)); else if (ut.high(d) == ZERO) return simplify(ut.low(d), ut.low(u)); else return MK(vu, simplify(ut.low(d), ut.low(u)), simplify(ut.high(d), ut.high(u))); else if (vd < vu) return MK(vd, simplify(ut.low(d), u), simplify(ut.high(d), u)); else return MK(vu, simplify(d, ut.low(u)), simplify(d, ut.high(u))); } @Override public boolean isZero() { return id == ZERO; } @Override public boolean isOne() { return id == ONE; } @Override public boolean isVar() { return ut.low(id) == ZERO && ut.high(id) == ONE; } @Override public boolean isNotVar() { return ut.low(id) == ONE && ut.high(id) == ZERO; } @Override public int[] varProfile() { int[] varp = new int[maxVar + 1]; try (GCLock lock = new GCLock()) { varProfile(id, varp, new HashSet<Integer>()); } return varp; } private void varProfile(int bdd, int[] varp, Set<Integer> seen) { // terminal node or already seen if (bdd < FIRST_NODE_NUM || !seen.add(bdd)) return; varp[ut.var(bdd)]++; varProfile(ut.low(bdd), varp, seen); varProfile(ut.high(bdd), varp, seen); } @Override public int nodeCount() { // we check in the cache first if (nodeCount >= 0) return nodeCount; try (GCLock lock = new GCLock()) { return nodeCount = nodeCount(id, new HashSet<Integer>()); } } private int nodeCount(int bdd, Set<Integer> seen) { // terminal node or already seen if (bdd < FIRST_NODE_NUM || !seen.add(bdd)) return 0; // variables or their negation if (bdd < NUMBER_OF_PREALLOCATED_NODES) return 1; return 1 + nodeCount(ut.low(bdd), seen) + nodeCount(ut.high(bdd), seen); } @Override public BDD replace(Map<Integer, Integer> renaming) { assertNonNull(renaming); if (id == ZERO) return makeZero(); else if (id == ONE) return makeOne(); int hash = renaming.hashCode(); try (GCLock lock = new GCLock()) { return new BDDImpl(innerReplace(id, renaming, hash)); } } @Override public BDD replaceWith(Map<Integer, Integer> renaming) { assertNonNull(renaming); if (id < FIRST_NODE_NUM) // terminal node return this; int hashCodeOfRenaming = renaming.hashCode(); try (GCLock lock = new GCLock()) { setId(innerReplace(id, renaming, hashCodeOfRenaming)); } return this; } @Override public BDD ite(BDD thenBDD, BDD elseBDD) { assertNonNull(thenBDD); assertNonNull(elseBDD); try (GCLock lock = new GCLock()) { return new BDDImpl(ite(id, ((BDDImpl) thenBDD).id, ((BDDImpl) elseBDD).id)); } } private int ite(int f, int g, int h) { if (f == ONE) return g; if (f == ZERO) return h; if (g == h) return g; if (g == ONE && h == ZERO) return f; if (g == ZERO && h == ONE) return innerImp(f, ZERO); int vf = ut.var(f); int vg = ut.var(g); int vh = ut.var(h); if (vf == vg) if (vf == vh) return MK(vf, ite(ut.low(f), ut.low(g), ut.low(h)), ite(ut.high(f), ut.high(g), ut.high(h))); else if (vf < vh) return MK(vf, ite(ut.low(f), ut.low(g), h), ite(ut.high(f), ut.high(g), h)); else return MK(vh, ite(f, g, ut.low(h)), ite(f, g, ut.high(h))); else if (vf < vg) if (vf == vh) return MK(vf, ite(ut.low(f), g, ut.low(h)), ite(ut.high(f), g, ut.high(h))); else if (vf < vh) return MK(vf, ite(ut.low(f), g, h), ite(ut.high(f), g, h)); else return MK(vh, ite(f, g, ut.low(h)), ite(f, g, ut.high(h))); else if (vg == vh) return MK(vg, ite(f, ut.low(g), ut.low(h)), ite(f, ut.high(g), ut.high(h))); else if (vg < vh) return MK(vg, ite(f, ut.low(g), h), ite(f, ut.high(g), h)); else return MK(vh, ite(f, g, ut.low(h)), ite(f, g, ut.high(h))); } @Override public BDD relProd(BDD other, BDD var) { assertNonNull(other); assertNonNull(var); // TODO this implementation is correct, but not efficient return and(other).exist(var); } @Override public BDD compose(BDD other, int var) { assertNonNull(other); try (GCLock lock = new GCLock()) { return new BDDImpl(compose(id, ((BDDImpl) other).id, var)); } } private int compose(int id1, int id2, int var) { int v1 = ut.var(id1); int v2 = ut.var(id2); if (v1 > var) return id1; if (v1 < var) if (v1 == v2) return MK(v1, compose(ut.low(id1), ut.low(id2), var), compose(ut.high(id1), ut.high(id2), var)); else if (v1 < v2) return MK(v1, compose(ut.low(id1), id2, var), compose(ut.high(id1), id2, var)); else return MK(v2, compose(id1, ut.low(id2), var), compose(id1, ut.high(id2), var)); else return ite(id2, ut.high(id1), ut.low(id1)); } @Override public boolean isEquivalentTo(BDD other) { assertNonNull(other); if (this == other) return true; BDDImpl otherImpl = (BDDImpl) other; try (GCLock lock = new GCLock()) { return id == otherImpl.id; } } @Override public int hashCodeAux() { return hashCode; } @Override public int var() { try (GCLock lock = new GCLock()) { return ut.var(id); } } @Override public BDDImpl high() { try (GCLock lock = new GCLock()) { return new BDDImpl(ut.high(id)); } } @Override public BDDImpl low() { try (GCLock lock = new GCLock()) { return new BDDImpl(ut.low(id)); } } @Override public Factory getFactory() { return Factory.this; } @Override public BitSet vars() { BitSet vars = new BitSet(); try (GCLock lock = new GCLock()) { updateVars(id, vars, new HashSet<Integer>()); } return vars; } private void updateVars(int id, BitSet vars, Set<Integer> seen) { if (!seen.add(id)) return; if (id >= FIRST_NODE_NUM) { vars.set(ut.var(id)); updateVars(ut.low(id), vars, seen); updateVars(ut.high(id), vars, seen); } } @Override public int maxVar() { try (GCLock lock = new GCLock()) { return maxVar(id); } } private int maxVar(int bdd) { if (bdd < FIRST_NODE_NUM) return -1; int low = ut.low(bdd); int maxVar = Math.max(ut.var(bdd), maxVar(low)); int high = ut.high(bdd); maxVar = Math.max(maxVar, maxVar(high)); return maxVar; } } /** * @return the maximum variable index used so far */ public int getMaxVar() { return maxVar; } private class AssignmentImpl implements Assignment { private final Map<Integer, Boolean> truthTable; private AssignmentImpl() { this.truthTable = new TreeMap<Integer, Boolean>(); } @Override public void put(int var, boolean value) { truthTable.put(var, value); } @Override public boolean holds(BDD var) throws IndexOutOfBoundsException { assertNonNull(var); return holds(var.var()); } @Override public boolean holds(int i) { Boolean result = truthTable.get(i); if (result != null) return result; throw new IndexOutOfBoundsException("unknown variable " + i); } @Override public BDD toBDD() { BDD res = makeOne(); for (int v: truthTable.keySet()) res.andWith(truthTable.get(v) == Boolean.FALSE ? makeNotVar(v) : makeVar(v)); return res; } @Override public String toString() { StringBuilder sb = new StringBuilder("<"); for (int v: truthTable.keySet()) sb.append(v).append(":").append(truthTable.get(v) == Boolean.TRUE ? 1 : 0).append(", "); return sb.substring(0, sb.length() - 2).concat(">"); } } public void printNodeTable() { try (GCLock lock = new GCLock()) { System.out.println(ut); } } /** * Runs the garbage collection. */ public void gc() { ut.gc(); } /** * Sets the maximal increase for the number nodes in the table of nodes * of this factory. * * @param maxIncrease the maximal increase * @return the old maximal increase */ public int setMaxIncrease(int maxIncrease) { return ut.setMaxIncrease(maxIncrease); } public double setIncreaseFactor(double increaseFactor) { return ut.setIncreaseFactor(increaseFactor); } /** * Sets the cache ratio for the operator caches. When the node table grows, * operator caches will also grow to maintain the ratio. * * @param cacheRatio the cache ratio * @return the old cache ratio */ public double setCacheRatio(double cacheRatio) { return ut.setCacheRatio(cacheRatio); } /** * Sets the minimum percentage of nodes to be reclaimed after a garbage collection. * If this percentage is not reclaimed, the node table will be grown. * The range of x is 0..1. The default is .20. * * @param minFreeNodes the percentage * @return the old percentage */ public double setMinFreeNodes(double minFreeNodes) { return ut.setMinFreeNodes(minFreeNodes); } /** * Sets the listener of garbage collection operations. * * @param listener the listener */ public void setGarbageCollectionListener(GarbageCollectionListener listener) { ut.setGarbageCollectionListener(listener); } /** * Sets the listener of resize operations. * * @param listener the listener */ public void setResizeListener(ResizeListener listener) { ut.setResizeListener(listener); } /** * Replaces the index of each BDD created so far into the new id provided * by the given map. * * @param newPositions a map from old to new index */ protected void updateIndicesOfAllBDDsCreatedSoFar(int[] newPositions) { for (BDDImpl bdd: allBDDsCreatedSoFar) if (bdd.id >= NUMBER_OF_PREALLOCATED_NODES) bdd.id = newPositions[bdd.id]; } /** * Marks in the given array the positions of the indices of the alive bdds. * * @param aliveNodes the array, that gets modified */ protected void markAliveNodes(boolean[] aliveNodes) { if (ut.size > 900000) { parallelMarkAliveNodes(aliveNodes); return; } for (int pos = 0; pos < NUMBER_OF_PREALLOCATED_NODES; pos++) aliveNodes[pos] = true; @SuppressWarnings("unchecked") List<BDDImpl> copy = (ArrayList<BDDImpl>) allBDDsCreatedSoFar.clone(); allBDDsCreatedSoFar.clear(); for (BDDImpl bdd: copy) { allBDDsCreatedSoFar.add(bdd); if (bdd.id >= NUMBER_OF_PREALLOCATED_NODES) markAsAlive(bdd.id, aliveNodes); } } private void parallelMarkAliveNodes(final boolean[] aliveNodes) { final int total = Runtime.getRuntime().availableProcessors(); @SuppressWarnings("unchecked") final List<BDDImpl> copy = (ArrayList<BDDImpl>) allBDDsCreatedSoFar.clone(); class AliveNodesMarker implements Runnable { private final List<BDDImpl> alive = new ArrayList<>(); private final int num; private AliveNodesMarker(int num) { this.num = num; } @Override public void run() { for (BDDImpl bdd: copy) { int id = bdd.id; if (id % total == num) { alive.add(bdd); if (id >= NUMBER_OF_PREALLOCATED_NODES) markAsAlive(id, aliveNodes); } } } } AliveNodesMarker[] slaves = new AliveNodesMarker[total]; for (int num = 0; num < total; num++) slaves[num] = new AliveNodesMarker(num); allBDDsCreatedSoFar.clear(); for (int pos = 0; pos < NUMBER_OF_PREALLOCATED_NODES; pos++) aliveNodes[pos] = true; Executors.parallelise(slaves); for (AliveNodesMarker slave: slaves) allBDDsCreatedSoFar.addAll(slave.alive); } private void markAsAlive(int node, boolean[] aliveNodes) { if (node >= NUMBER_OF_PREALLOCATED_NODES && !aliveNodes[node]) { aliveNodes[node] = true; markAsAlive(ut.low(node), aliveNodes); markAsAlive(ut.high(node), aliveNodes); } } /** * Counts the nodes in a collection of BDDs. * Shared nodes are counted only once. * * @param bdds the collection of BDDs * @return the total number of nodes */ public int nodeCount(Collection<BDD> bdds) { assertNonNull(bdds, "the collection of BBDs cannot be null here"); int count = 0; Set<Integer> seen = new HashSet<>(); for (BDD bdd: bdds) { BDDImpl bddi = (BDDImpl) bdd; if (bddi != null) try (GCLock lock = new GCLock()) { count += bddi.nodeCount(bddi.id, seen); } } return count; } @SuppressWarnings("unchecked") List<BDDImpl> getAllBDDsCreatedSoFarCopy() { return (ArrayList<BDDImpl>) allBDDsCreatedSoFar.clone(); } /** * @return the number of non-freed BDD instances created so far */ public int bddCount() { synchronized (allBDDsCreatedSoFar) { return allBDDsCreatedSoFar.size() - freedBDDsCounter; } } }