geogebra.kernel.AlgoFunctionAreaSums.java Source code

Java tutorial

Introduction

Here is the source code for geogebra.kernel.AlgoFunctionAreaSums.java

Source

/* 
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);

    }

}