/**
* Copyright (c) 2012 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.factor;
import org.eclipse.recommenders.jayes.util.MathUtils;
/**
* represents a cut through a decision tree as described in
* "Static and dynamic speedup techniques for the Junction Tree Algorithm" (Kutschke, 2011) (my Bachelor's thesis).
* Serves for exactly selecting those entries in the probability matrix of a factor that are relevant. It does so in
* linear time and space, to the number of dimensions of a factor.
*/
public class Cut implements Cloneable {
private final AbstractFactor factor;
private int start;
private int stepSize;
private int length;
private int subtreeStepsize;
private int rootDimension;
private int leafDimension;
// the subtree(s); only one because of the inherent regularities of the
// decision tree
private Cut subCut;
public Cut(AbstractFactor factor) {
this.factor = factor;
}
public void initialize() {
length = MathUtils.product(this.factor.getDimensions());
start = 0;
stepSize = 1;
rootDimension = 0;
if (length > 1) {
subtreeStepsize = length / this.factor.getDimensions()[0];
} else { // treat zero-dimensional factors specially
subtreeStepsize = 0;
}
leafDimension = this.factor.getDimensions().length - 1;
subCut = null;
leafCut();
rootCut();
createSubcut();
}
@Override
public Cut clone() {
try {
return (Cut) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
private void rootCut() {
while (rootDimension < leafDimension && this.factor.selections[rootDimension] != -1) {
descendSelectedDimension();
}
while (rootDimension < leafDimension && this.factor.selections[rootDimension + 1] == -1) {
descendUnselectedDimension();
}
}
private void descendSelectedDimension() {
length /= this.factor.dimensions[rootDimension];
start += subtreeStepsize * this.factor.selections[rootDimension];
descendUnselectedDimension();
}
private void descendUnselectedDimension() {
rootDimension++;
subtreeStepsize /= this.factor.dimensions[rootDimension];
}
private void leafCut() {
while (leafDimension >= 0 && this.factor.selections[leafDimension] != -1) {
ascendSelectedDimension();
}
}
private void ascendSelectedDimension() {
start += this.factor.selections[leafDimension] * stepSize;
stepSize *= this.factor.dimensions[leafDimension];
leafDimension--;
}
private void createSubcut() {
if (needsSplit()) {
subCut = null; // avoid circularity in object graph
subCut = clone();
subCut.descendUnselectedDimension();
subCut.length = subtreeStepsize;
subCut.rootCut(); // no leaf cut
subCut.createSubcut();
}
}
/**
* the Cut needs to further split if and only if there is an additional selection between root and leaf
*
* @return
*/
private boolean needsSplit() {
if (length < subtreeStepsize) {
return false;
}
for (int i = rootDimension; i < leafDimension; i++) {
if (this.factor.selections[i] != -1) {
return true;
}
}
return false;
}
public int getStart() {
return start;
}
public int getEnd() {
return start + length;
}
public int getStepSize() {
return stepSize;
}
public int getLength() {
return length;
}
public int getSubtreeStepsize() {
return subtreeStepsize;
}
public Cut getSubCut() {
return subCut;
}
}