/*******************************************************************************
* Copyright (C) 2012 Dominik Jain.
*
* This file is part of ProbCog.
*
* ProbCog 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.
*
* ProbCog 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 ProbCog. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package probcog.wcsp;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
/**
* Represents a WCSP constraint.
* @author Dominik Jain
*/
public class Constraint {
protected HashMap<ArrayKey, Tuple> tuples;
/**
* array of variable indices encompassed by this constraint
*/
protected int[] varIndices;
protected long defaultCost;
public Constraint(long defaultCost, int[] varIndices, int initialTuples) {
this.varIndices = varIndices;
this.defaultCost = defaultCost;
tuples = new HashMap<ArrayKey, Tuple>(initialTuples);
}
public void addTuple(int[] domainIndices, long cost) {
tuples.put(new ArrayKey(domainIndices), new Tuple(domainIndices, cost));
}
public void addTuple(Tuple t) {
tuples.put(new ArrayKey(t.domIndices), t);
}
public long getCost(int[] domainIndices) {
Tuple t = tuples.get(domainIndices);
if(t == null)
return defaultCost;
return t.cost;
}
public int[] getVarIndices() {
return varIndices;
}
public java.util.Collection<Tuple> getTuples() {
return tuples.values();
}
public Tuple getTuple(int[] setting) {
return tuples.get(new ArrayKey(setting));
}
public Tuple getTuple(ArrayKey k) {
return tuples.get(k);
}
public long getDefaultCosts() {
return defaultCost;
}
public void setDefaultCosts(long c) {
this.defaultCost = c;
}
/**
* @return the number of tuples in this constraint
*/
public int size() {
return tuples.size();
}
/**
* merge the contents of another constraint c2, whose domain and variable ordering is the same, into this constraint
* @param c2 the constraint to merge into this. The constraint should no longer be used after being passed to this
* method, as its contents are modified.
*/
public void merge(Constraint c2) {
long c2defaultCosts = c2.getDefaultCosts();
boolean c1TupleUpdateRequired = c2defaultCosts != 0L;
// add contents of c2's tuples to c1
HashSet<Tuple> processedTuples = c1TupleUpdateRequired ? new HashSet<Tuple>(c2.size()) : null;
for(Tuple t2 : c2.getTuples()) {
// unify with corresponding tuple
Tuple t1 = getTuple(t2.domIndices);
if(t1 != null) {
t1.cost += t2.cost;
if(c1TupleUpdateRequired) processedTuples.add(t1);
}
else {
t2.cost += this.defaultCost;
addTuple(t2);
if(c1TupleUpdateRequired) processedTuples.add(t2);
}
}
// if c2 has relevant default costs...
if(c1TupleUpdateRequired) {
// add c2's default costs to tuples found in c1 but not in c2
for(Tuple t1 : getTuples()) {
if(!processedTuples.contains(t1)) {
t1.cost += c2defaultCosts;
}
}
// update the default costs
setDefaultCosts(defaultCost + c2defaultCosts);
}
}
/**
* merges the contents of c2, which is assumed to have the same domain but not necessarily the same variable ordering,
* into this constraint
* @param c2 the constraint to merge into this. The constraint should no longer be used after being passed to this
* method, as its contents are modified.
*/
public void mergeReorder(Constraint c2) {
HashMap<Integer,Integer> varIdx2arrayIdx = new HashMap<Integer, Integer>();
int[] c2varIndices = c2.varIndices;
boolean sameOrder = true;
for(int k = 0; k < varIndices.length; k++) {
varIdx2arrayIdx.put(varIndices[k], k);
sameOrder = sameOrder && varIndices[k] == c2varIndices[k];
}
// if the order isn't the same, reorder all of c2's tuples
if(!sameOrder) {
for(Tuple t2 : c2.getTuples()) {
int[] domIndices = new int[t2.domIndices.length];
for(int k = 0; k < varIndices.length; k++)
domIndices[varIdx2arrayIdx.get(c2varIndices[k])] = t2.domIndices[k];
t2.domIndices = domIndices;
}
}
// do the actual merge
merge(c2);
}
public void writeWCSP(PrintStream out) {
// first line of the constraint: arity of the constraint followed by indices of the variables, default costs, and number of constraint lines
out.print(varIndices.length);
out.print(' ');
for(int varIdx : varIndices) {
out.print(varIdx);
out.print(' ');
}
out.print(defaultCost);
out.print(' ');
out.println(tuples.size());
// actual constraint tuples
for(Entry<ArrayKey, Tuple> e : tuples.entrySet()) {
Tuple t = e.getValue();
for(int domIdx : t.domIndices) {
out.print(domIdx);
out.print(' ');
}
out.println(t.cost);
}
}
protected static class Tuple {
public int[] domIndices;
public long cost;
public Tuple(int[] domIndices, long cost) {
this.cost = cost;
this.domIndices = domIndices;
}
/**
* @param c the constraint for which the check is made
* @param partialAssignment
* @return true if under the given partial assignment, the tuple could apply (i.e. the assignment
* includes only assignments that are also made in this tuple)
*/
public boolean couldApply(Constraint c, java.util.Map<Integer,Integer> partialAssignment) {
for(int i = 0; i < c.varIndices.length; i++) {
Integer a = partialAssignment.get(c.varIndices[i]);
if(a != null && domIndices[i] != a)
return false;
}
return true;
}
}
public static final class ArrayKey {
protected int[] array;
protected int hashCode;
public ArrayKey(int[] domIndices) {
this.array = domIndices;
this.hashCode = Arrays.hashCode(array);
}
@Override
public int hashCode() {
return hashCode;
}
public boolean equals(Object o) {
if(!(o instanceof ArrayKey))
return false;
return Arrays.equals(this.array, ((ArrayKey)o).array);
}
}
}