aima.core.probability.util.ProbUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aima-core Show documentation
Show all versions of aima-core Show documentation
AIMA-Java Core Algorithms from the book Artificial Intelligence a Modern Approach 3rd Ed.
package aima.core.probability.util;
import java.util.Map;
import aima.core.probability.CategoricalDistribution;
import aima.core.probability.RandomVariable;
import aima.core.probability.bayes.Node;
import aima.core.probability.domain.FiniteDomain;
import aima.core.probability.proposition.ConjunctiveProposition;
import aima.core.probability.proposition.Proposition;
import aima.core.util.Randomizer;
import aima.core.util.Util;
import aima.core.util.math.MixedRadixNumber;
public class ProbUtil {
/**
* Check if name provided is valid for use as the name of a RandomVariable.
*
* @param name
* proposed for the RandomVariable.
* @throws IllegalArgumentException
* if not a valid RandomVariable name.
*/
public static void checkValidRandomVariableName(String name)
throws IllegalArgumentException {
if (null == name || name.trim().length() == 0
|| name.trim().length() != name.length() || name.contains(" ")) {
throw new IllegalArgumentException(
"Name of RandomVariable must be specified and contain no leading, trailing or embedded spaces.");
}
if (name.substring(0, 1).toLowerCase().equals(name.substring(0, 1))) {
throw new IllegalArgumentException(
"Name must start with a leading upper case letter.");
}
}
/**
* Calculated the expected size of a ProbabilityTable for the provided
* random variables.
*
* @param vars
* null, 0 or more random variables that are to be used to
* construct a CategoricalDistribution.
* @return the size (i.e. getValues().length) that the
* CategoricalDistribution will need to be in order to represent the
* specified random variables.
*
* @see CategoricalDistribution#getValues()
*/
public static int expectedSizeOfProbabilityTable(RandomVariable... vars) {
// initially 1, as this will represent constant assignments
// e.g. Dice1 = 1.
int expectedSizeOfDistribution = 1;
if (null != vars) {
for (RandomVariable rv : vars) {
// Create ordered domains for each variable
if (!(rv.getDomain() instanceof FiniteDomain)) {
throw new IllegalArgumentException(
"Cannot have an infinite domain for a variable in this calculation:"
+ rv);
}
FiniteDomain d = (FiniteDomain) rv.getDomain();
expectedSizeOfDistribution *= d.size();
}
}
return expectedSizeOfDistribution;
}
/**
* Calculated the expected size of a CategoricalDistribution for the
* provided random variables.
*
* @param vars
* null, 0 or more random variables that are to be used to
* construct a CategoricalDistribution.
* @return the size (i.e. getValues().length) that the
* CategoricalDistribution will need to be in order to represent the
* specified random variables.
*
* @see CategoricalDistribution#getValues()
*/
public static int expectedSizeOfCategoricalDistribution(
RandomVariable... vars) {
// Equivalent calculation
return expectedSizeOfProbabilityTable(vars);
}
/**
* Convenience method for ensure a conjunction of probabilistic
* propositions.
*
* @param props
* propositions to be combined into a ConjunctiveProposition if
* necessary.
* @return a ConjunctivePropositions if more than 1 proposition in 'props',
* otherwise props[0].
*/
public static Proposition constructConjunction(Proposition[] props) {
return constructConjunction(props, 0);
}
/**
*
* @param probabilityChoice
* a probability choice for the sample
* @param Xi
* a Random Variable with a finite domain from which a random
* sample is to be chosen based on the probability choice.
* @param distribution
* Xi's distribution.
* @return a Random Sample from Xi's domain.
*/
public static Object sample(double probabilityChoice, RandomVariable Xi,
double[] distribution) {
FiniteDomain fd = (FiniteDomain) Xi.getDomain();
if (fd.size() != distribution.length) {
throw new IllegalArgumentException("Size of domain Xi " + fd.size()
+ " is not equal to the size of the distribution "
+ distribution.length);
}
int i = 0;
double total = distribution[0];
while (probabilityChoice > total) {
i++;
total += distribution[i];
}
return fd.getValueAt(i);
}
/**
* Get a random sample from P(Xi | parents(Xi))
*
* @param Xi
* a Node from a Bayesian network for the Random Variable
* Xi.
* @param event
* comprising assignments for parents(Xi)
* @param r
* a Randomizer for generating a probability choice for the
* sample.
* @return a random sample from P(Xi |
* parents(Xi))
*/
public static Object randomSample(Node Xi,
Map event, Randomizer r) {
return Xi.getCPD().getSample(r.nextDouble(),
getEventValuesForParents(Xi, event));
}
/**
* Get a random sample from P(Xi | mb(Xi)),
* where mb(Xi) is the Markov Blanket of Xi. The
* probability of a variable given its Markov blanket is proportional to the
* probability of the variable given its parents times the probability of
* each child given its respective parents (see equation 14.12 pg. 538
* AIMA3e):
*
* P(x'i|mb(Xi)) =
* αP(x'i|parents(Xi)) *
* ∏Yj ∈ Children(Xi)
* P(yj|parents(Yj))
*
* @param Xi
* a Node from a Bayesian network for the Random Variable
* Xi.
* @param event
* comprising assignments for the Markov Blanket Xi.
* @param r
* a Randomizer for generating a probability choice for the
* sample.
* @return a random sample from P(Xi | mb(Xi))
*/
public static Object mbRandomSample(Node Xi,
Map event, Randomizer r) {
return sample(r.nextDouble(), Xi.getRandomVariable(),
mbDistribution(Xi, event));
}
/**
* Calculate the probability distribution for P(Xi |
* mb(Xi)), where mb(Xi) is the Markov Blanket of
* Xi. The probability of a variable given its Markov blanket is
* proportional to the probability of the variable given its parents times
* the probability of each child given its respective parents (see equation
* 14.12 pg. 538 AIMA3e):
*
* P(x'i|mb(Xi)) =
* αP(x'i|parents(Xi)) *
* ∏Yj ∈ Children(Xi)
* P(yj|parents(Yj))
*
* @param Xi
* a Node from a Bayesian network for the Random Variable
* Xi.
* @param event
* comprising assignments for the Markov Blanket Xi.
* @return a random sample from P(Xi | mb(Xi))
*/
public static double[] mbDistribution(Node Xi,
Map event) {
FiniteDomain fd = (FiniteDomain) Xi.getRandomVariable().getDomain();
double[] X = new double[fd.size()];
for (int i = 0; i < fd.size(); i++) {
// P(x'i|mb(Xi)) =
// αP(x'i|parents(Xi)) *
// ∏Yj ∈ Children(Xi)
// P(yj|parents(Yj))
double cprob = 1.0;
for (Node Yj : Xi.getChildren()) {
cprob *= Yj.getCPD().getValue(
getEventValuesForXiGivenParents(Yj, event));
}
X[i] = Xi.getCPD()
.getValue(
getEventValuesForXiGivenParents(Xi,
fd.getValueAt(i), event))
* cprob;
}
return Util.normalize(X);
}
/**
* Get the parent values for the Random Variable Xi from the provided event.
*
* @param Xi
* a Node for the Random Variable Xi whose parent values are to
* be extracted from the provided event in the correct order.
* @param event
* an event containing assignments for Xi's parents.
* @return an ordered set of values for the parents of Xi from the provided
* event.
*/
public static Object[] getEventValuesForParents(Node Xi,
Map event) {
Object[] parentValues = new Object[Xi.getParents().size()];
int i = 0;
for (Node pn : Xi.getParents()) {
parentValues[i] = event.get(pn.getRandomVariable());
i++;
}
return parentValues;
}
/**
* Get the values for the Random Variable Xi's parents and its own value
* from the provided event.
*
* @param Xi
* a Node for the Random Variable Xi whose parent values and
* value are to be extracted from the provided event in the
* correct order.
* @param event
* an event containing assignments for Xi's parents and its own
* value.
* @return an ordered set of values for the parents of Xi and its value from
* the provided event.
*/
public static Object[] getEventValuesForXiGivenParents(Node Xi,
Map event) {
return getEventValuesForXiGivenParents(Xi,
event.get(Xi.getRandomVariable()), event);
}
/**
* Get the values for the Random Variable Xi's parents and its own value
* from the provided event.
*
* @param Xi
* a Node for the Random Variable Xi whose parent values are to
* be extracted from the provided event in the correct order.
* @param xDelta
* the value for the Random Variable Xi to be assigned to the
* values returned.
* @param event
* an event containing assignments for Xi's parents and its own
* value.
* @return an ordered set of values for the parents of Xi and its value from
* the provided event.
*/
public static Object[] getEventValuesForXiGivenParents(Node Xi,
Object xDelta, Map event) {
Object[] values = new Object[Xi.getParents().size() + 1];
int idx = 0;
for (Node pn : Xi.getParents()) {
values[idx] = event.get(pn.getRandomVariable());
idx++;
}
values[idx] = xDelta;
return values;
}
/**
* Calculate the index into a vector representing the enumeration of the
* value assignments for the variables X and their corresponding assignment
* in x. For example the Random Variables:
* Q::{true, false}, R::{'A', 'B','C'}, and T::{true, false}, would be
* enumerated in a Vector as follows:
*
*
* Index Q R T
* ----- - - -
* 00: true, A, true
* 01: true, A, false
* 02: true, B, true
* 03: true, B, false
* 04: true, C, true
* 05: true, C, false
* 06: false, A, true
* 07: false, A, false
* 08: false, B, true
* 09: false, B, false
* 10: false, C, true
* 11: false, C, false
*
*
* if x = {Q=true, R='C', T=false} the index returned would be 5.
*
* @param X
* a list of the Random Variables that would comprise the vector.
* @param x
* an assignment for the Random Variables in X.
* @return an index into a vector that would represent the enumeration of
* the values for X.
*/
public static int indexOf(RandomVariable[] X, Map x) {
if (0 == X.length) {
return ((FiniteDomain) X[0].getDomain()).getOffset(x.get(X[0]));
}
// X.length > 1 then calculate using a mixed radix number
//
// Note: Create radices in reverse order so that the enumeration
// through the distributions is of the following
// order using a MixedRadixNumber, e.g. for two Booleans:
// X Y
// true true
// true false
// false true
// false false
// which corresponds with how displayed in book.
int[] radixValues = new int[X.length];
int[] radices = new int[X.length];
int j = X.length - 1;
for (int i = 0; i < X.length; i++) {
FiniteDomain fd = (FiniteDomain) X[i].getDomain();
radixValues[j] = fd.getOffset(x.get(X[i]));
radices[j] = fd.size();
j--;
}
return new MixedRadixNumber(radixValues, radices).intValue();
}
/**
* Calculate the indexes for X[i] into a vector representing the enumeration
* of the value assignments for the variables X and their corresponding
* assignment in x. For example the Random Variables:
* Q::{true, false}, R::{'A', 'B','C'}, and T::{true, false}, would be
* enumerated in a Vector as follows:
*
*
* Index Q R T
* ----- - - -
* 00: true, A, true
* 01: true, A, false
* 02: true, B, true
* 03: true, B, false
* 04: true, C, true
* 05: true, C, false
* 06: false, A, true
* 07: false, A, false
* 08: false, B, true
* 09: false, B, false
* 10: false, C, true
* 11: false, C, false
*
*
* if X[i] = R and x = {..., R='C', ...} then the indexes returned would be
* [4, 5, 10, 11].
*
* @param X
* a list of the Random Variables that would comprise the vector.
* @param idx
* the index into X for the Random Variable whose assignment we
* wish to retrieve its indexes for.
* @param x
* an assignment for the Random Variables in X.
* @return the indexes into a vector that would represent the enumeration of
* the values for X[i] in x.
*/
public static int[] indexesOfValue(RandomVariable[] X, int idx,
Map x) {
int csize = ProbUtil.expectedSizeOfCategoricalDistribution(X);
FiniteDomain fd = (FiniteDomain) X[idx].getDomain();
int vdoffset = fd.getOffset(x.get(X[idx]));
int vdosize = fd.size();
int[] indexes = new int[csize / vdosize];
int blocksize = csize;
for (int i = 0; i < X.length; i++) {
blocksize = blocksize / X[i].getDomain().size();
if (i == idx) {
break;
}
}
for (int i = 0; i < indexes.length; i += blocksize) {
int offset = ((i / blocksize) * vdosize * blocksize)
+ (blocksize * vdoffset);
for (int b = 0; b < blocksize; b++) {
indexes[i + b] = offset + b;
}
}
return indexes;
}
//
// PRIVATE METHODS
//
private static Proposition constructConjunction(Proposition[] props, int idx) {
if ((idx + 1) == props.length) {
return props[idx];
}
return new ConjunctiveProposition(props[idx], constructConjunction(
props, idx + 1));
}
}