/**
* Copyright (c) 2011 Michael Kutschke.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michael Kutschke - initial API and implementation.
*/
package org.eclipse.recommenders.jayes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.recommenders.internal.jayes.util.BidirectionalMap;
import org.eclipse.recommenders.jayes.factor.AbstractFactor;
import org.eclipse.recommenders.jayes.factor.DenseFactor;
import org.eclipse.recommenders.jayes.factor.arraywrapper.DoubleArrayWrapper;
import org.eclipse.recommenders.jayes.util.MathUtils;
public class BayesNode {
private final String name;
private final List<BayesNode> children = new ArrayList<BayesNode>();
private final List<BayesNode> parents = new ArrayList<BayesNode>();
private int outcomes = 0;
private final BidirectionalMap<String, Integer> outcomeIndices = new BidirectionalMap<String, Integer>();
private final AbstractFactor factor = new DenseFactor();
private int id = -1;
private final List<String> outcomesList = new ArrayList<String>();
/**
* @deprecated use {@link BayesNet#createNode(String) BayesNet.createNode} instead
*/
@Deprecated
public BayesNode(String name) {
if (name == null) {
throw new NullPointerException("A BayesNode's name may not be null");
}
this.name = name;
}
/**
* Must be called after the parents and outcomes, and the outcome of the parents are set.
*/
public void setProbabilities(final double... probabilities) {
adjustFactordimensions();
if (probabilities.length != MathUtils.product(factor.getDimensions())) {
throw new IllegalArgumentException("Probability table does not have expected size. Expected: "
+ MathUtils.product(factor.getDimensions()) + "but got: " + probabilities.length);
}
factor.setValues(new DoubleArrayWrapper(probabilities));
}
public double[] getProbabilities() {
return factor.getValues().toDoubleArray();
}
public List<BayesNode> getChildren() {
return children;
}
public List<BayesNode> getParents() {
return Collections.unmodifiableList(parents);
}
public void setParents(final List<BayesNode> parents) {
if (parents.contains(null)) {
throw new NullPointerException("found null in parents - null is not a valid BayesNode");
}
for (BayesNode oldParent : this.parents) {
oldParent.children.remove(this);
}
this.parents.clear();
this.parents.addAll(parents);
for (BayesNode p : parents) {
p.children.add(this);
}
adjustFactordimensions();
}
private void adjustFactordimensions() {
final int[] dimensions = new int[parents.size() + 1];
final int[] dimensionIds = new int[parents.size() + 1];
fillWithParentDimensions(dimensions, dimensionIds);
insertSelf(dimensions, dimensionIds);
factor.setDimensions(dimensions);
factor.setDimensionIDs(dimensionIds);
}
private void insertSelf(final int[] dimensions, final int[] dimensionIds) {
dimensions[dimensions.length - 1] = getOutcomeCount();
dimensionIds[dimensionIds.length - 1] = getId();
}
private void fillWithParentDimensions(final int[] dimensions, final int[] dimensionIds) {
for (ListIterator<BayesNode> it = parents.listIterator(); it.hasNext();) {
final BayesNode p = it.next();
dimensions[it.nextIndex() - 1] = p.getOutcomeCount();
dimensionIds[it.nextIndex() - 1] = p.getId();
}
}
/**
* Marginalizes the conditional distribution with regard to the evidence. This only corresponds to any meaningful
* probability distribution if all parents are assigned a probability. This method is mostly used for sampling
* purposes.
*
*/
@Deprecated
public double[] marginalize(final Map<BayesNode, String> evidence) {
for (final BayesNode p : parents) {
if (evidence.containsKey(p)) {
getFactor().select(p.getId(), p.getOutcomeIndex(evidence.get(p)));
} else {
getFactor().select(p.getId(), -1);
}
}
final double[] result = MathUtils.normalize(factor.marginalizeAllBut(-1));
factor.resetSelections();
return result;
}
public int getId() {
return id;
}
/**
* @deprecated internal method, don't use. visibility might change to default
*/
@Deprecated
public void setId(final int id) {
if (this.id != -1) {
throw new IllegalStateException("Impossible to reset Id!");
}
if (id < 0) {
throw new IllegalArgumentException("id has to be greater or equal to 0");
}
this.id = id;
}
public void addOutcomes(String... names) {
if (!Collections.disjoint(outcomesList, Arrays.asList(names))) {
throw new IllegalArgumentException("Outcome already exists");
}
for (String name : names) {
outcomeIndices.put(name, outcomes);
outcomes++;
outcomesList.add(name);
}
adjustFactordimensions();
}
public int addOutcome(final String name) {
addOutcomes(name);
return outcomes - 1;
}
public int getOutcomeIndex(final String name) {
try {
return outcomeIndices.get(name);
} catch (NullPointerException ex) {
throw new IllegalArgumentException(name, ex);
}
}
public String getOutcomeName(final int index) {
return outcomeIndices.getKey(index);
}
public int getOutcomeCount() {
return outcomes;
}
public AbstractFactor getFactor() {
return factor;
}
public List<String> getOutcomes() {
return Collections.unmodifiableList(outcomesList);
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}