--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007 VTT Technical Research Centre of Finland and others.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.history.util;\r
+\r
+import java.util.Formatter;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.TreeMap;\r
+\r
+import org.simantics.databoard.primitives.MutableInteger;\r
+\r
+/**\r
+ * This class gives a rough distribution classification for double values. \r
+ * \r
+ * It calculates the frequency of values in each class. All values are \r
+ * accounted. There is a value between two samples. \r
+ * n(sample intervals) = n(samples) - 1 <p>\r
+ * \r
+ * Classes are derieved using logarithmic scale. \r
+ * Each class accounts all values within a range [ base ^ class .. base ^ (class-1) ).\r
+ * \r
+ * Example for classes of base number 2.0\r
+ * \r
+ * Class Ranges\r
+ * -10 [ 0,98ms .. 1,95ms )\r
+ * -9 [ 1,95ms .. 3,91ms )\r
+ * -8 [ 3,91ms .. 7,81ms )\r
+ * -7 [ 7,81ms .. 15,63ms )\r
+ * -6 [ 15,63ms .. 31,25ms )\r
+ * -5 [ 31,25ms .. 62,50ms )\r
+ * -4 [ 62,50ms .. 125,00ms )\r
+ * -3 [ 125,00ms .. 250,00ms )\r
+ * -2 [ 250,00ms .. 500,00ms )\r
+ * -1 [ 500,00ms .. 1 000,00ms )\r
+ * 0 [ 1 000,00ms .. 2 000,00ms )\r
+ * +1 [ 2 000,00ms .. 4 000,00ms )\r
+ * +2 [ 4 000,00ms .. 8 000,00ms )\r
+ * +3 [ 8 000,00ms .. 16 000,00ms )\r
+ * +4 [ 16 000,00ms .. 32 000,00ms )\r
+ * +5 [ 32 000,00ms .. 64 000,00ms )\r
+ * +6 [ 64 000,00ms .. 128 000,00ms )\r
+ * +7 [ 128 000,00ms .. 256 000,00ms )\r
+ * +8 [ 256 000,00ms .. 512 000,00ms )\r
+ * +9 [ 512 000,00ms .. 1 024 000,00ms )\r
+ * +10 [ 1 024 000,00ms .. 2 048 000,00ms ) \r
+ *\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ */\r
+public class ClassDistribution {\r
+ \r
+ // Optimization that tries to cirumvent slow Math.log\r
+ // previously entered value and its class is remembered\r
+ private transient double lastEnteredValue;\r
+ private transient int lastClass;\r
+ \r
+ double base;\r
+ private transient double log_base;\r
+\r
+ /** Distribution */\r
+ TreeMap<Integer, MutableInteger> distribution; \r
+ \r
+ public ClassDistribution() {\r
+ this( 2.0 );\r
+ }\r
+ \r
+ public ClassDistribution(double base) {\r
+ this.base = base;\r
+ log_base = Math.log( base );\r
+ lastEnteredValue = 0.001;\r
+ lastClass = (int) Math.floor( Math.log( 0.001 ) / log_base );\r
+ distribution = new TreeMap<Integer, MutableInteger>();\r
+ }\r
+ \r
+ public ClassDistribution(double base, TreeMap<Integer, MutableInteger> initialBreakdown) {\r
+ this.base = base;\r
+ log_base = Math.log( base );\r
+ lastEnteredValue = 0.001;\r
+ lastClass = (int) Math.floor( Math.log( 0.001 ) / log_base );\r
+ distribution = new TreeMap<Integer, MutableInteger>( initialBreakdown );\r
+ }\r
+ \r
+ public double getBase() {\r
+ return base;\r
+ }\r
+ \r
+ public void setBase(double base) {\r
+ this.base = base;\r
+ log_base = Math.log( base );\r
+ }\r
+\r
+ public double getSmallest() {\r
+ if (distribution.isEmpty()) return 1.0;\r
+ int clazz = distribution.firstKey();\r
+ return getClassAvg(clazz);\r
+ }\r
+ \r
+ /**\r
+ * Get median value\r
+ * \r
+ * @return\r
+ */\r
+ public double getMedian() {\r
+ // Count total\r
+ int n = 0;\r
+ for (MutableInteger v : distribution.values()) n += v.value;\r
+ \r
+ double median = (double) n/2;\r
+ double sum = 0;\r
+ for (Entry<Integer, MutableInteger> e : distribution.entrySet()) {\r
+ sum += e.getValue().value;\r
+ if (sum==median) { \r
+ int c = e.getKey();\r
+ Integer nextC = distribution.higherKey( c );\r
+ if (nextC == null)\r
+ return getClassMax( c );\r
+ \r
+ return ( getClassMax( c ) + getClassMin( nextC ) ) /2; \r
+ }\r
+ if (sum>median) {\r
+ int c = e.getKey();\r
+ return getClassAvg( c );\r
+ }\r
+ } \r
+ return 0;\r
+ }\r
+ \r
+ /**\r
+ * Get an index to the class with highest frequency.\r
+ *\r
+ * @return interval class or 0 if there are no samples\r
+ */\r
+ public int getLargestClassIndex() {\r
+ int result = 0;\r
+ int nResult = -1;\r
+ for (Entry<Integer, MutableInteger> e : distribution.entrySet()) {\r
+ if (e.getValue().value > nResult) {\r
+ nResult = e.getValue().value;\r
+ result = e.getKey();\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ /**\r
+ * Write distribution with user given map.\r
+ * \r
+ * @param result map where breakdown is written to\r
+ */\r
+ public void getDistribution(Map<Integer, Integer> result) {\r
+ result.clear();\r
+ for (Entry<Integer, MutableInteger> e : distribution.entrySet()) {\r
+ result.put( e.getKey(), Integer.valueOf(e.getValue().value));\r
+ }\r
+ }\r
+ \r
+ public void setDistribution(Map<Integer, Integer> map) {\r
+ this.distribution.clear();\r
+ for (Entry<Integer, Integer> e : map.entrySet()) {\r
+ distribution.put( e.getKey(), new MutableInteger(e.getValue()) );\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Create a snapshot copy of the distribution.\r
+ * \r
+ * @return a histogram \r
+ */\r
+ public TreeMap<Integer, Integer> getDistribution() {\r
+ TreeMap<Integer, Integer> result = new TreeMap<Integer, Integer>();\r
+ getDistribution(result);\r
+ return result;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Add new value to the distribution.\r
+ * \r
+ * @param value\r
+ */\r
+ public void addValue(double value) { \r
+ Integer k = Integer.valueOf( getClassIndex(value) );\r
+ MutableInteger r = distribution.get(k);\r
+ if ( r == null ) {\r
+ r = new MutableInteger();\r
+ distribution.put(k, r);\r
+ }\r
+ r.value ++;\r
+ }\r
+\r
+ /**\r
+ * Get lowest value of a class\r
+ * \r
+ * @param intervalClass\r
+ * @return min value\r
+ */\r
+ public double getClassMin(int intervalClass) {\r
+ return Math.pow(base, intervalClass);\r
+ }\r
+\r
+ /**\r
+ * Get highest value of a class \r
+ * \r
+ * @param intervalClass\r
+ * @return max value\r
+ */\r
+ public double getClassMax(int intervalClass) {\r
+ return Math.pow(base, intervalClass+1);\r
+ }\r
+ \r
+ /**\r
+ * Get average value of a class\r
+ * \r
+ * @param intervalClass\r
+ * @return average interval\r
+ */\r
+ public double getClassAvg(int intervalClass) {\r
+ double min = getClassMin(intervalClass);\r
+ double max = getClassMax(intervalClass);\r
+ return (max-min)/2+min;\r
+ }\r
+ \r
+ /**\r
+ * Get class index for a value\r
+ * @param interval\r
+ * @return class index\r
+ */\r
+ public int getClassIndex(double interval) {\r
+ if (interval == lastEnteredValue) return lastClass;\r
+ lastClass = (int) Math.floor( Math.log(interval) / log_base );\r
+ lastEnteredValue = interval; \r
+ return lastClass;\r
+ }\r
+ \r
+ @Override\r
+ public String toString() {\r
+ StringBuilder sb = new StringBuilder();\r
+ Formatter f = new Formatter(sb);\r
+ sb.append("Index Range Count\n");\r
+ for (Entry<Integer, MutableInteger> e : distribution.entrySet()) {\r
+ int ic = e.getKey();\r
+ double start = getClassMin(ic);\r
+ double end = getClassMax(ic);\r
+ double avg = getClassAvg(ic);\r
+ int count = e.getValue().value;\r
+ String format;\r
+ if (start<0.001) {\r
+ start *= 1000.0;\r
+ end *= 1000.0;\r
+ avg *= 1000.0;\r
+ format = " %+3d [ %(,8fm .. %(,8fm ) = %d, avg = %(,8fm\n";\r
+ } else\r
+ if (start<1) {\r
+ start *= 1000.0;\r
+ end *= 1000.0;\r
+ avg *= 1000.0;\r
+ format = " %+3d [ %(,8.2fm .. %(,8.2fm ) = %d, avg = %(,8.2fm\n";\r
+ } else {\r
+ format = " %+3d [ %(9.0f .. %(9.0f ) = %d, avg = %(8.1f\n";\r
+ }\r
+ f.format(format , ic, start, end, count, avg);\r
+ }\r
+ return sb.toString();\r
+ }\r
+ \r
+}\r
+\r