/******************************************************************************* * Copyright 2014 Felipe Takiyama * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package br.usp.poli.takiyama.prv; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import br.usp.poli.takiyama.common.Constraint; import br.usp.poli.takiyama.common.InequalityConstraint; import br.usp.poli.takiyama.utils.MathUtils; /** * Definition by [Kisynski, 2010]: * <p> * A counting formula is of the form #<sub>A:C<sub>A</sub></sub>[f(...,A,...)], * where: * <li> A is a logical variable that is bound by the # sign * <li> C is a set of inequality constraints involving A * <li> f(...,A,...) is a parameterized random variable. * </p> * <p> * The value of #<sub>A:C<sub>A</sub></sub>[f(...,A,...)], given an assignment * of values to random variables v, is the histogram function * h<sup>v</sup> : range(f) → N defined by * v(#<sub>A:C<sub>A</sub></sub>[f(...,A,...)]) = h<sup>v</sup>(x) = * |{a ∈ (D(A):C) : v(f(...,a,...)) = x}|, * where x is in the range of f. * </p> * * @author Felipe Takiyama * */ public final class CountingFormula implements Prv { private final LogicalVariable bound; private final Set<Constraint> constraints; private final Prv prv; //private final List<Histogram<? extends RangeElement>> range; private final List<Histogram<RangeElement>> range; /* ************************************************************************ * Constructors * ************************************************************************/ /** * Creates a counting formula * * @param boundVariable The logical variable bound to this counting * formula (that is, the variable being counted) * @param constraints A set of constraints involving the bound logical * variable * @param prv The parameterized random variable associated with this * counting formula. * * @throws IllegalArgumentException If the set of constraints contains * a constraint not involving the bound logical variable <b>or</b> if the * specified logical variable is not a parameter of the specified * parameterized random variable * @throws IllegalStateException if the set of constraints is too * restrictive to create a PRV. */ private CountingFormula(LogicalVariable bound, Set<Constraint> constraints, Prv prv) throws IllegalArgumentException, IllegalStateException { this.prv = StdPrv.getInstance(prv); this.bound = StdLogicalVariable.getInstance(bound); this.constraints = new HashSet<Constraint>(constraints); this.range = new ArrayList<Histogram<RangeElement>>(); if (!prv.contains(bound)) { throw new IllegalArgumentException(); } if (this.bound.individualsSatisfying(this.constraints).size() == 0) { throw new IllegalStateException(); } for (Constraint c : constraints) { if (!c.contains(bound)) { throw new IllegalArgumentException(); } } int allowedDomainSize = this.bound.numberOfIndividualsSatisfying(constraints); Histogram<RangeElement> histogram = new Histogram<RangeElement>(prv.range()); generateHistograms(this.range, allowedDomainSize, histogram, 0); } /** * Generates all possible histograms for this counting formula. All * histograms are put in the specified list. * <p> * This function is recursive, and behaves well for histograms with * short ranges (for instance, binary ranges). Performance suffers * exponentially as the range grows. * </p> * <p> * Initially, * <li><code>allHistograms</code> must be an empty list, which will * contain all histograms at the end of the execution of this function. * <li><code>maxCount</code> is the maximum count a bucket can hold, and must * be calculated beforehand considering bound logical variable population * size and constraints involving this logical variable. * <li><code>histogram</code> is an empty histogram. * <li><code>currentBucket</code> is 0. * </p> * <br> * * @param allHistograms The set of all histograms. * @param maxCount The current maximum count * @param histogram The current histogram being built * @param currentBucket The current bucket index */ private void generateHistograms(List<Histogram<RangeElement>> allHistograms, int maxCount, Histogram<RangeElement> histogram, int currentBucket) { if (currentBucket == histogram.size() - 1 || maxCount == 0) { histogram.setCount(prv.range().get(currentBucket), maxCount); allHistograms.add(new Histogram<RangeElement>(histogram)); return; } int count = maxCount; while (count >= 0) { histogram.setCount(prv.range().get(currentBucket), count); generateHistograms(allHistograms, maxCount - count, histogram, currentBucket + 1); count--; } } /* ************************************************************************ * Static factories * ************************************************************************/ /** * Returns a counting formula. * * @param boundVariable The logical variable bound to this counting * formula (that is, the variable being counted) * @param constraints A set of constraints involving the bound logical * variable * @param prv The parameterized random variable associated with this * counting formula. * @return A counting formula * * @throws IllegalArgumentException If the set of constraints contains * a constraint not involving the bound logical variable <b>or</b> if the * specified logical variable is not a parameter of the specified * parameterized random variable. */ public static CountingFormula getInstance(LogicalVariable bound, Prv prv, Set<Constraint> constraints) throws IllegalArgumentException { return new CountingFormula(bound, constraints, prv); } /** * Returns a counting formula without constraints. * * @param boundVariable The logical variable bound to this counting * formula (that is, the variable being counted) * @param prv The parameterized random variable associated with this * counting formula. * @return A counting formula * * @throws IllegalArgumentException If the * specified logical variable is not a parameter of the specified * parameterized random variable. */ public static CountingFormula getInstance(LogicalVariable bound, Prv prv) throws IllegalArgumentException { Set<Constraint> constraints = new HashSet<Constraint>(0); return new CountingFormula(bound, constraints, prv); } /** * Returns a counting formula with one constraint. * * @param boundVariable The logical variable bound to this counting * formula (that is, the variable being counted) * @param prv The parameterized random variable associated with this * counting formula. * @param constraint A constraint involving the bound logical * variable * @return A counting formula * * @throws IllegalArgumentException If the * specified logical variable is not a parameter of the specified * parameterized random variable <b>or</b> if the constraint does not * involve the bound logical variable */ public static CountingFormula getInstance(LogicalVariable bound, Prv prv, Constraint constraint) throws IllegalArgumentException { Set<Constraint> constraints = new HashSet<Constraint>(1); constraints.add(constraint); return new CountingFormula(bound, constraints, prv); } /* ************************************************************************ * Getters * ************************************************************************/ @Override public Set<Constraint> constraints() { return new HashSet<Constraint>(constraints); } /** * Returns the name of the PRV associated with this counting formula. */ @Override public String name() { return prv.name(); } @Override public List<LogicalVariable> parameters() { List<LogicalVariable> param = new ArrayList<LogicalVariable>(prv.parameters()); param.remove(bound); return param; } @Override public List<Term> terms() { // List<Term> terms = new ArrayList<Term>(prv.terms()); // terms.remove(bound); // return terms; /* * This list includes the bound variable (let's call it A). * It is not correct to return A as a term or parameter. Why? * Because when you have a counting formula #.A:C [f(...A...)] you * are actually representing all PRVs f(...a...) where 'a' is a * constant from the set D(A):C. Thus the correct term to return * would be 'a', which is not a logical variable nor a constant. It * represents a constant without being one. */ return prv.terms(); } @Override public LogicalVariable boundVariable() { return StdLogicalVariable.getInstance(bound); } @Override public int groundSetSize(Set<Constraint> constraints) { int size = 1; for (LogicalVariable v : parameters()) { size = size * v.individualsSatisfying(constraints).size(); } return size; } @Override public List<RangeElement> range() { return new ArrayList<RangeElement>(range); } /** * Returns the size of the range of the PRV associated with this * counting formula. * * @return the size of the range of the PRV associated with this * counting formula. */ public int prvRangeSize() { return prv.range().size(); } @Override public boolean contains(Term t) { return parameters().contains(t); } /** * Returns the count of <code>bucket</code> for the specified * {@link Histogram}. If the specified histogram is not a {@link Histogram} * or it is not in the range of this counting formula, returns -1. * * @param histogram The histogram to analyze * @param bucket The bucket from which the counting must be done * @return The count for the specified bucket in the specified histogram, * or -1 when there is no such bucket or histogram. */ public int getCount(RangeElement histogram, RangeElement bucket) { int count = -1; if (range.contains(histogram)) { int hIndex = range.indexOf(histogram); count = range.get(hIndex).getCount(bucket); } return count; } /** * Returns <code>true</code> if the counting formula can be converted to a * standard parameterized random variable, <code>false</code> otherwise. * <p> * A counting formula can be converted to standard parameterized random * variable when the set of constraints is big enough to restrict the * bound logical variable to a single individual. * </p> * * @return <code>true</code> if the counting formula can be converted to a * standard parameterized random variable, <code>false</code> otherwise. */ public boolean isStdPrv() { return (bound.individualsSatisfying(constraints).size() == 1); } /** * Returns the value of the multinomial defined by the values in buckets * from specified histogram. */ @Override public BigDecimal getSumOutCorrection(RangeElement e) { @SuppressWarnings("unchecked") Histogram<RangeElement> h = (Histogram<RangeElement>) e; return new BigDecimal(MathUtils.multinomial(h.toMultinomial())); } @Override public boolean isEquivalentTo(RandomVariableSet s) { return s.prv().equals(prv) && s.constraints().equals(constraints); } @Override public Prv getCanonicalForm() { return prv.getCanonicalForm(); } /* ************************************************************************ * Setters * ************************************************************************/ /** * Adds a constraint to this counting formula. This method returns a new * instance of the counting formula. * * @param constraint The constraint to be added to the counting formula. * @return A new counting formula equal to this one with the addition of * the constraint specified. */ private CountingFormula add(Constraint constraint) { HashSet<Constraint> constraints = new HashSet<Constraint>(this.constraints); constraints.add(constraint); return CountingFormula.getInstance(bound, prv, constraints); } /** * Returns this counting formula with the specified term removed. The * remotion is done by adding a {@link InequalityConstraint} involving * the bound logical variable from this counting formula and the * specified term to the set of constraints. * * @param t The term to "remove" * @return This counting formula with the specified term removed */ public Prv remove(Term t) { Constraint constraintOnTerm = InequalityConstraint.getInstance(bound, t); CountingFormula result = add(constraintOnTerm); return result; } /** * Returns the {@link Prv} associated with this counting formula with * the the bound logical variable replaced by the specified term. * * @param t The replacement for the bound logical variable * @return the {@link Prv} associated with this counting formula with * the the bound logical variable replaced by the specified term. */ public Prv takeOut(Term t) { Binding b = Binding.getInstance(bound, t); Substitution s = Substitution.getInstance(b); return prv.apply(s); } /** * Returns this counting formula converted to {@link StdPrv}, if this * conversion is possible. Otherwise, returns this counting formula. * * @return This counting formula converted to {@link StdPrv}, if this * conversion is possible. Otherwise, returns this counting formula. */ public Prv simplify() { return (this.isStdPrv()) ? this.toStdPrv() : this; } @Override public Prv apply(Substitution s) { Prv substituted = StdPrv.getInstance(prv); Set<Constraint> constraints = new HashSet<Constraint>(this.constraints); LogicalVariable boundLv = StdLogicalVariable.getInstance(bound); for (Iterator<LogicalVariable> it = s.getSubstitutedIterator(); it.hasNext(); ) { LogicalVariable toReplace = it.next(); Term replacement = s.getReplacement(toReplace); // Cannot substitute bound logical variable with constant if ((!boundLv.equals(toReplace)) || replacement.isVariable()) { Substitution sub = Substitution.getInstance(Binding.getInstance(toReplace, replacement)); substituted = substituted.apply(sub); constraints = apply(sub, constraints); if (boundLv.equals(toReplace)) { boundLv = StdLogicalVariable.getInstance((LogicalVariable) replacement); } } } return CountingFormula.getInstance(boundLv, (StdPrv) substituted, constraints); } /** * Returns the result of applying a substitution to the parameterized * random variable of this counting formula. * * @param s The substitution to be made. * @return The parameterized random variable associated with this * counting formula with the specified substitution applied. */ public Prv applyToPrv(Substitution s) { return prv.apply(s); } /** * Returns the set of constraints that result from applying the * specified substitution to the specified set of constraints. * @param s * @param constraints * @return */ private Set<Constraint> apply(Substitution s, Set<Constraint> constraints) { Set<Constraint> substituted = new HashSet<Constraint>(constraints.size()); for (Constraint c : constraints) { try { Constraint newConstraint = c.apply(s); substituted.add(newConstraint); } catch (IllegalArgumentException e) { // Illegal constraint does not get added } } return substituted; } /** * Adds the specified amount to the bucket of the specified histogram. * <p> * This method does not modify this counting formula, all operations are * made on copies of elements from this counting formula. * </p> * * @param hIndex The index of the histogram in the range of this counting * formula * @param e The bucket where addition will be made * @param n The amount to add * @return The histogram with the specified amount added to the specified * bucket. */ public RangeElement increaseCount(int hIndex, RangeElement e, int n) { Histogram<RangeElement> hist = new Histogram<RangeElement>(range.get(hIndex)); hist.addCount(e, n); return hist; } @Override public Prv rename(String name) { throw new UnsupportedOperationException("Not implemented!"); } /** * Converts this counting formula to a standard parameterized random * variable when its bound logical variable is constrained to a single * individual. * <p> * If conversion is not possible, returns this counting formula. * </p> * * @return This counting formula converted to a standard parameterized * random variable. */ public Prv toStdPrv() { Population constrainedIndividuals = bound.individualsSatisfying(constraints); Prv result; if (constrainedIndividuals.size() != 1) { result = this; } else { Constant loneIndividual = constrainedIndividuals.iterator().next(); Binding b = Binding.getInstance(bound, loneIndividual); Substitution s = Substitution.getInstance(b); result = prv.apply(s); } return result; } /* ************************************************************************ * hashCode, equals and toString * ************************************************************************/ @Override public String toString() { return "#." + bound.toString() + ":" + constraints.toString() + " [ " + prv.toString() + " ]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((bound == null) ? 0 : bound.hashCode()); result = prime * result + ((constraints == null) ? 0 : constraints.hashCode()); result = prime * result + ((prv == null) ? 0 : prv.hashCode()); result = prime * result + ((range == null) ? 0 : range.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof CountingFormula)) { return false; } CountingFormula other = (CountingFormula) obj; if (bound == null) { if (other.bound != null) { return false; } } else if (!bound.equals(other.bound)) { return false; } if (constraints == null) { if (other.constraints != null) { return false; } } else if (!constraints.equals(other.constraints)) { return false; } if (prv == null) { if (other.prv != null) { return false; } } else if (!prv.equals(other.prv)) { return false; } if (range == null) { if (other.range != null) { return false; } } else if (!range.equals(other.range)) { return false; } return true; } }