Java tutorial
/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program 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. */ package geogebra.kernel; import geogebra.euclidian.EuclidianView; import geogebra.kernel.arithmetic.MyDouble; import geogebra.kernel.arithmetic.NumberValue; import geogebra.kernel.optimization.ExtremumFinder; import geogebra.kernel.optimization.NegativeRealRootFunction; import geogebra.kernel.roots.RealRootFunction; import geogebra.main.Application; import java.util.ArrayList; import org.apache.commons.math.distribution.BinomialDistributionImpl; import org.apache.commons.math.distribution.HypergeometricDistributionImpl; import org.apache.commons.math.distribution.IntegerDistribution; import org.apache.commons.math.distribution.PascalDistributionImpl; import org.apache.commons.math.distribution.PoissonDistributionImpl; import org.apache.commons.math.distribution.ZipfDistributionImpl; /** * Superclass for lower/upper sum of function f in interval [a, b] with * n intervals */ public abstract class AlgoFunctionAreaSums extends AlgoElement implements EuclidianViewCE, AlgoDrawInformation { // largest possible number of rectangles private static final int MAX_RECTANGLES = 10000; // subsample every 5 pixels private static final int SAMPLE_PIXELS = 5; // find global minimum in an interval with the following heuristic: // 1) sample the function for some values of x in [a, b] // 2) get x[i] with minimal f(x[i]) // 3) use parabolic interpolation and Brent's Method in One Dimension // for interval x[i-1] to x[i+1] // (Numerical Recipes in C++, pp.406) private int type; /** Upper Rieeman sum **/ public static final int TYPE_UPPERSUM = 0; /** Lower Rieman sum **/ public static final int TYPE_LOWERSUM = 1; /** Left Rieman sum (Ulven: 09.02.11) **/ public static final int TYPE_LEFTSUM = 2; /** Rectangle sum with divider for step interval (Ulven: 09.02.11) **/ public static final int TYPE_RECTANGLESUM = 3; /** Trapezoidal sum**/ public static final int TYPE_TRAPEZOIDALSUM = 4; /** Barchart from expression**/ public static final int TYPE_BARCHART = 10; /** Barchart from raw data **/ public static final int TYPE_BARCHART_RAWDATA = 11; /** Barchart from (values,frequencies)**/ public static final int TYPE_BARCHART_FREQUENCY_TABLE = 12; /** Barchart from (values,frequencies) with given width**/ public static final int TYPE_BARCHART_FREQUENCY_TABLE_WIDTH = 13; /** Histogram from(class boundaries, raw data) with default density = 1 * or Histogram from(class boundaries, frequencies) no density required **/ public static final int TYPE_HISTOGRAM = 21; /** Histogram from(class boundaries, raw data) with given density **/ public static final int TYPE_HISTOGRAM_DENSITY = 22; /** barchart of a discrete probability distribution **/ public static final int TYPE_BARCHART_BINOMIAL = 40; public static final int TYPE_BARCHART_PASCAL = 41; public static final int TYPE_BARCHART_POISSON = 42; public static final int TYPE_BARCHART_HYPERGEOMETRIC = 43; public static final int TYPE_BARCHART_BERNOULLI = 44; public static final int TYPE_BARCHART_ZIPF = 45; // tolerance for parabolic interpolation private static final double TOLERANCE = 1E-7; private GeoFunction f; // input private NumberValue a, b, n, width, density, p1, p2, p3; // input /** * @return the p1 */ public NumberValue getP1() { return p1; } /** * @return the p2 */ public NumberValue getP2() { return p2; } /** * @return the p3 */ public NumberValue getP3() { return p3; } private NumberValue d; // input: divider for Rectangle sum, 0..1 private GeoList list1, list2; // input private GeoElement ageo, bgeo, ngeo, dgeo, widthGeo, densityGeo, useDensityGeo, isCumulative, p1geo, p2geo, p3geo; /** * @return the densityGeo */ public GeoElement getDensityGeo() { return densityGeo; } /** * @return the useDensityGeo */ public GeoElement getUseDensityGeo() { return useDensityGeo; } /** * @return the isCumulative */ public GeoElement getIsCumulative() { return isCumulative; } private GeoNumeric sum; // output sum private int N; private double STEP; private double[] yval; // y value (= min) in interval 0 <= i < N private double[] leftBorder; // leftBorder (x val) of interval 0 <= i < N //private double [] widths; private ExtremumFinder extrFinder; // maximum frequency of bar chart // this is used by stat dialogs when setting window dimensions private double freqMax; private boolean histogramRight; /** * Returns maximum frequency of a bar chart or histogram * @return maximum frequency of a bar chart or histogram */ public double getFreqMax() { return freqMax; } /** * Returns y values (heights) of a bar chart or histogram * @return y values (heights) of a bar chart or histogram */ public double[] getYValue() { return yval; } /** * Returns left class borders of a bar chart or histogram * @return left class borders of a bar chart or histogram */ public double[] getLeftBorder() { return leftBorder; } /** * Rectangle sum * @param cons * @param label * @param f * @param a * @param b * @param n * @param d * @param type */ public AlgoFunctionAreaSums(Construction cons, String label, GeoFunction f, NumberValue a, NumberValue b, NumberValue n, NumberValue d, int type) { super(cons); this.type = type; this.f = f; this.a = a; this.b = b; this.n = n; this.d = d; ageo = a.toGeoElement(); bgeo = b.toGeoElement(); ngeo = n.toGeoElement(); dgeo = d.toGeoElement(); sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); } public AlgoFunctionAreaSums(GeoFunction f, NumberValue a, NumberValue b, NumberValue n, NumberValue d, int type) { super(f.cons, false); this.f = f; this.a = a; this.b = b; this.n = n; this.d = d; ageo = a.toGeoElement(); bgeo = b.toGeoElement(); ngeo = n.toGeoElement(); dgeo = d.toGeoElement(); N = (int) Math.round(n.getDouble()); } /** * Upper o lower sum * @param cons * @param label * @param f * @param a * @param b * @param n * @param type */ public AlgoFunctionAreaSums(Construction cons, String label, GeoFunction f, NumberValue a, NumberValue b, NumberValue n, int type) { super(cons); this.type = type; extrFinder = cons.getExtremumFinder(); this.f = f; this.a = a; this.b = b; this.n = n; ageo = a.toGeoElement(); bgeo = b.toGeoElement(); ngeo = n.toGeoElement(); sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); } public AlgoFunctionAreaSums(NumberValue a, NumberValue b, NumberValue n, int type, double[] vals, double[] borders) { super(a.getKernel().getConstruction(), false); this.type = type; this.a = a; this.b = b; this.n = n; this.yval = vals; this.leftBorder = borders; N = (int) Math.round(n.getDouble()); } /** * BARCHART * @param cons * @param label * @param a * @param b * @param list1 */ public AlgoFunctionAreaSums(Construction cons, String label, NumberValue a, NumberValue b, GeoList list1) { super(cons); type = TYPE_BARCHART; this.a = a; this.b = b; this.list1 = list1; ageo = a.toGeoElement(); bgeo = b.toGeoElement(); sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); } /** * Constructor for copying BarChart * @param a * @param b * @param vals * @param borders * @param N */ protected AlgoFunctionAreaSums(NumberValue a, NumberValue b, double[] vals, double[] borders, int N) { super(a.getKernel().getConstruction(), false); type = TYPE_BARCHART; this.a = a; this.b = b; this.yval = vals; this.leftBorder = borders; this.N = N; } /** * BarChart [<list of data without repetition>, <frequency of each of these data>] * @param cons * @param label * @param list1 * @param list2 */ public AlgoFunctionAreaSums(Construction cons, String label, GeoList list1, GeoList list2) { this(cons, list1, list2); sum.setLabel(label); } public AlgoFunctionAreaSums(Construction cons, GeoList list1, GeoList list2) { super(cons); type = TYPE_BARCHART_FREQUENCY_TABLE; this.list1 = list1; this.list2 = list2; sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setDrawable(true); } /** * Constructor for copying BarChart * @param cons * @param dummy to distinguish from other constructors * @param vals * @param borders * @param N */ protected AlgoFunctionAreaSums(Construction cons, boolean dummy, double[] vals, double[] borders, int N) { super(cons, false); type = TYPE_BARCHART_FREQUENCY_TABLE; this.yval = vals; this.leftBorder = borders; this.N = N; } /** * BarChart [<list of data without repetition>, <frequency of each of these data>, <width>] * @param cons * @param label * @param list1 * @param list2 * @param width */ public AlgoFunctionAreaSums(Construction cons, String label, GeoList list1, GeoList list2, NumberValue width) { this(cons, list1, list2, width); sum.setLabel(label); } public AlgoFunctionAreaSums(Construction cons, GeoList list1, GeoList list2, NumberValue width) { super(cons); type = TYPE_BARCHART_FREQUENCY_TABLE_WIDTH; this.list1 = list1; this.list2 = list2; this.width = width; widthGeo = width.toGeoElement(); sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setDrawable(true); } /** * Constructor for copying BarChart * @param width * @param vals * @param borders * @param N */ protected AlgoFunctionAreaSums(NumberValue width, double[] vals, double[] borders, int N) { super(width.getKernel().getConstruction(), false); type = TYPE_BARCHART_FREQUENCY_TABLE_WIDTH; this.width = width; widthGeo = width.toGeoElement(); this.yval = vals; this.leftBorder = borders; this.N = N; } /** * BarChart [<list of data>, <width>] * @param cons * @param label * @param list1 * @param n */ public AlgoFunctionAreaSums(Construction cons, String label, GeoList list1, GeoNumeric n) { super(cons); type = TYPE_BARCHART_RAWDATA; this.list1 = list1; this.n = n; ngeo = n.toGeoElement(); sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); } /** * Constructor for copying BarChart * @param n * @param vals * @param borders * @param N */ protected AlgoFunctionAreaSums(GeoNumeric n, double[] vals, double[] borders, int N) { super(n.getKernel().getConstruction(), false); type = TYPE_BARCHART_RAWDATA; this.n = n; this.yval = vals; this.leftBorder = borders; this.N = N; } /** * HISTOGRAM[ <list of class boundaries>, <list of heights> ] * @param cons * @param label * @param list1 * @param list2 * @param right */ public AlgoFunctionAreaSums(Construction cons, String label, GeoList list1, GeoList list2, boolean right) { super(cons); type = TYPE_HISTOGRAM; this.histogramRight = right; this.list1 = list1; this.list2 = list2; sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); } public AlgoFunctionAreaSums(Construction cons, double[] vals, double[] borders, int N) { super(cons, false); type = TYPE_HISTOGRAM; this.leftBorder = borders; this.yval = vals; this.N = N; } /** * Histogram [<list of class boundaries>, <list of raw data>, <useDensity>, <densityFactor>] * @param cons * @param label * @param isCumulative * @param list1 * @param list2 * @param useDensity * @param density * @param right */ public AlgoFunctionAreaSums(Construction cons, String label, GeoBoolean isCumulative, GeoList list1, GeoList list2, GeoBoolean useDensity, GeoNumeric density, boolean right) { this(cons, isCumulative, list1, list2, useDensity, density, right); sum.setLabel(label); } public AlgoFunctionAreaSums(Construction cons, GeoBoolean isCumulative, GeoList list1, GeoList list2, GeoBoolean useDensity, GeoNumeric density, boolean right) { super(cons); this.histogramRight = right; type = TYPE_HISTOGRAM_DENSITY; this.isCumulative = isCumulative; this.list1 = list1; this.list2 = list2; this.density = density; if (density != null) densityGeo = density.toGeoElement(); this.useDensityGeo = useDensity; sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setDrawable(true); } public AlgoFunctionAreaSums(GeoBoolean isCumulative, GeoBoolean useDensity, GeoNumeric density, double[] vals, double[] borders, int N) { super(useDensity.getConstruction(), false); type = TYPE_HISTOGRAM_DENSITY; this.isCumulative = isCumulative; this.N = N; this.density = density; this.useDensityGeo = useDensity; this.leftBorder = borders; this.yval = vals; } /** * Discrete distribution bar chart * @param cons * @param label * @param p1 * @param p2 * @param p3 * @param isCumulative * @param type */ public AlgoFunctionAreaSums(Construction cons, String label, NumberValue p1, NumberValue p2, NumberValue p3, GeoBoolean isCumulative, int type) { super(cons); this.type = type; this.p1 = p1; this.p2 = p2; this.p3 = p3; p1geo = p1.toGeoElement(); if (p2 != null) p2geo = p2.toGeoElement(); if (p3 != null) p3geo = p3.toGeoElement(); this.isCumulative = isCumulative; sum = new GeoNumeric(cons); // output setInputOutput(); // for AlgoElement compute(); sum.setLabel(label); sum.setDrawable(true); if (yval == null) { yval = new double[0]; leftBorder = new double[0]; } } protected AlgoFunctionAreaSums(NumberValue p1, NumberValue p2, NumberValue p3, GeoBoolean isCumulative, int type, NumberValue a, NumberValue b, double[] vals, double[] borders, int N) { super(isCumulative.getConstruction(), false); this.type = type; this.p1 = p1; this.p2 = p2; this.p3 = p3; p1geo = p1.toGeoElement(); if (p2 != null) p2geo = p2.toGeoElement(); if (p3 != null) p3geo = p3.toGeoElement(); this.isCumulative = isCumulative; this.a = a; this.b = b; this.yval = vals; this.leftBorder = borders; this.N = N; } public boolean isRight() { return histogramRight; } final public boolean euclidianViewUpdate() { compute(); return false; } public abstract String getClassName(); // for AlgoElement protected void setInputOutput() { switch (type) { case TYPE_UPPERSUM: case TYPE_LOWERSUM: case TYPE_TRAPEZOIDALSUM: case TYPE_LEFTSUM: //Ulven: 09.02.11 input = new GeoElement[4]; input[0] = f; input[1] = ageo; input[2] = bgeo; input[3] = ngeo; break; case TYPE_RECTANGLESUM: //Ulven: 09.02.11 input = new GeoElement[5]; input[0] = f; input[1] = ageo; input[2] = bgeo; input[3] = ngeo; input[4] = dgeo; break; case TYPE_BARCHART: input = new GeoElement[3]; input[0] = ageo; input[1] = bgeo; input[2] = list1; break; case TYPE_BARCHART_FREQUENCY_TABLE: input = new GeoElement[2]; input[0] = list1; input[1] = list2; break; case TYPE_BARCHART_FREQUENCY_TABLE_WIDTH: input = new GeoElement[3]; input[0] = list1; input[1] = list2; input[2] = widthGeo; break; case TYPE_BARCHART_RAWDATA: input = new GeoElement[2]; input[0] = list1; input[1] = ngeo; break; case TYPE_HISTOGRAM: input = new GeoElement[2]; input[0] = list1; input[1] = list2; break; case TYPE_HISTOGRAM_DENSITY: if (isCumulative == null) { if (densityGeo == null) { input = new GeoElement[3]; input[0] = list1; input[1] = list2; input[2] = useDensityGeo; } else { input = new GeoElement[4]; input[0] = list1; input[1] = list2; input[2] = useDensityGeo; input[3] = densityGeo; } } else { if (densityGeo == null) { input = new GeoElement[4]; input[0] = isCumulative; input[1] = list1; input[2] = list2; input[3] = useDensityGeo; } else { input = new GeoElement[5]; input[0] = isCumulative; input[1] = list1; input[2] = list2; input[3] = useDensityGeo; input[4] = densityGeo; } } break; case TYPE_BARCHART_BERNOULLI: case TYPE_BARCHART_BINOMIAL: case TYPE_BARCHART_PASCAL: case TYPE_BARCHART_HYPERGEOMETRIC: case TYPE_BARCHART_POISSON: case TYPE_BARCHART_ZIPF: ArrayList<GeoElement> inputList = new ArrayList<GeoElement>(); inputList.add(p1geo); if (p2geo != null) inputList.add(p2geo); if (p3geo != null) inputList.add(p3geo); if (isCumulative != null) inputList.add(isCumulative); input = new GeoElement[inputList.size()]; input = inputList.toArray(input); break; } setOutputLength(1); setOutput(0, sum); setDependencies(); // done by AlgoElement } /** * function * @return function */ public GeoFunction getF() { return f; } /** * number of intervals * @return number of intervals */ public int getIntervals() { return N; } /** * Returns length of step for sums * @return length of step for sums */ public double getStep() { return STEP; } /** * Returns list of function values * @return list of function values */ public double[] getValues() { return yval; } /** * Returns the resulting sum * @return the resulting sum */ public GeoNumeric getSum() { return sum; } /** * Returns lower bound for sums * @return lower bound for sums */ public NumberValue getA() { return a == null ? new MyDouble(kernel, Double.NaN) : a; } /** * Returns upper bound for sums * @return upper bound for sums */ public NumberValue getB() { return b == null ? new MyDouble(kernel, Double.NaN) : b; } /** * Returns n * @return n */ public GeoNumeric getN() { return (GeoNumeric) ngeo; } /** * Returns d * @return d */ public GeoNumeric getD() { return (GeoNumeric) dgeo; } /** * Returns list of raw data * @return list of raw data */ public GeoList getList1() { return list1; } /** * Returns list of frequencies for histogram * @return list of frequencies for histogram */ public GeoList getList2() { return list2; } protected final void compute() { GeoElement geo; // temporary variable //Application.debug(type); switch (type) { case TYPE_LOWERSUM: case TYPE_UPPERSUM: if (!(f.isDefined() && ageo.isDefined() && bgeo.isDefined() && ngeo.isDefined())) sum.setUndefined(); RealRootFunction fun = f.getRealRootFunctionY(); double ad = a.getDouble(); double bd = b.getDouble(); double ints = n.getDouble(); if (ints < 1) { sum.setUndefined(); return; } else if (ints > MAX_RECTANGLES) { N = MAX_RECTANGLES; } else { N = (int) Math.round(ints); } STEP = (bd - ad) / N; // calc minimum in every interval if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; } RealRootFunction fmin = fun; if (type == TYPE_UPPERSUM) fmin = new NegativeRealRootFunction(fun); // use -f to find maximum double cumSum = 0; double left, right, min; // calulate the min and max x-coords of what actually needs to be drawn // do subsampling only in this region EuclidianView ev = app.getEuclidianView(); double visibleMin = Math.max(Math.min(ad, bd), ev.getXmin()); double visibleMax = Math.min(Math.max(ad, bd), ev.getXmax()); // subsample every 5 pixels double noOfSamples = Math.abs(ev.toScreenCoordXd(visibleMax) - ev.toScreenCoordX(visibleMin)) / SAMPLE_PIXELS; double subStep = Math.abs(visibleMax - visibleMin) / noOfSamples; boolean doSubSamples = !Kernel.isZero(subStep) && Math.abs(STEP) > subStep; boolean positiveStep = STEP >= 0; for (int i = 0; i < N; i++) { leftBorder[i] = ad + i * STEP; if (positiveStep) { left = leftBorder[i]; right = leftBorder[i] + STEP; } else { left = leftBorder[i] + STEP; right = leftBorder[i]; } min = Double.POSITIVE_INFINITY; // heuristic: take some samples in this interval // and find smallest one // subsampling needed in case there are two eg minimums and we get the wrong one with extrFinder.findMinimum() //Application.debug(left + " "+ visibleMin+" "+right + " "+visibleMax); // subsample visible bit only if (doSubSamples && ((STEP > 0 ? left : right) < visibleMax && (STEP > 0 ? right : left) > visibleMin)) { //Application.debug("subsampling from "+left+" to "+right); double y, minSample = left; for (double x = left; x < right; x += subStep) { y = fmin.evaluate(x); if (y < min) { min = y; minSample = x; } } // if the minimum is on the left border then minSample == left now // check right border too y = fmin.evaluate(right); if (y < min) minSample = right; // investigate only the interval around the minSample // make sure we don't get out of our interval! left = Math.max(left, minSample - subStep); right = Math.min(right, minSample + subStep); } // find minimum (resp. maximum) over interval double x = extrFinder.findMinimum(left, right, fmin, TOLERANCE); double y = fmin.evaluate(x); // one of the evaluated sub-samples could be smaller // e.g. at the border of this interval if (y > min) y = min; // store min/max if (type == TYPE_UPPERSUM) y = -y; yval[i] = y; // add to sum cumSum += y; } // calc area of rectangles sum.setValue(cumSum * STEP); break; case TYPE_TRAPEZOIDALSUM: case TYPE_RECTANGLESUM: case TYPE_LEFTSUM: if (!(f.isDefined() && ageo.isDefined() && bgeo.isDefined() && ngeo.isDefined())) sum.setUndefined(); /* Rectanglesum needs extra treatment */ if ((type == TYPE_RECTANGLESUM) && (!dgeo.isDefined())) { //extra parameter sum.setUndefined(); } //if d parameter for rectanglesum fun = f.getRealRootFunctionY(); ad = a.getDouble(); bd = b.getDouble(); ints = n.getDouble(); if (ints < 1) { sum.setUndefined(); return; } else if (ints > MAX_RECTANGLES) { N = MAX_RECTANGLES; } else { N = (int) Math.round(ints); } STEP = (bd - ad) / N; // calc minimum in every interval if (yval == null || yval.length < N + 1) {// N+1 for trapezoids yval = new double[N + 1]; // N+1 for trapezoids leftBorder = new double[N + 1]; // N+1 for trapezoids } cumSum = 0; for (int i = 0; i < N + 1; i++) { // N+1 for trapezoids leftBorder[i] = ad + i * STEP; /* Extra treatment for RectangleSum */ if (type == TYPE_RECTANGLESUM) { double dd = d.getDouble(); if ((0.0 <= dd) && (dd <= 1.0)) { yval[i] = fun.evaluate(leftBorder[i] + dd * STEP); //divider into step-interval } else { sum.setUndefined(); return; } // if divider ok } else { yval[i] = fun.evaluate(leftBorder[i]); } //if cumSum += yval[i]; } // calc area of rectangles or trapezoids if ((type == TYPE_RECTANGLESUM) || (type == TYPE_LEFTSUM)) { cumSum -= yval[N]; //Last right value not needed } else { cumSum -= (yval[0] + yval[N]) / 2; } //if rectangles or trapezoids //for (int i=0; i < N+1 ; i++) cumSum += yval[i]; sum.setValue(cumSum * STEP); break; case TYPE_BARCHART: if (!(ageo.isDefined() && bgeo.isDefined() && list1.isDefined())) { sum.setUndefined(); return; } N = list1.size(); ad = a.getDouble(); bd = b.getDouble(); ints = list1.size(); if (ints < 1) { sum.setUndefined(); return; } else if (ints > MAX_RECTANGLES) { N = MAX_RECTANGLES; } else { N = (int) Math.round(ints); } STEP = (bd - ad) / N; if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; } cumSum = 0; for (int i = 0; i < N; i++) { leftBorder[i] = ad + i * STEP; geo = list1.get(i); if (geo.isGeoNumeric()) yval[i] = ((GeoNumeric) geo).getDouble(); else yval[i] = 0; cumSum += yval[i]; } // calc area of rectangles sum.setValue(cumSum * STEP); break; case TYPE_BARCHART_RAWDATA: // BarChart[{1,1,2,3,3,3,4,5,5,5,5,5,5,5,6,8,9,10,11,12},3] if (!list1.isDefined() || !ngeo.isDefined()) { sum.setUndefined(); return; } double mini = Double.POSITIVE_INFINITY; double maxi = Double.NEGATIVE_INFINITY; int minIndex = -1, maxIndex = -1; double step = n.getDouble(); int rawDataSize = list1.size(); if (step < 0 || Kernel.isZero(step) || rawDataSize < 2) { sum.setUndefined(); return; } // find max and min for (int i = 0; i < rawDataSize; i++) { geo = list1.get(i); if (!geo.isGeoNumeric()) { sum.setUndefined(); return; } double val = ((GeoNumeric) geo).getDouble(); if (val > maxi) { maxi = val; maxIndex = i; } if (val < mini) { mini = val; minIndex = i; } } if (maxi == mini || maxIndex == -1 || minIndex == -1) { sum.setUndefined(); return; } double totalWidth = maxi - mini; double noOfBars = totalWidth / n.getDouble(); double gap = 0; /* if (kernel.isInteger(noOfBars)) { N = (int)noOfBars + 1; a = (NumberValue)list1.get(minIndex); b = (NumberValue)list1.get(maxIndex); } else */ { N = (int) noOfBars + 2; gap = ((N - 1) * step - totalWidth) / 2.0; a = (NumberValue) (new GeoNumeric(cons, mini - gap)); b = (NumberValue) (new GeoNumeric(cons, maxi + gap)); //Application.debug("gap = "+gap); } //Application.debug("N = "+N+" maxi = "+maxi+" mini = "+mini); if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; } // fill in class boundaries //double width = (maxi-mini)/(double)(N-2); for (int i = 0; i < N; i++) { leftBorder[i] = mini - gap + step * i; } // zero frequencies for (int i = 0; i < N; i++) yval[i] = 0; // work out frequencies in each class double datum; for (int i = 0; i < list1.size(); i++) { geo = list1.get(i); if (geo.isGeoNumeric()) datum = ((GeoNumeric) geo).getDouble(); else { sum.setUndefined(); return; } // if datum is outside the range, set undefined //if (datum < leftBorder[0] || datum > leftBorder[N-1] ) { sum.setUndefined(); return; } // fudge to make the last boundary eg 10 <= x <= 20 // all others are 10 <= x < 20 double oldMaxBorder = leftBorder[N - 1]; leftBorder[N - 1] += Math.abs(leftBorder[N - 1] / 100000000); // check which class this datum is in for (int j = 1; j < N; j++) { //System.out.println("checking "+leftBorder[j]); if (datum < leftBorder[j]) { //System.out.println(datum+" "+j); yval[j - 1]++; break; } } leftBorder[N - 1] = oldMaxBorder; // area of rectangles sum.setValue(list1.size() * step); } // find maximum frequency // this is used by the stat dialogs freqMax = 0.0; for (int k = 0; k < yval.length; ++k) { freqMax = Math.max(yval[k], freqMax); } break; case TYPE_BARCHART_FREQUENCY_TABLE: case TYPE_BARCHART_BINOMIAL: case TYPE_BARCHART_POISSON: case TYPE_BARCHART_HYPERGEOMETRIC: case TYPE_BARCHART_PASCAL: case TYPE_BARCHART_ZIPF: if (type != TYPE_BARCHART_FREQUENCY_TABLE) { if (!prepareDistributionLists()) { sum.setUndefined(); return; } } // BarChart[{11,12,13,14,15},{1,5,0,13,4}] if (!list1.isDefined() || !list2.isDefined()) { sum.setUndefined(); return; } N = list1.size() + 1; //if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; // } if (N == 2) { // special case, 1 bar yval = new double[2]; leftBorder = new double[2]; yval[0] = ((GeoNumeric) (list2.get(0))).getDouble(); leftBorder[0] = ((GeoNumeric) (list1.get(0))).getDouble() - 0.5; leftBorder[1] = leftBorder[0] + 1; ageo = new GeoNumeric(cons, leftBorder[0]); bgeo = new GeoNumeric(cons, leftBorder[1]); a = (NumberValue) ageo; b = (NumberValue) bgeo; sum.setValue(yval[0]); return; } if (list2.size() + 1 != N || N < 3) { sum.setUndefined(); return; } double start = ((GeoNumeric) (list1.get(0))).getDouble(); double end = ((GeoNumeric) (list1.get(N - 2))).getDouble(); step = ((GeoNumeric) (list1.get(1))).getDouble() - start; //Application.debug("N = "+N+" start = "+start+" end = "+end+" width = "+width); if (!Kernel.isEqual(end - start, step * (N - 2)) // check first list is (consistent) with being AP || step <= 0) { sum.setUndefined(); return; } ageo = new GeoNumeric(cons, start - step / 2); bgeo = new GeoNumeric(cons, end + step / 2); a = (NumberValue) ageo; b = (NumberValue) bgeo; // fill in class boundaries for (int i = 0; i < N; i++) { leftBorder[i] = start - step / 2 + step * i; } double area = 0; // fill in frequencies for (int i = 0; i < N - 1; i++) { geo = list2.get(i); if (!geo.isGeoNumeric()) { sum.setUndefined(); return; } yval[i] = ((GeoNumeric) (list2.get(i))).getDouble(); area += yval[i] * step; } // area of rectangles = total frequency if (type == TYPE_BARCHART_FREQUENCY_TABLE) { sum.setValue(area); } else { if (isCumulative != null && ((GeoBoolean) isCumulative).getBoolean()) { sum.setValue(Double.POSITIVE_INFINITY); } else sum.setValue(1.0); sum.updateCascade(); } break; case TYPE_BARCHART_BERNOULLI: double p = p1.getDouble(); if (p < 0 || p > 1) { sum.setUndefined(); return; } N = 3; // special case, 2 bars leftBorder = new double[3]; yval = new double[3]; boolean cumulative = ((GeoBoolean) isCumulative).getBoolean(); yval[0] = 1 - p; yval[1] = cumulative ? 1 : p; leftBorder[0] = -0.5; leftBorder[1] = 0.5; leftBorder[2] = 1.5; ageo = new GeoNumeric(cons, leftBorder[0]); bgeo = new GeoNumeric(cons, leftBorder[1]); a = (NumberValue) ageo; b = (NumberValue) bgeo; if (isCumulative != null && ((GeoBoolean) isCumulative).getBoolean()) { sum.setValue(Double.POSITIVE_INFINITY); } else sum.setValue(1.0); sum.updateCascade(); return; case TYPE_BARCHART_FREQUENCY_TABLE_WIDTH: // BarChart[{1,2,3,4,5},{1,5,0,13,4}, 0.5] if (!list1.isDefined() || !list2.isDefined()) { sum.setUndefined(); return; } N = list1.size() + 1; int NN = 2 * N - 1; if (list2.size() + 1 != N || N < 3) { sum.setUndefined(); return; } start = ((GeoNumeric) (list1.get(0))).getDouble(); end = ((GeoNumeric) (list1.get(N - 2))).getDouble(); step = ((GeoNumeric) (list1.get(1))).getDouble() - start; double colWidth = width.getDouble(); //Application.debug("N = "+N+" start = "+start+" end = "+end+" colWidth = "+colWidth); if (!Kernel.isEqual(end - start, step * (N - 2)) // check first list is (consistent) with being AP || step <= 0) { sum.setUndefined(); return; } ageo = new GeoNumeric(cons, start - colWidth / 2); bgeo = new GeoNumeric(cons, end + colWidth / 2); a = (NumberValue) ageo; b = (NumberValue) bgeo; if (yval == null || yval.length < NN - 1) { yval = new double[NN - 1]; leftBorder = new double[NN - 1]; } // fill in class boundaries for (int i = 0; i < NN - 1; i += 2) { leftBorder[i] = start + step * (i / 2.0) - colWidth / 2.0; leftBorder[i + 1] = start + step * (i / 2.0) + colWidth / 2.0; } area = 0; // fill in frequencies for (int i = 0; i < NN - 1; i++) { if (i % 2 == 1) { // dummy columns, zero height yval[i] = 0; } else { geo = list2.get(i / 2); if (!geo.isGeoNumeric()) { sum.setUndefined(); return; } yval[i] = ((GeoNumeric) (list2.get(i / 2))).getDouble(); area += yval[i] * colWidth; } } // area of rectangles = total frequency sum.setValue(area); N = NN - 1; break; case TYPE_HISTOGRAM: case TYPE_HISTOGRAM_DENSITY: if (!list1.isDefined() || !list2.isDefined()) { sum.setUndefined(); return; } N = list1.size(); if (N < 2) { sum.setUndefined(); return; } // set the density scale factor // default is 1; densityFactor == -1 means do not convert from frequency to density double densityFactor; if (useDensityGeo == null) { densityFactor = 1; } else if (!((GeoBoolean) useDensityGeo).getBoolean()) { densityFactor = -1; } else { densityFactor = (density != null) ? density.getDouble() : 1; if (densityFactor <= 0 && densityFactor != -1) { sum.setUndefined(); return; } } // list2 contains raw data // eg Histogram[{1,1.5,2,4},{1.0,1.1,1.1,1.2,1.7,1.7,1.8,2.2,2.5,4.0}] // problem: if N-1 = list2.size() then raw data is not assumed // fix for now is to check if other parameters are present, then it must be raw data if (N - 1 != list2.size() || useDensityGeo != null || isCumulative != null) { if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; } // fill in class boundaries for (int i = 0; i < N - 1; i++) { geo = list1.get(i); if (i == 0) { if (geo.isNumberValue()) a = (NumberValue) geo; else { sum.setUndefined(); return; } } if (geo.isGeoNumeric()) leftBorder[i] = ((GeoNumeric) geo).getDouble(); else { sum.setUndefined(); return; } } geo = list1.get(N - 1); if (geo.isNumberValue()) b = (NumberValue) geo; else { sum.setUndefined(); return; } leftBorder[N - 1] = ((GeoNumeric) geo).getDouble(); // zero frequencies for (int i = 0; i < N; i++) yval[i] = 0; // work out frequencies in each class //TODO: finish right histogram option for 2nd case below for (int i = 0; i < list2.size(); i++) { geo = list2.get(i); if (geo.isGeoNumeric()) datum = ((GeoNumeric) geo).getDouble(); else { sum.setUndefined(); return; } // if datum is outside the range, set undefined if (datum < leftBorder[0] || datum > leftBorder[N - 1]) { sum.setUndefined(); return; } if (!this.histogramRight) { // fudge to make the last boundary eg 10 <= x <= 20 // all others are 10 <= x < 20 double oldMaxBorder = leftBorder[N - 1]; leftBorder[N - 1] += Math.abs(leftBorder[N - 1] / 100000000); // check which class this datum is in for (int j = 1; j < N; j++) { if (Kernel.isGreater(leftBorder[j], datum)) { yval[j - 1]++; break; } } leftBorder[N - 1] = oldMaxBorder; } else { // fudge to make the first boundary eg 10 <= x <= 20 // all others are 10 < x <= 20 (HistogramRight) double oldMinBorder = leftBorder[0]; leftBorder[0] += Math.abs(leftBorder[0] / 100000000); // check which class this datum is in for (int j = 1; j < N; j++) { if (Kernel.isGreaterEqual(leftBorder[j], datum)) { yval[j - 1]++; break; } } leftBorder[0] = oldMinBorder; } } // turn frequencies into frequency densities // if densityFactor = -1 then do not convert frequency to density if (densityFactor != -1) for (int i = 1; i < N; i++) yval[i - 1] = densityFactor * yval[i - 1] / (leftBorder[i] - leftBorder[i - 1]); //convert to cumulative frequencies if cumulative option is set if (isCumulative != null && ((GeoBoolean) isCumulative).getBoolean()) { for (int i = 1; i < N; i++) yval[i] += yval[i - 1]; } // area of rectangles = total frequency * densityFactor sum.setValue(Math.abs(list2.size() * densityFactor)); // find maximum frequency // this is used by the stat dialogs freqMax = 0.0; for (int k = 0; k < yval.length; ++k) { freqMax = Math.max(yval[k], freqMax); } } else { // list2 contains the heights if (yval == null || yval.length < N) { yval = new double[N]; leftBorder = new double[N]; } for (int i = 0; i < N - 1; i++) { geo = list1.get(i); if (i == 0) { if (geo.isNumberValue()) a = (NumberValue) geo; else { sum.setUndefined(); return; } } if (geo.isGeoNumeric()) leftBorder[i] = ((GeoNumeric) geo).getDouble(); else { sum.setUndefined(); return; } geo = list2.get(i); if (geo.isGeoNumeric()) yval[i] = ((GeoNumeric) geo).getDouble(); else { sum.setUndefined(); return; } } yval[N - 1] = yval[N - 2]; geo = list1.get(N - 1); if (geo.isNumberValue()) b = (NumberValue) geo; else { sum.setUndefined(); return; } leftBorder[N - 1] = ((GeoNumeric) geo).getDouble(); cumSum = 0; for (int i = 1; i < N; i++) cumSum += (leftBorder[i] - leftBorder[i - 1]) * yval[i - 1]; // area of rectangles sum.setValue(cumSum); } // find maximum frequency // this is used by the stat dialogs freqMax = 0.0; for (int k = 0; k < yval.length; ++k) { freqMax = Math.max(yval[k], freqMax); } break; } } public String toString() { return getCommandDescription(); } /** * Returns true iff this is trapezoidal sum * @return true iff this is trapezoidal sums */ public boolean useTrapeziums() { switch (type) { case TYPE_TRAPEZOIDALSUM: return true; default: return false; } } /** * Returns true iff this is histogram * @return true iff this is histogram */ public boolean isHistogram() { switch (type) { case TYPE_HISTOGRAM: case TYPE_HISTOGRAM_DENSITY: return true; default: return false; } } /** * Returns type of the sum, see TYPE_* constants of this class * @return type of the sum */ public int getType() { return type; } /** * Prepares list1 and list2 for use with probability distribution bar charts */ private boolean prepareDistributionLists() { IntegerDistribution dist = null; int first = 0, last = 0; try { // get the distribution and the first, last list values for given distribution type switch (type) { case TYPE_BARCHART_BINOMIAL: if (!(p1geo.isDefined() && p2geo.isDefined())) return false; int n = (int) Math.round(p1.getDouble()); double p = p2.getDouble(); dist = new BinomialDistributionImpl(n, p); first = 0; last = n; break; case TYPE_BARCHART_PASCAL: if (!(p1geo.isDefined() && p2geo.isDefined())) return false; n = (int) Math.round(p1.getDouble()); p = p2.getDouble(); dist = new PascalDistributionImpl(n, p); first = 0; last = (int) Math.max(1, kernel.getXmax() + 1); break; case TYPE_BARCHART_ZIPF: if (!(p1geo.isDefined() && p2geo.isDefined())) return false; n = (int) Math.round(p1.getDouble()); p = p2.getDouble(); dist = new ZipfDistributionImpl(n, p); first = 0; last = n; break; case TYPE_BARCHART_POISSON: if (!p1geo.isDefined()) return false; double lambda = p1.getDouble(); dist = new PoissonDistributionImpl(lambda); first = 0; last = (int) Math.max(1, kernel.getXmax() + 1); break; case TYPE_BARCHART_HYPERGEOMETRIC: if (!(p1geo.isDefined() && p2geo.isDefined() && p3geo.isDefined())) return false; int pop = (int) p1.getDouble(); int successes = (int) p2.getDouble(); int sample = (int) p3.getDouble(); dist = new HypergeometricDistributionImpl(pop, successes, sample); first = Math.max(0, successes + sample - pop); last = Math.min(successes, sample); break; } // load class list and probability list loadDistributionLists(first, last, dist); } catch (Exception e) { Application.debug(e.getMessage()); return false; } return true; } /** * Utility method, creates and loads list1 and list2 with classes and probabilities for * the probability distribution bar charts */ private void loadDistributionLists(int first, int last, IntegerDistribution dist) throws Exception { boolean oldSuppress = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); if (list1 != null) list1.remove(); list1 = new GeoList(cons); if (list2 != null) list2.remove(); list2 = new GeoList(cons); double prob; double cumProb = 0; for (int i = first; i <= last; i++) { list1.add(new GeoNumeric(cons, i)); prob = dist.probability(i); cumProb += prob; if (isCumulative != null && ((GeoBoolean) isCumulative).getBoolean()) list2.add(new GeoNumeric(cons, cumProb)); else list2.add(new GeoNumeric(cons, prob)); } cons.setSuppressLabelCreation(oldSuppress); } }