sadl.models.pdrta.StateStatistic.java Source code

Java tutorial

Introduction

Here is the source code for sadl.models.pdrta.StateStatistic.java

Source

/**
 * This file is part of SADL, a library for learning all sorts of (timed) automata and performing sequence-based anomaly detection.
 * Copyright (C) 2013-2016  the original author or authors.
 *
 * SADL 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.
 *
 * SADL 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 SADL.  If not, see <http://www.gnu.org/licenses/>.
 */
package sadl.models.pdrta;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.NavigableMap;

import com.google.common.collect.Multimap;

import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectDoubleHashMap;
import sadl.modellearner.rtiplus.SimplePDRTALearner;
import sadl.modellearner.rtiplus.tester.LikelihoodValue;

/**
 * This class manages the time and symbol probabilities for a {@link PDRTAState} which are needed for calculating the Likelihood Ratio Test and anomaly
 * detection. It also provides static methods for calculating the Likelihood Ratio.
 * 
 * @author Fabian Witter
 * 
 */
public class StateStatistic implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * Contains the sizes (width) of the histogram bins
     */
    private int[] histBarSizes;

    /**
     * Contains the number of outgoing {@link TimedTail}s for each histogram bin while training
     */
    private int[] timeCount;

    /**
     * Contains the number of outgoing {@link TimedTail}s for each symbol while training
     */
    private int[] symbolCount;

    /**
     * Contains the total number of outgoing {@link TimedTail}s while training
     */
    private int totalOutCount;

    /**
     * Contains the number of incoming {@link TimedTail}s while training
     */
    private int totalInCount;

    /**
     * Determines if this {@link StateStatistic} exists while training or during anomaly detection
     */
    private boolean trainMode;

    /**
     * Contains the probabilities for outgoing {@link TimedTail}s for each histogram bin during anomaly detection
     */
    private double[] timeProbs;

    /**
     * Contains the probabilities for outgoing {@link TimedTail}s for each symbol during anomaly detection
     */
    private double[] symbolProbs;

    /**
     * Contains the probability for incoming {@link TimedTail}s to end in the {@link PDRTAState} during anomaly detection
     */
    private double tailEndProb;

    /**
     * Contains the probabilities for transitions to be used by {@link TimedTail}s during anomaly detection
     */
    private TIntObjectHashMap<TObjectDoubleHashMap<Interval>> intervalProbs;

    /**
     * Creates an initial {@link StateStatistic} for training
     * 
     * @param numSymbols
     *            The number of symbols from the input set
     * @param histoBarSizes
     *            The sizes of the histogram bins
     * @return An initial {@link StateStatistic} for training
     */
    protected static StateStatistic initStat(int numSymbols, int[] histoBarSizes) {

        return new StateStatistic(numSymbols, histoBarSizes);
    }

    /**
     * Creates a {@link StateStatistic} reconstructed from a already trained and persisted {@link PDRTA}
     * 
     * @param alphSize
     *            The number of symbols
     * @param histoBarSizes
     *            The sizes of the histogram bins
     * @param stats
     *            A set containing the probabilities embedded in formated {@link String}s
     * @return A {@link StateStatistic} reconstructed from a already trained and persisted {@link PDRTA}
     */
    protected static StateStatistic reconstructStat(int alphSize, int[] histoBarSizes, Collection<String> stats) {

        double tailEndProb = 0.0;
        double[] timeProbs = new double[0];
        double[] symbolProbs = new double[0];

        assert (stats.size() == 3);
        for (String s : stats) {
            if (s.matches("^\\d+ T.+")) {
                // Sample: 0 TIME 0.0 / 0.0 / 0.0
                final String[] p = s.split(" ", 3)[2].split(" / ");
                timeProbs = new double[p.length];
                for (int i = 0; i < p.length; i++) {
                    try {
                        timeProbs[i] = Double.parseDouble(p[i]);
                    } catch (final NumberFormatException e) {
                        e.printStackTrace();
                    }
                }
                if (timeProbs.length != histoBarSizes.length) {
                    throw new IllegalArgumentException("Content is not correct!");
                }
            } else if (s.matches("^\\d+ S.+")) {
                // Sample: 0 SYM 0.0 / 0.0 / 0.0
                final String[] p = s.split(" ", 3)[2].split(" / ");
                symbolProbs = new double[p.length];
                for (int i = 0; i < p.length; i++) {
                    try {
                        symbolProbs[i] = Double.parseDouble(p[i]);
                    } catch (final NumberFormatException e) {
                        e.printStackTrace();
                    }
                }
                if (symbolProbs.length != alphSize) {
                    throw new IllegalArgumentException("Content is not correct!");
                }
            } else if (s.matches("^\\d+ \\[.+")) {
                // Sample: 0 [ xlabel = "0.0", fillcolor = "#FFA9A9" ];
                s = s.split("\"", 3)[1];
                try {
                    tailEndProb = Double.parseDouble(s);
                } catch (final NumberFormatException e) {
                    e.printStackTrace();
                }
            }
        }

        return new StateStatistic(histoBarSizes, timeProbs, symbolProbs, tailEndProb);
    }

    /**
     * Calculates the {@link LikelihoodValue} of the symbol distributions for merging two {@link PDRTAState}s
     * 
     * @param s1
     *            First {@link PDRTAState} for merging
     * @param s2
     *            Second {@link PDRTAState} for merging
     * @return The {@link LikelihoodValue} of the symbol distributions for merging
     */
    public static LikelihoodValue getLikelihoodRatioSym(PDRTAState s1, PDRTAState s2, boolean advancedPooling,
            CalcRatio cr) {

        final StateStatistic st1 = s1.getStat();
        final StateStatistic st2 = s2.getStat();
        final PDRTA a = s1.getPDRTA();
        final int minData = PDRTA.getMinData();

        if (!st1.trainMode || !st2.trainMode) {
            throw new UnsupportedOperationException();
        }

        // LRT_FIX : Operator for calculation interruption (thesis: AND, impl: OR, own: AND)
        if (SimplePDRTALearner.bOp[2].eval(st1.totalOutCount < minData, st2.totalOutCount < minData)) {
            return new LikelihoodValue();
        } else {
            return calcInterimLRT(a, st1.symbolCount, st2.symbolCount, advancedPooling, cr);
        }
    }

    /**
     * Calculates the {@link LikelihoodValue} of the histogram bin distributions for merging two {@link PDRTAState}s
     * 
     * @param s1
     *            First {@link PDRTAState} for merging
     * @param s2
     *            Second {@link PDRTAState} for merging
     * @return The {@link LikelihoodValue} of the histogram bin distributions for merging
     */
    public static LikelihoodValue getLikelihoodRatioTime(PDRTAState s1, PDRTAState s2, boolean advancedPooling,
            CalcRatio cr) {

        final StateStatistic st1 = s1.getStat();
        final StateStatistic st2 = s2.getStat();
        final PDRTA a = s1.getPDRTA();
        final int minData = PDRTA.getMinData();

        if (!st1.trainMode || !st2.trainMode) {
            throw new UnsupportedOperationException();
        }

        // LRT_FIX : Operator for calculation interruption (thesis: AND, impl: OR, own: AND)
        if (SimplePDRTALearner.bOp[2].eval(st1.totalOutCount < minData, st2.totalOutCount < minData)) {
            return new LikelihoodValue();
        } else {
            return calcInterimLRT(a, st1.timeCount, st2.timeCount, advancedPooling, cr);
        }
    }

    /**
     * Calculates the {@link LikelihoodValue} of the symbol distributions for splitting a transition. This done by splitting the set of {@link TimedTail}s in a
     * {@link PDRTAState}
     * 
     * @param s
     *            The {@link PDRTAState} for splitting
     * @param mSym
     *            The Set of {@link TimedTail}s to be split apart clustered by symbol index
     * @return The {@link LikelihoodValue} of the symbol distributions for splitting a transition
     */
    public static LikelihoodValue getLikelihoodRatioSym(PDRTAState s, Multimap<Integer, TimedTail> mSym,
            boolean advancedPooling, CalcRatio cr) {

        final StateStatistic st = s.getStat();
        final PDRTA a = s.getPDRTA();
        final int minData = PDRTA.getMinData();

        if (!st.trainMode) {
            throw new UnsupportedOperationException();
        }

        // LRT_FIX : Operator for calculation interruption (thesis: AND, impl: OR, own: AND)
        if (SimplePDRTALearner.bOp[2].eval((st.totalOutCount - mSym.size()) < minData, mSym.size() < minData)) {
            return new LikelihoodValue();
        }

        final int[] part1SymCount = Arrays.copyOf(st.symbolCount, st.symbolCount.length);
        final int[] part2SymCount = new int[st.symbolCount.length];
        for (final Entry<Integer, Collection<TimedTail>> eCol : mSym.asMap().entrySet()) {
            part1SymCount[eCol.getKey().intValue()] -= eCol.getValue().size();
            part2SymCount[eCol.getKey().intValue()] += eCol.getValue().size();
        }

        return calcInterimLRT(a, part1SymCount, part2SymCount, advancedPooling, cr);
    }

    /**
     * Calculates the {@link LikelihoodValue} of the histogram bin distributions for splitting a transition. This done by splitting the set of {@link TimedTail}
     * s in a {@link PDRTAState}
     * 
     * @param s
     *            The {@link PDRTAState} for splitting
     * @param mHist
     *            The Set of {@link TimedTail}s to be split apart clustered by histogram index
     * @return The {@link LikelihoodValue} of the histogram bin distributions for splitting a transition
     */
    public static LikelihoodValue getLikelihoodRatioTime(PDRTAState s, Multimap<Integer, TimedTail> mHist,
            boolean advancedPooling, CalcRatio cr) {

        final StateStatistic st = s.getStat();
        final PDRTA a = s.getPDRTA();
        final int minData = PDRTA.getMinData();

        if (!st.trainMode) {
            throw new UnsupportedOperationException();
        }

        // LRT_FIX : Operator for calculation interruption (thesis: AND, impl: OR, own: AND)
        if (SimplePDRTALearner.bOp[2].eval((st.totalOutCount - mHist.size()) < minData, mHist.size() < minData)) {
            return new LikelihoodValue();
        }

        final int[] part1TimeCount = Arrays.copyOf(st.timeCount, st.timeCount.length);
        final int[] part2TimeCount = new int[st.timeCount.length];
        for (final Entry<Integer, Collection<TimedTail>> eCol : mHist.asMap().entrySet()) {
            part1TimeCount[eCol.getKey().intValue()] -= eCol.getValue().size();
            part2TimeCount[eCol.getKey().intValue()] += eCol.getValue().size();
        }

        return calcInterimLRT(a, part1TimeCount, part2TimeCount, advancedPooling, cr);
    }

    public static LikelihoodValue getLikelihoodTime(PDRTAState s) {

        final StateStatistic st = s.getStat();

        if (!st.trainMode) {
            throw new UnsupportedOperationException();
        }

        return calcLikelihood(st.timeCount, st.totalOutCount);
    }

    public static LikelihoodValue getLikelihoodSym(PDRTAState s) {

        final StateStatistic st = s.getStat();

        if (!st.trainMode) {
            throw new UnsupportedOperationException();
        }

        return calcLikelihood(st.symbolCount, st.totalOutCount);
    }

    private static LikelihoodValue calcLikelihood(int[] count, int total) {

        double ratio = 0.0;
        int params = 0;
        for (int i = 0; i < count.length; i++) {
            if (count[i] > 0) {
                ratio += Math.log((double) count[i] / (double) total) * count[i];
                params++;
            }
        }

        // LRT_FIX : Check if params or count.length (example in paper says count.length)
        // Does not work with params -> Degrees of Freedom for compared states may not be equal then
        // *****
        // Tested whether or not to subtract 1 of the degrees of freedom
        // -> Results are equal and decided to use subtraction according to paper
        // -> Contradiction with Verwer's implementation of LRT with pooling
        if (params > 0) {
            return new LikelihoodValue(ratio, count.length - 1);
        } else {
            return new LikelihoodValue();
        }
    }

    /**
     * Creates a deep copy of a given {@link StateStatistic}
     * 
     * @param st
     *            The {@link StateStatistic} to be copied
     */
    protected StateStatistic(StateStatistic st) {

        if (st.trainMode) {
            histBarSizes = st.histBarSizes;
            timeCount = Arrays.copyOf(st.timeCount, st.timeCount.length);
            symbolCount = Arrays.copyOf(st.symbolCount, st.symbolCount.length);
            totalOutCount = st.totalOutCount;
            totalInCount = st.totalInCount;
            trainMode = true;
        } else {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Adds an incoming {@link TimedTail} to the statistics while training
     * 
     * @param t
     *            The incoming {@link TimedTail} to be added
     */
    protected void addToStats(TimedTail t) {

        if (trainMode && t != null) {
            totalInCount++;
            t = t.getNextTail();
            if (t != null) {
                totalOutCount++;
                symbolCount[t.getSymbolAlphIndex()]++;
                timeCount[t.getHistBarIndex()]++;
            }
        } else {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Merges another {@link StateStatistic} into this statistic while training. This is used when two {@link PDRTAState}s are merged.
     * 
     * @param st
     *            The {@link StateStatistic} to be merged
     */
    public void merge(StateStatistic st) {

        assert (trainMode);
        assert (timeCount.length == st.timeCount.length);
        assert (symbolCount.length == st.symbolCount.length);
        assert (histBarSizes.length == st.histBarSizes.length);

        for (int i = 0; i < timeCount.length; i++) {
            timeCount[i] += st.timeCount[i];
        }
        for (int i = 0; i < symbolCount.length; i++) {
            symbolCount[i] += st.symbolCount[i];
        }
        totalOutCount += st.totalOutCount;
        totalInCount += st.totalInCount;
    }

    /**
     * Adds the probability for a transition to be used by a {@link TimedTail} when reconstructing the {@link StateStatistic} from an already trained and
     * persisted {@link PDRTA}
     * 
     * @param in
     *            A transition
     * @param prob
     *            The probability corresponding to the transition
     */
    protected void addInterval(int symIdx, Interval in, double prob) {

        if (!trainMode) {
            addToMap(symIdx, in, prob);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    private void addToMap(int symIdx, Interval in, double prob) {
        TObjectDoubleHashMap<Interval> m;
        if (intervalProbs.containsKey(symIdx)) {
            m = intervalProbs.get(symIdx);
        } else {
            m = new TObjectDoubleHashMap<>();
            intervalProbs.put(symIdx, m);
        }
        m.put(in, prob);
    }

    /**
     * Returns the probability for incoming {@link TimedTail}s to end in the {@link PDRTAState}
     * 
     * @return The probability for incoming {@link TimedTail}s to end in the {@link PDRTAState}
     */
    protected double getTailEndProb() {

        if (trainMode) {
            return 1.0 - ((double) totalOutCount / (double) totalInCount);
        } else {
            return tailEndProb;
        }
    }

    /**
     * Returns the probability for a given transition to be used by {@link TimedTail}s
     * 
     * @param in
     *            The transition to get the probability for
     * @return The probability the transition to be used by {@link TimedTail}s
     */
    protected double getTransProb(int symIdx, Interval in) {

        if (trainMode) {
            return (double) in.getTails().size() / (double) totalOutCount;
        } else {
            if (intervalProbs.containsKey(symIdx)) {
                final TObjectDoubleHashMap<Interval> m = intervalProbs.get(symIdx);
                if (m.containsKey(in)) {
                    return m.get(in);
                }
            }
            return 0.0;
        }
    }

    /**
     * Returns the probability for a given {@link TimedTail} according to the independent symbol and histogram bin probabilities
     * 
     * @param t
     *            The {@link TimedTail} to get the probability for
     * @return The probability for a given {@link TimedTail} according to the independent symbol and histogram bin probabilities
     */
    protected double[] getHistProb(TimedTail t) {

        if (t.getHistBarIndex() < 0 || t.getSymbolAlphIndex() < 0) {
            return new double[] { 0.0, 0.0 };
        }

        if (trainMode) {
            final double timeP = (double) timeCount[t.getHistBarIndex()] / (double) totalOutCount;
            final double symP = (double) symbolCount[t.getSymbolAlphIndex()] / (double) totalOutCount;
            return new double[] { symP, (timeP / histBarSizes[t.getHistBarIndex()]) };
        } else {
            final double timeP = (timeProbs[t.getHistBarIndex()] / histBarSizes[t.getHistBarIndex()]);
            final double symP = symbolProbs[t.getSymbolAlphIndex()];
            return new double[] { symP, timeP };
        }
    }

    /**
     * Returns the number of outgoing {@link TimedTail}s while training
     * 
     * @return The number of outgoing {@link TimedTail}s
     */
    public int getTotalOutEvents() {

        if (trainMode) {
            return totalOutCount;
        } else {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Returns a {@link String} representation of the histogram bin distribution for persistence
     * 
     * @return The {@link String} representation of the histogram bin distribution
     */
    protected String getTimeProbsString() {

        String s = "";
        if (trainMode) {
            int c;
            if (totalOutCount == 0) {
                c = Integer.MAX_VALUE;
            } else {
                c = totalOutCount;
            }
            double p = (double) timeCount[0] / (double) c;
            s += p;
            for (int i = 1; i < timeCount.length; i++) {
                p = (double) timeCount[i] / (double) c;
                s += " / " + p;
            }
        } else {
            s += timeProbs[0];
            for (int i = 1; i < timeProbs.length; i++) {
                s += " / " + timeProbs[i];
            }
        }
        return s;
    }

    /**
     * Returns a {@link String} representation of the symbol distribution for persistence
     * 
     * @return The {@link String} representation of the symbol distribution
     */
    protected String getSymbolProbsString() {

        String s = "";
        if (trainMode) {
            int c;
            if (totalOutCount == 0) {
                c = Integer.MAX_VALUE;
            } else {
                c = totalOutCount;
            }
            double p = (double) symbolCount[0] / (double) c;
            s += p;
            for (int i = 1; i < symbolCount.length; i++) {
                p = (double) symbolCount[i] / (double) c;
                s += " / " + p;
            }
        } else {
            s += symbolProbs[0];
            for (int i = 1; i < symbolProbs.length; i++) {
                s += " / " + symbolProbs[i];
            }
        }
        return s;
    }

    /**
     * Creates an initial {@link StateStatistic} for training
     * 
     * @param numSymbols
     *            The number of symbols from the input set
     * @param histoBarSizes
     *            The sizes of the histogram bins
     */
    private StateStatistic(int numSymbols, int[] histoBarSizes) {

        histBarSizes = histoBarSizes;
        timeCount = new int[histoBarSizes.length];
        symbolCount = new int[numSymbols];
        totalOutCount = 0;
        totalInCount = 0;
        trainMode = true;
    }

    /**
     * Creates a {@link StateStatistic} reconstructed from a already trained and persisted {@link PDRTA}
     * 
     * @param histoBarSizes
     *            The sizes of the histogram bins
     * @param timeProbs
     *            The probabilities for outgoing {@link TimedTail}s for each histogram bin
     * @param symbolProbs
     *            The probabilities for outgoing {@link TimedTail}s for each symbol
     * @param tailEndProb
     *            The probability for incoming {@link TimedTail}s to end in the {@link PDRTAState}
     */
    private StateStatistic(int[] histoBarSizes, double[] timeProbs, double[] symbolProbs, double tailEndProb) {

        histBarSizes = histoBarSizes;
        this.timeProbs = timeProbs;
        this.symbolProbs = symbolProbs;
        this.tailEndProb = tailEndProb;
        this.intervalProbs = new TIntObjectHashMap<>();
        this.trainMode = false;
    }

    /**
     * Calculates the {@link LikelihoodValue} for two given sets of {@link TimedTail} counts in a {@link PDRTA}
     * 
     * @param a
     *            The {@link PDRTA}
     * @param v1
     *            The first set of {@link TimedTail} counts
     * @param v2
     *            The second set of {@link TimedTail} counts
     * @return The {@link LikelihoodValue} for two given sets of {@link TimedTail} counts
     */
    private static LikelihoodValue calcInterimLRT(PDRTA a, int[] v1, int[] v2, boolean advancedPooling,
            CalcRatio cr) {

        double ratio = 0.0;
        int parameters = 0;
        final int minData = PDRTA.getMinData();

        assert (v1.length == v2.length);

        final TIntArrayList[] pooled = poolStats(v1, v2, minData, advancedPooling);
        final int total1 = pooled[0].get(pooled[0].size() - 1);
        final int total2 = pooled[1].get(pooled[1].size() - 1);

        // Calculating ratio and parameters
        for (int i = 0; i < (pooled[0].size() - 1); i++) {
            ratio += cr.calc(pooled[0].get(i), total1, pooled[1].get(i), total2);
        }
        // Minus 1 because sum of values is last item (not counting as parameter)
        parameters = pooled[0].size() - 1;

        // LRT_FIX : Thesis: parameters -1 only explained without pooling, Impl: parameters
        // parameters--;

        if (parameters >= 0) {
            return new LikelihoodValue(ratio, parameters);
        } else {
            return new LikelihoodValue();
        }
    }

    /**
     * Calculates the Likelihood Ratio for two given {@link TimedTail} counts
     * 
     * @param v1
     *            The first specific counts
     * @param v1Total
     *            The first total counts
     * @param v2
     *            The second specific counts
     * @param v2Total
     *            The second total counts
     * @return The Likelihood Ratio for two given {@link TimedTail} counts
     */
    public static double calcLRTRatio(int v1, int v1Total, int v2, int v2Total) {

        double v1Prob = 1.0;
        if (v1 > 0) {
            v1Prob = (double) v1 / (double) v1Total;
        }
        double v2Prob = 1.0;
        if (v2 > 0) {
            v2Prob = (double) v2 / (double) v2Total;
        }
        double bothProb = 1.0;
        if (v1 > 0 || v2 > 0) {
            bothProb = (double) (v1 + v2) / (double) (v1Total + v2Total);
        }

        double ratio = 0.0;
        ratio += v1 * Math.log(bothProb);
        ratio -= v1 * Math.log(v1Prob);
        ratio += v2 * Math.log(bothProb);
        ratio -= v2 * Math.log(v2Prob);

        // TODO Test double problem
        // double ratio2 = 0.0;
        // ratio2 += (v1 + v2) * Math.log(bothProb);
        // ratio2 -= v1 * Math.log(v1Prob);
        // ratio2 -= v2 * Math.log(v2Prob);
        // if (Double.compare(ratio, ratio2) != 0) {
        // System.out.println("ratio1 and ratio2 are not equal.\nratio1="
        // + ratio + "\nratio2=" + ratio2);
        // }
        return ratio;
    }

    public static double calcFMRatio(int v1, int v1Total, int v2, int v2Total) {

        final int minData = PDRTA.getMinData();

        final double total = v1 + v2;
        final double expected1 = (v1Total * total) / (v1Total + v2Total);
        final double expected2 = (v2Total * total) / (v1Total + v2Total);

        double top1 = v1 - expected1;
        double top2 = v2 - expected2;

        // Yates correction for continuity
        if (v1 < minData || v2 < minData) {
            if (top1 < 0) {
                top1 = -top1;
            }
            top1 -= 0.5;
            if (top2 < 0) {
                top2 = -top2;
            }
            top2 -= 0.5;
        }

        // FIXME Check what happens if expected is 0 => Is NaN ok?
        return ((top1 * top1) / expected1) + ((top2 * top2) / expected2);
    }

    private static TIntArrayList[] poolStats(int[] stat1, int[] stat2, int minData, boolean advanced) {

        assert (stat1.length == stat2.length);

        final TIntArrayList pooled1 = new TIntArrayList(stat1.length + 1);
        final TIntArrayList pooled2 = new TIntArrayList(stat1.length + 1);

        int iPool = 0;
        pooled1.add(0);
        pooled2.add(0);

        int sum1 = 0;
        int sum2 = 0;

        for (int i = 0; i < stat1.length; i++) {
            sum1 += stat1[i];
            sum2 += stat2[i];
            // POOL_FIX : Operator for pooling (thesis: AND, impl: AND, own: AND)
            if (SimplePDRTALearner.bOp[0].eval(stat1[i] < minData, stat2[i] < minData)) {
                // Number of sequences is less than minData
                if (advanced && !SimplePDRTALearner.bOp[0].eval(pooled1.get(iPool) < minData,
                        pooled2.get(iPool) < minData)) {
                    // Close full pool and open new pool
                    iPool = pooled1.size();
                    pooled1.add(stat1[i]);
                    pooled2.add(stat2[i]);
                } else {
                    // Add to existing pool
                    pooled1.set(iPool, pooled1.get(iPool) + stat1[i]);
                    pooled2.set(iPool, pooled2.get(iPool) + stat2[i]);
                }
            } else {
                // Number of sequences is more or equal minData
                pooled1.add(stat1[i]);
                pooled2.add(stat2[i]);
            }
        }

        // Discard small pools
        // POOL_FIX : Operator for pool discarding (thesis: missing, impl: [LRT: OR, FM: AND], own: AND)
        if (SimplePDRTALearner.bOp[1].eval(pooled1.get(iPool) < minData, pooled2.get(iPool) < minData)) {
            sum1 -= pooled1.removeAt(iPool);
            sum2 -= pooled2.removeAt(iPool);
        }

        // Append sum of values
        pooled1.add(sum1);
        pooled2.add(sum2);

        assert (pooled1.size() == pooled2.size());

        final TIntArrayList[] pools = new TIntArrayList[2];
        pools[0] = pooled1;
        pools[1] = pooled2;
        return pools;
    }

    @Override
    public int hashCode() {

        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(histBarSizes);
        result = prime * result + ((intervalProbs == null) ? 0 : intervalProbs.hashCode());
        result = prime * result + Arrays.hashCode(symbolCount);
        result = prime * result + Arrays.hashCode(symbolProbs);
        long temp;
        temp = Double.doubleToLongBits(tailEndProb);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        result = prime * result + Arrays.hashCode(timeCount);
        result = prime * result + Arrays.hashCode(timeProbs);
        result = prime * result + totalInCount;
        result = prime * result + totalOutCount;
        result = prime * result + (trainMode ? 1231 : 1237);
        return result;
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final StateStatistic other = (StateStatistic) obj;
        if (!Arrays.equals(histBarSizes, other.histBarSizes)) {
            return false;
        }
        if (intervalProbs == null) {
            if (other.intervalProbs != null) {
                return false;
            }
        } else if (!intervalProbs.equals(other.intervalProbs)) {
            return false;
        }
        if (!Arrays.equals(symbolCount, other.symbolCount)) {
            return false;
        }
        if (!Arrays.equals(symbolProbs, other.symbolProbs)) {
            return false;
        }
        if (Double.doubleToLongBits(tailEndProb) != Double.doubleToLongBits(other.tailEndProb)) {
            return false;
        }
        if (!Arrays.equals(timeCount, other.timeCount)) {
            return false;
        }
        if (!Arrays.equals(timeProbs, other.timeProbs)) {
            return false;
        }
        if (totalInCount != other.totalInCount) {
            return false;
        }
        if (totalOutCount != other.totalOutCount) {
            return false;
        }
        if (trainMode != other.trainMode) {
            return false;
        }
        return true;
    }

    void cleanUp(PDRTAState s) {

        intervalProbs = new TIntObjectHashMap<>();
        for (int i = 0; i < symbolCount.length; i++) {
            final NavigableMap<Integer, Interval> ins = s.getIntervals(i);
            for (final Interval in : ins.values()) {
                addToMap(i, in, getTransProb(i, in));
            }
        }

        symbolProbs = new double[symbolCount.length];
        timeProbs = new double[histBarSizes.length];
        if (totalOutCount > 0) {
            for (int i = 0; i < symbolCount.length; i++) {
                symbolProbs[i] = (double) symbolCount[i] / (double) totalOutCount;
            }
            for (int i = 0; i < histBarSizes.length; i++) {
                timeProbs[i] = (double) timeCount[i] / (double) totalOutCount;
            }
        } else {
            Arrays.fill(symbolProbs, 0.0);
            Arrays.fill(timeProbs, 0.0);
        }

        tailEndProb = getTailEndProb();

        trainMode = false;
        timeCount = null;
        symbolCount = null;
        totalOutCount = -1;
        totalInCount = -1;
    }

    public interface CalcRatio {
        double calc(int v1, int t1, int v2, int t2);
    }

}