/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.engine.bool; import static kodkod.engine.bool.BooleanConstant.FALSE; import static kodkod.engine.bool.BooleanConstant.TRUE; import static kodkod.engine.bool.Operator.AND; import static kodkod.engine.bool.Operator.OR; import java.util.Iterator; import kodkod.util.collections.Containers; import kodkod.util.ints.ArraySequence; import kodkod.util.ints.HomogenousSequence; import kodkod.util.ints.IndexedEntry; import kodkod.util.ints.IntIterator; import kodkod.util.ints.IntSet; import kodkod.util.ints.Ints; import kodkod.util.ints.RangeSequence; import kodkod.util.ints.SparseSequence; import kodkod.util.ints.TreeSequence; /** * <p>An n-dimensional matrix of {@link kodkod.engine.bool.BooleanValue boolean values}. * Boolean matrices are indexed using flat integer indeces. For example, * let m be a the 2 x 3 matrix of boolean variables identifed by labels [0 4 1; 5 10 2]. * Then, m[0] = 0, m[3] = 5, m[5] = 2, etc. </p> * * <p>All values stored in the same matrix must be created by the same {@link kodkod.engine.bool.BooleanFactory circuit factory}. * All methods that accept another BooleanMatrix as an input will throw an * IllegalArgumentException if the values in the input matrix do not belong * to the same factory as the values in the receiver matrix. </p> * * <p>Some instances can store only constant values, or can only store * values at particular indices (see {@link kodkod.engine.bool.BooleanFactory#matrix(Dimensions, IntSet, IntSet)}). * If this is the case, an attempt to call {@link #set(int, BooleanValue) } * with invalid parameters will cause an IllegalArgumentException or an IndexOutOfBoundsException. </p> * * @specfield dimensions: Dimensions * @specfield factory: BooleanFactory * @specfield elements: [0..dimensions.capacity) -> one factory.components * * @author Emina Torlak */ public final class BooleanMatrix implements Iterable<IndexedEntry<BooleanValue>>, Cloneable { private final Dimensions dims; private final BooleanFactory factory; private final SparseSequence<BooleanValue> cells; /** * Constructs a new matrix with the given dimensions, factory, and entries. * * @requires dimensions != null && factory != null && seq != null * @requires seq.indices() in [0..dimensions.capacity) * @ensures this.dimensions' = dimensions && this.factory' = factory && * this.elements' = [0..dimensions.capacity)->one FALSE */ private BooleanMatrix(Dimensions dimensions, BooleanFactory factory, SparseSequence<BooleanValue> seq) { this.dims = dimensions; this.factory = factory; this.cells = seq; } /** * Constructs a new matrix with the given dimensions and factory, * backed by a sparse sequence which can most efficiently hold * the elements storable in the sparse sequences s0 and s1. * @ensures this.dimensions' = dimensions && this.factory' = factory && * this.elements' = [0..dimensions.capacity)->one FALSE */ private BooleanMatrix(Dimensions d, BooleanFactory f, SparseSequence<BooleanValue> s0, SparseSequence<BooleanValue> s1) { this.dims = d; this.factory = f; final Class<?> c0 = s0.getClass(), c1 = s1.getClass(); if (c0!=c1 || c0==RangeSequence.class) this.cells = new RangeSequence<BooleanValue>(); else if (c0==HomogenousSequence.class) this.cells = new HomogenousSequence<BooleanValue>(TRUE, Ints.bestSet(d.capacity())); else this.cells = new TreeSequence<BooleanValue>(); } /** * Constructs a new matrix with the given dimensions and factory, * backed by a sparse sequence which can most efficiently hold * the elements storable in the matrices m and rest. * @requires null !in d + m + rest[int] * @requires m.factory = rest[int].factory * @requires d.equals(m.dims) => d.equals(rest[int].dims) * @ensures this.dimensions' = dimensions && this.factory' = m.factory && * this.elements' = [0..dimensions.capacity)->one FALSE * @throws IllegalArgumentException m.factory != rest[int].factory * @throws IllegalArgumentException !(d.equals(m.dims) => d.equals(rest[int].dims)) */ private BooleanMatrix(Dimensions d, BooleanMatrix m, BooleanMatrix...rest) { this.dims = d; this.factory = m.factory; final Class<?> h = HomogenousSequence.class, t = TreeSequence.class; final boolean sameDim = d.equals(m); Class<?> c = m.cells.getClass(); int cId = c==h ? 1 : c==t ? 2 : 4; for(BooleanMatrix other : rest) { checkFactory(factory, other.factory); if (sameDim) checkDimensions(d, other.dims); c = other.cells.getClass(); cId |= c==h ? 1 : c==t ? 2 : 4; } switch(cId) { case 1 : this.cells = new HomogenousSequence<BooleanValue>(TRUE, Ints.bestSet(d.capacity())); break; case 2 : this.cells = new TreeSequence<BooleanValue>(); break; default : this.cells = new RangeSequence<BooleanValue>(); } } /** * Constructs a new matrix with the given dimensions and factory. * The constructed matrix can store any kind of BooleanValue. * * @requires dimensions != null && factory != null * @ensures this.dimensions' = dimensions && this.factory' = factory && * this.elements' = [0..dimensions.capacity)->one FALSE */ BooleanMatrix(Dimensions dims, BooleanFactory factory) { this.dims = dims; this.factory = factory; this.cells = new RangeSequence<BooleanValue>(); } /** * Constructs a new matrix with the given dimensions and factory, * and initializes the indices in the given set to TRUE. * The constructed matrix will be capable of storing only constants * iff trueIndeces.equals(allIndices). Otherwise, it will be able to store any kind of BooleanValue * ONLY at the indices given by allIndices. Any attempt to call {@link #set(int, BooleanValue) } on * an index outside of allIndices may result in an IndexOutOfBoundsException. * * @requires allIndices.containsAll(trueIndices) * @requires trueIndices is not modifiable using an external handle * @requires dimensions != null && factory != null && trueIndices != null && allIndices != null * @requires dimensions.validate(allIndices.min()) && dimensions.validate(allIndices.max()) * @ensures this.dimensions' = dimensions && this.factory' = factory && * this.elements' = [0..dimensions.capacity)->one FALSE ++ trueIndices -> one TRUE */ BooleanMatrix(Dimensions dims, BooleanFactory factory, IntSet allIndices, IntSet trueIndices) { this.dims = dims; this.factory = factory; final int tsize = trueIndices.size(), asize = allIndices.size(); if (tsize==asize) this.cells = new HomogenousSequence<BooleanValue>(TRUE, trueIndices); else { this.cells = tsize==0 || asize/tsize >= 2 ? new ArraySequence<BooleanValue>(allIndices) : new RangeSequence<BooleanValue>(); for(IntIterator iter = trueIndices.iterator(); iter.hasNext(); ) { cells.put(iter.next(), TRUE); } } } /** * Returns the dimensions of this matrix. * @return this.dimensions */ public final Dimensions dimensions() { return dims; } /** * Returns the factory used to construct all the non-constant * entries in this matrix. * @return this.factory */ public final BooleanFactory factory() { return factory; } /** * Returns the number of non-FALSE entries in this matrix. * @return #this.elements.(BooleanValue - FALSE) */ public final int density() { return cells.size(); } /** * Returns an IndexedEntry-based view of the non-FALSE entries in this matrix. The returned * iterator enumerates indexed entries that represent the non-FALSE entries in the matrix, in the ascending * order of indeces. For example, suppose that the elements of this are 0->FALSE, 1->(a & b), 2->FALSE, 3->(c | d). Then, * the Iterator will return two IndexedEntries, c1 then c2, such that c1.index=1 && c1.value = a & b and * c2.index=3 && c.value = c | d. Calling {@link Iterator#remove()} on the returned iterator has the same effect * as setting the entry obtained through the last call to {@link Iterator#next()} to FALSE. * @return an iterator over IndexedEntries representing the non-FALSE entries in this matrix. */ public final Iterator<IndexedEntry<BooleanValue>> iterator() { return cells.iterator(); } /** * Returns the set of all indices in this matrix that contain * non-FALSE values. * @return the set of all indices in this matrix that contain * non-FALSE values. */ public final IntSet denseIndices() { return cells.indices(); } /** * Return FALSE if value is null; otherwise return value itself. * @return FALSE if value is null; otherwise return value itself. */ private final BooleanValue maskNull(BooleanValue value) { return value == null ? FALSE : value; } /** * Returns the value at the given index, without checking that the index is in bounds. * @return this.elements[index] */ private final BooleanValue fastGet(final int index) { return maskNull(cells.get(index)); } /** * Returns the element at the specified index. * @return this.elements[index] * @throws IndexOutOfBoundsException index < 0 || index >= this.dimensions.capacity */ public final BooleanValue get(final int index) { if (!dims.validate(index)) throw new IndexOutOfBoundsException(index + " is not a valid index."); return maskNull(cells.get(index)); } /** * Returns a new matrix each of whose entries is a negation of the * corresponding entry in this matrix. * * @return { m: BooleanMatrix | m.dimensions=this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | m.elements[i] = !this.elements[i] } */ public final BooleanMatrix not() { final BooleanMatrix negation = new BooleanMatrix(dims, factory, cells, cells); for (int i = 0, max = dims.capacity(); i < max; i++) { BooleanValue v = cells.get(i); if (v==null) negation.cells.put(i, TRUE); else if (v!=TRUE) negation.cells.put(i, v.negation()); } return negation; } /** * @throws IllegalArgumentException f != this.factory */ private static final void checkFactory(BooleanFactory f0, BooleanFactory f1) { if (f0 != f1) throw new IllegalArgumentException("Incompatible factories: " + f0 + " and " + f1); } /** * @throws IllegalArgumentException !d0.equals(d1) */ private static final void checkDimensions(Dimensions d0, Dimensions d1) { if (!d0.equals(d1)) throw new IllegalArgumentException("Incompatible dimensions: " + d0 + " and " + d1); } /** * Returns a new matrix such that an entry in the returned matrix represents a * conjunction of the corresponding entries in this and other matrix. The effect * of this method is the same as calling this.compose(ExprOperator.Binary.AND, other). * * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | * m.elements[i] = this.elements[i] AND other.elements[i] } * @throws NullPointerException other = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanMatrix and(BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); final SparseSequence<BooleanValue> s1 = other.cells; if (cells.isEmpty() || s1.isEmpty()) return ret; for(IndexedEntry<BooleanValue> e0 : cells) { BooleanValue v1 = s1.get(e0.index()); if (v1!=null) ret.fastSet(e0.index(), factory.and(e0.value(), v1)); } return ret; } /** * Returns a new matrix such that an entry in the returned matrix represents a * conjunction of the corresponding entries in this and other matrices. * * @requires all i: [0..others.length) | others[i].dimensions = this.dimensions && others[i].factory = this.factory * @return others.length = 0 => m else * { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | m.elements[i] = AND(this.elements[i], others[int].elements[i]) } * @throws NullPointerException others = null * @throws IllegalArgumentException some m: others[int] | !m.dimensions.equals(this.dimensions) || m.factory != this.factory */ public final BooleanMatrix and(final BooleanMatrix...others) { final BooleanMatrix ret = new BooleanMatrix(dims, this, others); for(IndexedEntry<BooleanValue> cell : cells) { final BooleanAccumulator acc = BooleanAccumulator.treeGate(AND, cell.value()); for(BooleanMatrix other : others) { if (acc.add(other.fastGet(cell.index()))==BooleanConstant.FALSE) break; } if (!acc.isShortCircuited()) { ret.fastSet(cell.index(), factory.accumulate(acc)); } } return ret; } /** * Returns a new matrix such that an entry in the returned matrix represents a * combination of the corresponding entries in this and other matrix. The effect * of this method is the same as calling this.compose(ExprOperator.Binary.OR, other). * * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | * m.elements[i] = this.elements[i] OR other.elements[i] } * @throws NullPointerException other = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanMatrix or(BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); if (this.cells.isEmpty()) return other.clone(); else if (other.cells.isEmpty()) return this.clone(); final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); final SparseSequence<BooleanValue> retSeq = ret.cells; for(IndexedEntry<BooleanValue> e0 : cells) { BooleanValue v1 = other.cells.get(e0.index()); if (v1==null) retSeq.put(e0.index(), e0.value()); else retSeq.put(e0.index(), factory.or(e0.value(), v1)); } for(IndexedEntry<BooleanValue> e1 : other.cells) { if (!cells.containsIndex(e1.index())) retSeq.put(e1.index(), e1.value()); } return ret; } /** * Returns a new matrix such that an entry in the returned matrix represents a * disjunction of the corresponding entries in this and other matrices. * * @requires all i: [0..others.length) | others[i].dimensions = this.dimensions && others[i].factory = this.factory * @return others.length = 0 => m else * { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | m.elements[i] = OR(this.elements[i], others[int].elements[i]) } * @throws NullPointerException others = null * @throws IllegalArgumentException some m: others[int] | !m.dimensions.equals(this.dimensions) || m.factory != this.factory */ public final BooleanMatrix or(final BooleanMatrix... others) { final BooleanMatrix ret = new BooleanMatrix(dims, this, others); for(IndexedEntry<BooleanValue> cell : cells) { final BooleanAccumulator acc = BooleanAccumulator.treeGate(OR, cell.value()); for(BooleanMatrix other : others) { if (acc.add(other.fastGet(cell.index()))==BooleanConstant.TRUE) break; } ret.fastSet(cell.index(), factory.accumulate(acc)); } for(int i = 0, length = others.length; i < length; i++) { for(IndexedEntry<BooleanValue> cell : others[i].cells) { if (ret.cells.containsIndex(cell.index())) continue; final BooleanAccumulator acc = BooleanAccumulator.treeGate(OR, cell.value()); for(int j = i+1; j < length; j++) { if (acc.add(others[j].fastGet(cell.index()))==BooleanConstant.TRUE) break; } ret.fastSet(cell.index(), factory.accumulate(acc)); } } return ret; } /** * Returns the cross product of this and other matrix, using conjunction instead of * multiplication. * * @return { m: BooleanMatrix | m = this x other } * @throws NullPointerException other = null * @throws IllegalArgumentException this.factory != other.factory */ public final BooleanMatrix cross(final BooleanMatrix other) { checkFactory(this.factory, other.factory); final BooleanMatrix ret = new BooleanMatrix(dims.cross(other.dims), factory, cells, other.cells); if (cells.isEmpty() || other.cells.isEmpty()) return ret; final int ocap = other.dims.capacity(); for(IndexedEntry<BooleanValue> e0 : cells) { int i = ocap * e0.index(); for(IndexedEntry<BooleanValue> e1: other.cells) { BooleanValue conjunction = factory.and(e0.value(), e1.value()); if (conjunction != FALSE) ret.cells.put(i + e1.index(), conjunction); } } return ret; } /** * Updates the itrs and idxs arrays for the next step of the cross-product computation and returns a partial * index based on the updated idxs values. * @requires matrices.length = itrs.length = idxs.length * @requires all m: matrices[int] | m.density() > 0 * @requires currentIdx is a partial index based on the current value of idxs * @ensures updates the itrs and idxs arrays for the next step cross-product computation * @return a partial index based on the freshly updated idxs values. */ private static int nextCross(final BooleanMatrix[] matrices, final IntIterator[] itrs, final int[] idxs, int currentIdx) { int mult = 1; for(int i = itrs.length-1; i >= 0; i--) { if (itrs[i].hasNext()) { final int old = idxs[i]; idxs[i] = itrs[i].next(); return currentIdx - mult*old + mult*idxs[i]; } else { itrs[i] = matrices[i].cells.indices().iterator(); final int old = idxs[i]; idxs[i] = itrs[i].next(); currentIdx = currentIdx - mult*old + mult*idxs[i]; mult *= matrices[i].dims.capacity(); } } return -1; } /** * Initializes the itrs and idxs arrays for cross-product computation and returns a partial * index based on the freshly computed idxs values. * @requires matrices.length = itrs.length = idxs.length * @requires all m: matrices[int] | m.density() > 0 * @ensures initializes the itrs and idxs arrays for cross-product computation * @return a partial index based on the freshly computed idxs values. */ private static int initCross(final BooleanMatrix[] matrices, final IntIterator[] itrs, final int[] idxs) { int mult = 1, idx = 0; for(int i = matrices.length-1; i >= 0; i--) { itrs[i] = matrices[i].cells.indices().iterator(); idxs[i] = itrs[i].next(); idx += mult*idxs[i]; mult *= matrices[i].dims.capacity(); } return idx; } /** * Returns the cross product of this and other matrices, using conjunction instead of * multiplication. * @requires this.factory = others[int].factory * @return others.length=0 => { m: BooleanMatrix | m.dimensions = this.dimensions && no m.elements } else * { m: BooleanMatrix | m = this x others[0] x ... x others[others.length-1] } * @throws NullPointerException others = null * @throws IllegalArgumentException this.factory != others[int].factory */ public final BooleanMatrix cross(final BooleanMatrix...others) { Dimensions retDims = dims; boolean empty = cells.isEmpty(); for(BooleanMatrix other : others) { retDims = retDims.cross(other.dims); empty = empty || other.cells.isEmpty(); } final BooleanMatrix ret = new BooleanMatrix(retDims, this, others); if (empty) return ret; final IntIterator[] itrs = new IntIterator[others.length]; final int[] otherIdxs = new int[others.length]; final int ocap = retDims.capacity() / dims.capacity(); for(IndexedEntry<BooleanValue> cell : cells) { final int idx = ocap * cell.index(); for(int restIdx = initCross(others, itrs, otherIdxs); restIdx >= 0; restIdx = nextCross(others, itrs, otherIdxs, restIdx)) { final BooleanAccumulator acc = BooleanAccumulator.treeGate(AND, cell.value()); for(int i = others.length-1; i >= 0; i--) { if (acc.add(others[i].fastGet(otherIdxs[i]))==BooleanConstant.FALSE) break; } if (!acc.isShortCircuited()) { ret.fastSet(idx + restIdx, factory.accumulate(acc)); } } } return ret; } /** * Sets the value at the specified index to the given value; * returns the value previously at the specified position. * It performs no index or null checking. * * @ensures this.elements'[index] = formula */ private final void fastSet(final int index, final BooleanValue formula) { if (formula==FALSE) cells.remove(index); else cells.put(index,formula); } /** * Returns the dot product of this and other matrix, using conjunction instead of * multiplication and disjunction instead of addition. * * @return { m: BooleanMatrix | m = this*other } * @throws NullPointerException other = null * @throws IllegalArgumentException this.factory != other.factory * @throws IllegalArgumentException dimensions incompatible for multiplication */ public final BooleanMatrix dot(final BooleanMatrix other) { checkFactory(this.factory, other.factory); final BooleanMatrix ret = new BooleanMatrix(dims.dot(other.dims), factory, cells, other.cells); if (cells.isEmpty() || other.cells.isEmpty()) return ret; final SparseSequence<BooleanValue> mutableCells = ret.clone().cells; final int b = other.dims.dimension(0); final int c = other.dims.capacity() / b; for(IndexedEntry<BooleanValue> e0 : cells) { int i = e0.index(); BooleanValue iVal = e0.value(); int rowHead = (i % b)*c, rowTail = rowHead + c - 1; for(Iterator<IndexedEntry<BooleanValue>> iter1 = other.cells.iterator(rowHead, rowTail); iter1.hasNext();) { IndexedEntry<BooleanValue> e1 = iter1.next(); BooleanValue retVal = factory.and(iVal, e1.value()); if (retVal != FALSE) { int k = (i / b)*c + e1.index()%c; if (retVal==TRUE) mutableCells.put(k, TRUE); else { BooleanValue kVal = mutableCells.get(k); if (kVal != TRUE) { if (kVal==null) { kVal = BooleanAccumulator.treeGate(OR); mutableCells.put(k, kVal); } ((BooleanAccumulator) kVal).add(retVal); } } } } } // make mutable gates immutable for(IndexedEntry<BooleanValue> e : mutableCells) { if (e.value()!=TRUE) { ret.fastSet(e.index(), factory.accumulate((BooleanAccumulator) e.value())); } else { ret.fastSet(e.index(), TRUE); } } return ret; } /** * Returns a formula stating that the entries in this matrix are a subset of * the entries in the given matrix; i.e. the value of every entry in this matrix * implies the value of the corresponding entry in the given matrix. * @return { f: BooleanValue | f <=> (this.elements[0]=>other.elements[0]) AND ... * AND (this.elements[this.dimensions.capacity-1]=>other.elements[this.dimensions.capacity-1])) * @throws NullPointerException other = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanValue subset(BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); final BooleanAccumulator a = BooleanAccumulator.treeGate(AND); for(IndexedEntry<BooleanValue> e0: cells) { if (a.add(factory.or(e0.value().negation(), other.fastGet(e0.index())))==FALSE) return FALSE; } return factory.accumulate(a); } /** * Returns a formula stating that the entries in this matrix are equivalent to * the entries in the given matrix; i.e. the value of every entry in this matrix * is true if and only if the value of the corresponding entry in the given matrix is true. * The same formula can be obtained by calling factory.and(this.subset(other), other.subset(this)), * but this method performs the operation more efficiently. * @return { f: BooleanValue | f <=> (this.elements[0]<=>other.elements[0]) AND ... * AND (this.elements[this.dimensions.capacity-1]<=>other.elements[this.dimensions.capacity-1])) * @throws NullPointerException other = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanValue eq(BooleanMatrix other) { return factory.and(this.subset(other), other.subset(this)); } /** * Returns a matrix representing the asymmetric difference between * the entries in this and the given matrix. The same matrix can * be obtained by calling this.and(other.not()), but this method * performs the operation more efficiently (intermediate * values are not explicitly created). * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && * all i: [0..m.dimensions.capacity) | * m.elements[i] = this.elements[i] AND !other.elements[i] } * @throws NullPointerException other = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanMatrix difference(BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); if (this.cells.isEmpty() || other.cells.isEmpty()) return this.clone(); final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); for(IndexedEntry<BooleanValue> e0 : cells) { ret.fastSet(e0.index(), factory.and(e0.value(), other.fastGet(e0.index()).negation())); } return ret; } /** * Returns the transitive closure of this matrix. * * @return { m: BooleanMatrix | m = ^this } * @throws UnsupportedOperationException #this.diensions != 2 || !this.dimensions.square() */ public final BooleanMatrix closure() { if (dims.numDimensions() != 2 || !dims.isSquare()) { throw new UnsupportedOperationException("#this.diensions != 2 || !this.dimensions.square()"); } if (cells.isEmpty()) return clone(); // System.out.println("closure of " + this); BooleanMatrix ret = this; // compute the number of rows in the matrix int rowNum = 0; final int rowFactor = dims.dimension(1); for(IndexedEntry<BooleanValue> rowLead = cells.first(); rowLead != null; rowLead = cells.ceil(((rowLead.index()/rowFactor) + 1) * rowFactor)) { rowNum++; } // compute closure using iterative squaring for(int i = 1; i < rowNum; i*=2) { ret = ret.or(ret.dot(ret)); } // System.out.println(ret); return ret==this ? clone() : ret; } /** * Returns the transpose of this matrix. * * @return { m: BooleanMatrix | m = ~this } * @throws UnsupportedOperationException #this.dimensions != 2 */ public final BooleanMatrix transpose() { final BooleanMatrix ret = new BooleanMatrix(dims.transpose(), factory, cells, cells); final int rows = dims.dimension(0), cols = dims.dimension(1); for (IndexedEntry<BooleanValue> e0 : cells) { ret.cells.put((e0.index()%cols)*rows + (e0.index()/cols), e0.value()); } return ret; } /** * Returns a boolean matrix m such that m = this if the given condition evaluates * to TRUE and m = other otherwise. * * @return { m: BooleanMatrix | m.dimensions = this.dimensions && * all i: [0..m.dimensions.capacity) | * m.elements[i] = condition => this.elements[i], other.elements[i] } * @throws NullPointerException other = null || condition = null * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory */ public final BooleanMatrix choice(BooleanValue condition, BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); if (condition==TRUE) return this.clone(); else if (condition==FALSE) return other.clone(); final BooleanMatrix ret = new BooleanMatrix(dims, factory); final SparseSequence<BooleanValue> otherCells = other.cells; for(IndexedEntry<BooleanValue> e0 : cells) { BooleanValue v1 = otherCells.get(e0.index()); if (v1==null) ret.fastSet(e0.index(), factory.and(condition, e0.value())); else ret.fastSet(e0.index(), factory.ite(condition, e0.value(), v1)); } for(IndexedEntry<BooleanValue> e1 : other.cells) { if (!cells.containsIndex(e1.index())) ret.fastSet(e1.index(), factory.and(condition.negation(), e1.value())); } return ret; } /** * Returns a matrix m such that the relational value of m is equal to the * relational value of this projected on the specified columns. * @requires column[int] in this.dimensions.dimensions[int] * @requires this.dimensions.isSquare() * @return { m: BooleanMatrix | [[m]] = project([[this]], columns) } * @throws IllegalArgumentExceptions columns.length < 1 || !this.dimensions.isSquare() * @throws NullPointerException columns = null */ public final BooleanMatrix project(Int[] columns) { if (!dims.isSquare()) throw new IllegalArgumentException("!this.dimensions.isSquare()"); final int rdnum = columns.length; if (rdnum < 1) throw new IllegalArgumentException("columns.length < 1"); final Dimensions rdims = Dimensions.square(dims.dimension(0), rdnum); final BooleanMatrix ret = new BooleanMatrix(rdims, factory, cells, cells); final int tdnum = dims.numDimensions(); final int[] tvector = new int[tdnum]; final int[] ivector = new int[rdnum]; final int[] rvector = new int[rdnum]; int nVarCols = 1; // detect constant columns to avoid unnecessary looping; for(int i = 0; i < rdnum; i++) { if (columns[i].isConstant()) { int value = columns[i].value(); if (value < 0 || value >= tdnum) { return ret; } else { // distinguish constants by making them negative ivector[i] = -value; } } else { nVarCols *= tdnum; } } PROJECT : for(int i = 0; i < nVarCols; i++) { BooleanValue colVal = TRUE; for(int j = 0; j < rdnum; j++) { // if the jth column is non-constant, check that it can take on the value ivector[j] if (ivector[j] >= 0) { colVal = factory.and(colVal, columns[j].eq(factory.integer(ivector[j]))); if (colVal==FALSE) continue PROJECT; } } for(IndexedEntry<BooleanValue> e : cells) { dims.convert(e.index(), tvector); for(int j = 0; j < rdnum; j++) { rvector[j] = tvector[StrictMath.abs(ivector[j])]; } int rindex = rdims.convert(rvector); ret.fastSet(rindex, factory.or(factory.and(e.value(), colVal), ret.fastGet(rindex))); } for(int j = rdnum-1; j >= 0; j--) { // update ivector // update ivector[j] only if the jth column is not constant if (ivector[j]>=0) { if (ivector[j]+1==tdnum) { ivector[j] = 0; } else { ivector[j] += 1; break; } } } } return ret; } /** * Returns a conjunction of the negated values between * start, inclusive, and end, exclusive. * @requires 0 <= start < end <= this.dimensions.capacity() * @return !this.elements[start] && !this.elements[start+1] && ... && !this.elements[end-1] */ private final BooleanValue nand(int start, int end) { final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); for(Iterator<IndexedEntry<BooleanValue>> iter = cells.iterator(start, end-1); iter.hasNext(); ) { if (g.add(iter.next().value().negation())==FALSE) return FALSE; } return factory.accumulate(g); } /** * Overrides the values in this matrix with those in <code>other</code>. * Specifically, for each index i of the returned matrix m, * m.elements[i] is true iff other.elements[i] is true or * this.elements[i] is true and all elements of <code>other</code> * that are in the same row as i are false. * @return {m: BooleanMatrix | m.dimensions = this.dimensions && * all i: [0..m.capacity()) | m.elements[i] = * other.elements[i] || * this.elements[i] && !OR(other.elements[row(i)]) } * where other.elements[row(i)] selects all elements of <code>other</code> * that are in the same row as i. * @throws NullPointerException other = null * @throws IllegalArgumentException other.dimensions != this.dimensions */ public final BooleanMatrix override(BooleanMatrix other) { checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); if (other.cells.isEmpty()) return this.clone(); final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); ret.cells.putAll(other.cells); final int rowLength = dims.capacity() / dims.dimension(0); int row = -1; BooleanValue rowVal = BooleanConstant.TRUE; for(IndexedEntry<BooleanValue> e0 : cells) { int e0row = e0.index() / rowLength; if (row != e0row) { row = e0row; rowVal = other.nand(row*rowLength, (row+1)*rowLength); } ret.fastSet(e0.index(), factory.or(ret.fastGet(e0.index()), factory.and(e0.value(), rowVal))); } return ret; } /** * Overrides the values in this matrix with those in <code>other</code>. * @return others.length = 0 => { m: BooleanMatrix | m.dimensions = this.dimensions && m.elements = this.elements) else * others.length = 1 => {m: BooleanMatrix | m.dimensions = this.dimensions && * all i: [0..m.capacity()) | m.elements[i] = * other.elements[i] || this.elements[i] && !OR(other.elements[rowOf(i)]) } else * this.override(others[0).override(others[1..others.length)) * @throws NullPointerException others = null * @throws IllegalArgumentException others[int].factory != this.factory or others[int].dimensions != this.dimensions */ public final BooleanMatrix override(BooleanMatrix... others) { if (others.length==0) return clone(); final BooleanMatrix[] matrices = Containers.copy(others, 0, new BooleanMatrix[others.length+1], 1, others.length); matrices[0] = this; for(int part = matrices.length; part > 1; part -= part/2) { final int max = part-1; for(int i = 0; i < max; i += 2) { matrices[i/2] = matrices[i].override(matrices[i+1]); } if (max%2==0) { // even max => odd number of entries matrices[max/2] = matrices[max]; } } return matrices[0]; } /** * Returns an Int that represents the cardinality (number of non-FALSE entries) of this * matrix using this.factory.intEncoding. * @return {i: Int | [[i]] = sum({v: elements[int] | if [[v]] then 1 else 0}) } */ public final Int cardinality() { return factory.sum(cells.values()); } /** * Returns a BooleanValue that constrains at least one value in this.elements to be true. The * effect of this method is the same as calling this.orFold(). * @return { f: BooleanValue | f <=> this.elements[0] || ... || this.elements[this.dimensions.capacity-1] } */ public final BooleanValue some() { final BooleanAccumulator g = BooleanAccumulator.treeGate(OR); for(IndexedEntry<BooleanValue> e : cells) { if (g.add(e.value())==TRUE) return TRUE; } return factory.accumulate(g); } /** * Returns a BooleanValue that constrains at most one value in this.elements to be true. * The effect of this method is the same as calling this.factory.or(this.one(), this.none()). * @return { f: BooleanValue | f <=> this.one() || this.none() } */ public final BooleanValue lone() { if (cells.isEmpty()) return TRUE; else { final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); BooleanValue partial = FALSE; for(IndexedEntry<BooleanValue> e: cells) { if (g.add(factory.or(e.value().negation(), partial.negation()))==FALSE) return FALSE; partial = factory.or(partial, e.value()); } return factory.accumulate(g); } } /** * Returns a BooleanValue that constraints exactly one value in this.elements to be true. * @return { f: BooleanValue | f <=> #this.elements[int] = 1 } */ public final BooleanValue one() { if (cells.isEmpty()) return FALSE; else { final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); BooleanValue partial = FALSE; for(IndexedEntry<BooleanValue> e: cells) { if (g.add(factory.or(e.value().negation(), partial.negation()))==FALSE) return FALSE; partial = factory.or(partial, e.value()); } g.add(partial); return factory.accumulate(g); } } /** * Returns a BooleanValue that constraints all values in this.elements to be false. * The effect of this method is the same as calling this.factory.not(this.some()). * @return { f: BooleanValue | f <=> !(this.elements[0] || ... || !this.elements[this.dimensions.capacity-1]) } */ public final BooleanValue none() { return some().negation(); } /** * Sets the specified index to the given value. * * @requires value in this.factory.components * @ensures this.elements'[index] = value * @throws NullPointerException value = null * @throws IllegalArgumentException the given is a formula, and this matrix accepts only constants * @throws IndexOutOfBoundsException the given index does not belong to the set of indices at which * this matrix can store non-FALSE values. */ public final void set(final int index, final BooleanValue value) { if (!dims.validate(index)) throw new IndexOutOfBoundsException("index < 0 || index >= this.dimensions.capacity"); if (value==null) throw new NullPointerException("formula=null"); if (value==FALSE) cells.remove(index); else cells.put(index,value); } /** * Returns a copy of this boolean matrix. * @return {m: BooleanMatrix - this | m.dimensions = this.dimensions && * m.elements = copy of this.elements } */ public BooleanMatrix clone() { try { return new BooleanMatrix(dims, factory, cells.clone()); } catch (CloneNotSupportedException e) { throw new InternalError(); // unreachable code. } } /** * @see java.lang.Object#toString() */ public String toString() { final StringBuilder buff = new StringBuilder("dimensions: "); buff.append(dims); buff.append(", elements: "); buff.append(cells); return buff.toString(); } }