/*
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 org.geogebra.common.kernel.algos;
import java.util.ArrayList;
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.distribution.BinomialDistribution;
import org.apache.commons.math3.distribution.HypergeometricDistribution;
import org.apache.commons.math3.distribution.IntegerDistribution;
import org.apache.commons.math3.distribution.PascalDistribution;
import org.apache.commons.math3.distribution.PoissonDistribution;
import org.apache.commons.math3.distribution.ZipfDistribution;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.arithmetic.MyDouble;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.optimization.ExtremumFinderI;
import org.geogebra.common.kernel.optimization.NegativeRealRootFunction;
import org.geogebra.common.util.debug.Log;
/**
* Superclass for lower/upper sum of function f in interval [a, b] with n
* intervals
*/
public abstract class AlgoFunctionAreaSums extends AlgoElement
implements DrawInformationAlgo {
// largest possible number of rectangles
private static final int MAX_RECTANGLES = 10000;
/**
* number of points used for checking that function is defined on the
* interval
**/
double CHECKPOINTS = 100;
// 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 SumType type;
public enum SumType {
/** Upper Rieeman sum **/
UPPERSUM,
/** Lower Rieman sum **/
LOWERSUM,
/** Left Rieman sum (Ulven: 09.02.11) **/
LEFTSUM,
/** Rectangle sum with divider for step interval (Ulven: 09.02.11) **/
RECTANGLESUM,
/** Trapezoidal sum **/
TRAPEZOIDALSUM,
/** Barchart from expression **/
BARCHART,
/** Barchart from raw data **/
BARCHART_RAWDATA,
/** Barchart from (values,frequencies) **/
BARCHART_FREQUENCY_TABLE,
/** Barchart from (values,frequencies) with given width **/
BARCHART_FREQUENCY_TABLE_WIDTH,
/**
* Histogram from(class boundaries, raw data) with default density = 1
* or Histogram from(class boundaries, frequencies) no density required
**/
HISTOGRAM,
/** Histogram from(class boundaries, raw data) with given density **/
HISTOGRAM_DENSITY,
/** barchart of a discrete probability distribution **/
BARCHART_BINOMIAL, BARCHART_PASCAL, BARCHART_POISSON, BARCHART_HYPERGEOMETRIC, BARCHART_BERNOULLI, BARCHART_ZIPF
}
// tolerance for parabolic interpolation
private static final double TOLERANCE = 1E-7;
private GeoFunction f; // input
private GeoNumberValue a, b, n, width, density, p1, p2, p3; // input
/**
* @return the p1
*/
public GeoNumberValue getP1() {
return p1;
}
/**
* @return the p2
*/
public GeoNumberValue getP2() {
return p2;
}
/**
* @return the p3
*/
public GeoNumberValue getP3() {
return p3;
}
private NumberValue d; // input: divider for Rectangle sum, 0..1
private GeoList list1, list2, list3; // 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 ExtremumFinderI 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() {
freqMax = 0;
for (int k = 0; k < yval.length; ++k) {
freqMax = Math.max(yval[k], freqMax);
}
return freqMax;
}
/**
* 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,
GeoNumberValue a, GeoNumberValue b, GeoNumberValue n,
GeoNumberValue d, SumType 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.setDrawable(true);
sum.setLabel(label);
}
public AlgoFunctionAreaSums(GeoFunction f, GeoNumberValue a,
GeoNumberValue b, GeoNumberValue n, GeoNumberValue d) {
super(f.cons, false);
this.type = SumType.RECTANGLESUM;
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.setDrawable(true);
}
/**
* 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,
GeoNumberValue a, GeoNumberValue b, GeoNumberValue n,
SumType 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.setDrawable(true);
sum.setLabel(label);
}
public AlgoFunctionAreaSums(GeoNumberValue a, GeoNumberValue b,
GeoNumberValue n, SumType type, double[] vals, double[] borders,
Construction cons1) {
super(cons1, 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,
GeoNumberValue a, GeoNumberValue b, GeoList list1) {
super(cons);
type = SumType.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.setDrawable(true);
sum.setLabel(label);
}
/**
* 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 = SumType.BARCHART_FREQUENCY_TABLE;
this.list1 = list1;
this.list2 = list2;
sum = new GeoNumeric(cons); // output
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
}
/**
* 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, GeoNumberValue width) {
this(cons, list1, list2, width);
sum.setLabel(label);
}
public AlgoFunctionAreaSums(Construction cons, GeoList list1, GeoList list2,
GeoNumberValue width) {
super(cons);
type = SumType.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);
}
/**
* 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 = SumType.BARCHART_RAWDATA;
this.list1 = list1;
this.n = n;
ngeo = n.toGeoElement();
sum = new GeoNumeric(cons); // output
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
sum.setLabel(label);
}
/**
* BarChart [<list of data>, <width>]
*
* @param cons
* @param list1
* @param n
*/
public AlgoFunctionAreaSums(Construction cons, GeoList list1,
GeoNumeric n) {
super(cons);
type = SumType.BARCHART_RAWDATA;
this.list1 = list1;
this.n = n;
ngeo = n.toGeoElement();
sum = new GeoNumeric(cons); // output
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
}
/**
* 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) {
this(cons, list1, list2, right);
sum.setLabel(label);
}
/**
* HISTOGRAM[ <list of class boundaries>, <list of heights> ] (no label)
*
* @param cons
* @param list1
* @param list2
* @param right
*/
public AlgoFunctionAreaSums(Construction cons, GeoList list1, GeoList list2,
boolean right) {
super(cons);
type = SumType.HISTOGRAM;
this.histogramRight = right;
this.list1 = list1;
this.list2 = list2;
sum = new GeoNumeric(cons); // output
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
}
public AlgoFunctionAreaSums(Construction cons, double[] vals,
double[] borders, int N) {
super(cons, false);
type = SumType.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,
GeoList list3, GeoBoolean useDensity, GeoNumeric density,
boolean right) {
this(cons, isCumulative, list1, list2, list3, useDensity, density,
right);
sum.setLabel(label);
}
public AlgoFunctionAreaSums(Construction cons, GeoBoolean isCumulative,
GeoList list1, GeoList list2, GeoList list3, GeoBoolean useDensity,
GeoNumeric density, boolean right) {
super(cons);
this.histogramRight = right;
type = SumType.HISTOGRAM_DENSITY;
this.isCumulative = isCumulative;
this.list1 = list1;
this.list2 = list2;
this.list3 = list3;
this.density = density;
if (density != null) {
densityGeo = density.toGeoElement();
}
this.useDensityGeo = useDensity;
sum = new GeoNumeric(cons); // output
setInputOutput(); // for AlgoElement
compute();
if (isCumulative != null && isCumulative.getBoolean()) {
yval[yval.length - 1] = 0.0;
}
sum.setDrawable(true);
}
public AlgoFunctionAreaSums(GeoBoolean isCumulative, GeoBoolean useDensity,
GeoNumeric density, double[] vals, double[] borders, int N) {
super(useDensity.getConstruction(), false);
type = SumType.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,
GeoNumberValue p1, GeoNumberValue p2, GeoNumberValue p3,
GeoBoolean isCumulative, SumType 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.setDrawable(true);
sum.setLabel(label);
if (yval == null) {
yval = new double[0];
leftBorder = new double[0];
}
}
protected AlgoFunctionAreaSums(GeoNumberValue p1, GeoNumberValue p2,
GeoNumberValue p3, GeoBoolean isCumulative, SumType type,
GeoNumberValue a, GeoNumberValue b, double[] vals, double[] borders,
int N, Construction cons) {
super(cons, 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;
}
@Override
final public boolean euclidianViewUpdate() {
compute(true);
return false;
}
// for AlgoElement
@Override
protected void setInputOutput() {
switch (type) {
case UPPERSUM:
case LOWERSUM:
case TRAPEZOIDALSUM:
case LEFTSUM: // Ulven: 09.02.11
input = new GeoElement[4];
input[0] = f;
input[1] = ageo;
input[2] = bgeo;
input[3] = ngeo;
break;
case 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 BARCHART:
input = new GeoElement[3];
input[0] = ageo;
input[1] = bgeo;
input[2] = list1;
break;
case BARCHART_FREQUENCY_TABLE:
input = new GeoElement[2];
input[0] = list1;
input[1] = list2;
break;
case BARCHART_FREQUENCY_TABLE_WIDTH:
input = new GeoElement[3];
input[0] = list1;
input[1] = list2;
input[2] = widthGeo;
break;
case BARCHART_RAWDATA:
input = new GeoElement[2];
input[0] = list1;
input[1] = ngeo;
break;
case HISTOGRAM:
case HISTOGRAM_DENSITY:
ArrayList<GeoElement> tempList = new ArrayList<GeoElement>();
if (isCumulative != null) {
tempList.add(isCumulative);
}
tempList.add(list1);
tempList.add(list2);
if (list3 != null) {
tempList.add(list3);
}
if (useDensityGeo != null) {
tempList.add(useDensityGeo);
}
if (densityGeo != null) {
tempList.add(densityGeo);
}
input = new GeoElement[tempList.size()];
input = tempList.toArray(input);
break;
case BARCHART_BERNOULLI:
case BARCHART_BINOMIAL:
case BARCHART_PASCAL:
case BARCHART_HYPERGEOMETRIC:
case BARCHART_POISSON:
case 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 final 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 GeoNumberValue getA() {
return a == null ? new GeoNumeric(cons, Double.NaN) : a;
}
/**
* Returns upper bound for sums
*
* @return upper bound for sums
*/
public GeoNumberValue getB() {
return b == null ? new GeoNumeric(cons, 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;
}
@Override
public final void compute() {
compute(false);
}
private void compute(boolean onlyZoom) {
GeoElement geo, geo2; // temporary variables
boolean isDefined = true;
// problem with Sequence[LowerSum[x^2, i, i + 1, 1], i, 1, 5] on file
// load
if (sum == null) {
sum = new GeoNumeric(cons);
}
switch (type) {
case LOWERSUM:
case UPPERSUM:
if (f == null || !(f.isDefined() && ageo.isDefined()
&& bgeo.isDefined() && ngeo.isDefined())) {
sum.setUndefined();
return;
}
UnivariateFunction fun = f.getUnivariateFunctionY();
double ad = a.getDouble();
double bd = b.getDouble();
if (!onlyZoom) {
isDefined = functionDefined(ad, bd);
} else {
isDefined = sum.isDefined();
}
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];
}
UnivariateFunction fmin = fun;
if (type == SumType.UPPERSUM)
{
fmin = new NegativeRealRootFunction(fun); // use -f to find
// maximum
}
double totalArea = 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
double visibleMin = Math.max(Math.min(ad, bd),
kernel.getViewsXMin(sum));
double visibleMax = Math.min(Math.max(ad, bd),
kernel.getViewsXMax(sum));
// subsample every 5 pixels
double noOfSamples = kernel.getApplication().countPixels(visibleMin,
visibleMax) / 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.value(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.value(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.value(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 == SumType.UPPERSUM) {
y = -y;
}
yval[i] = y;
// add to sum
totalArea += y;
}
// calc area of rectangles
sum.setValue(totalArea * STEP);
if (!isDefined) {
sum.setUndefined();
}
break;
case TRAPEZOIDALSUM:
case RECTANGLESUM:
case LEFTSUM:
if (f == null || !(f.isDefined() && ageo.isDefined()
&& bgeo.isDefined() && ngeo.isDefined())) {
sum.setUndefined();
return;
}
/* Rectanglesum needs extra treatment */
if ((type == SumType.RECTANGLESUM) && (!dgeo.isDefined())) { // extra
// parameter
sum.setUndefined();
} // if d parameter for rectanglesum
fun = f.getUnivariateFunctionY();
// lower bound
ad = a.getDouble();
// upper bound
bd = b.getDouble();
if (!onlyZoom) {
isDefined = functionDefined(ad, bd);
} else {
isDefined = sum.isDefined();
}
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
}
totalArea = 0;
int upperBound = type == SumType.TRAPEZOIDALSUM ? N + 1 : N;
for (int i = 0; i < upperBound; i++) { // N+1 for trapezoids
leftBorder[i] = ad + i * STEP;
/* Extra treatment for RectangleSum */
if (type == SumType.RECTANGLESUM) {
double dd = d.getDouble();
if ((dd >= 0) && (dd <= 1)) {
// make sure we don't get an overflow
// eg sqrt(1-1.00000000000001)
double xVal = Math.min(bd, leftBorder[i] + dd * STEP);
yval[i] = fun.value(xVal); // divider
// into
// step-interval
} else {
sum.setUndefined();
return;
} // if divider ok
} else {
yval[i] = fun.value(leftBorder[i]);
} // if
totalArea += yval[i];
}
// calc area of rectangles or trapezoids
if (type == SumType.TRAPEZOIDALSUM) {
totalArea -= (yval[0] + yval[N]) / 2;
} // if rectangles or trapezoids
// for (int i=0; i < N+1 ; i++) cumSum += yval[i];
sum.setValue(totalArea * STEP);
break;
case 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];
}
totalArea = 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;
}
totalArea += yval[i];
}
// calc area of rectangles
sum.setValue(totalArea * STEP);
if (!isDefined) {
sum.setUndefined();
}
break;
case 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 = (new GeoNumeric(cons, mini - gap));
b = (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, valueFrequency = 1;
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);
}
break;
case BARCHART_FREQUENCY_TABLE:
case BARCHART_BINOMIAL:
case BARCHART_POISSON:
case BARCHART_HYPERGEOMETRIC:
case BARCHART_PASCAL:
case BARCHART_ZIPF:
if (type != SumType.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 = (GeoNumberValue) ageo;
b = (GeoNumberValue) 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 = (GeoNumberValue) ageo;
b = (GeoNumberValue) 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 == SumType.BARCHART_FREQUENCY_TABLE) {
sum.setValue(area);
} else {
if (isCumulative != null
&& ((GeoBoolean) isCumulative).getBoolean()) {
sum.setValue(Double.POSITIVE_INFINITY);
} else {
sum.setValue(1);
}
sum.updateCascade();
}
break;
case 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 = (isCumulative != null
&& ((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 = (GeoNumberValue) ageo;
b = (GeoNumberValue) bgeo;
if (cumulative) {
sum.setValue(Double.POSITIVE_INFINITY);
} else {
sum.setValue(1);
}
sum.updateCascade();
return;
case 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 < 2) {
sum.setUndefined();
return;
}
start = ((GeoNumeric) (list1.get(0))).getDouble();
end = ((GeoNumeric) (list1.get(N - 2))).getDouble();
if (N == 2) {
// special case, one bar
step = 1;
} else {
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 = (GeoNumberValue) ageo;
b = (GeoNumberValue) 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 (MyDouble.isOdd(i)) {
// 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 HISTOGRAM:
case HISTOGRAM_DENSITY:
if (!list1.isDefined() || !list2.isDefined()) {
sum.setUndefined();
return;
}
N = list1.size();
if (N < 2) {
sum.setUndefined();
return;
}
boolean useFrequency = list3 != null;
if (useFrequency && !list3.isDefined()) {
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 instanceof GeoNumberValue) {
a = (GeoNumberValue) geo;
} else {
sum.setUndefined();
return;
}
}
if (geo.isGeoNumeric()) {
leftBorder[i] = ((GeoNumeric) geo).getDouble();
} else {
sum.setUndefined();
return;
}
}
geo = list1.get(N - 1);
if (geo instanceof GeoNumberValue) {
b = (GeoNumberValue) 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
valueFrequency = 1;
for (int i = 0; i < list2.size(); i++) {
geo = list2.get(i);
if (geo.isGeoNumeric()) {
datum = ((GeoNumeric) geo).getDouble();
} else {
sum.setUndefined();
return;
}
if (useFrequency) {
geo2 = list3.get(i);
if (geo2.isGeoNumeric()) {
valueFrequency = ((GeoNumeric) geo2).getDouble();
} else {
sum.setUndefined();
return;
}
}
// if negative frequency, set undefined
if (valueFrequency < 0) {
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] += valueFrequency;
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] += valueFrequency;
break;
}
}
leftBorder[0] = oldMinBorder;
}
}
} else { // list2 contains the heights
// make sure heights not rescaled #2579
densityFactor = -1;
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 instanceof GeoNumberValue) {
a = (GeoNumberValue) geo;
} else {
sum.setUndefined();
return;
}
}
if (geo instanceof NumberValue) {
leftBorder[i] = ((NumberValue) geo).getDouble();
} else {
sum.setUndefined();
return;
}
geo = list2.get(i);
if (geo instanceof NumberValue) {
yval[i] = ((NumberValue) geo).getDouble();
} else {
sum.setUndefined();
return;
}
}
// yval[N - 1] = yval[N - 2];
geo = list1.get(N - 1);
if (geo instanceof GeoNumberValue) {
b = (GeoNumberValue) geo;
} else {
sum.setUndefined();
return;
}
leftBorder[N - 1] = ((GeoNumeric) geo).getDouble();
}
// 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];
}
yval[N - 1] = 0.0;
}
// 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]);
}
}
totalArea = 0;
for (int i = 1; i < N; i++) {
totalArea += (leftBorder[i] - leftBorder[i - 1]) * yval[i - 1];
}
// area of rectangles
sum.setValue(totalArea);
break;
}
}
private boolean functionDefined(double ad, double bd) {
double interval = (bd - ad) / CHECKPOINTS;
for (double temp = ad; (interval > 0 && temp < bd)
|| (interval < 0 && temp > bd); temp += interval) {
double val = f.value(temp);
if (Double.isNaN(val) || Double.isInfinite(val)) {
return false;
}
}
double val = f.value(bd);
if (Double.isNaN(val) || Double.isInfinite(val)) {
return false;
}
return true;
}
/**
* Returns true iff this is trapezoidal sum
*
* @return true iff this is trapezoidal sums
*/
public boolean useTrapeziums() {
switch (type) {
case TRAPEZOIDALSUM:
return true;
default:
return false;
}
}
/**
* Returns true iff this is histogram
*
* @return true iff this is histogram
*/
public boolean isHistogram() {
switch (type) {
case HISTOGRAM:
case HISTOGRAM_DENSITY:
return true;
default:
return false;
}
}
/**
* Returns type of the sum, see TYPE_* constants of this class
*
* @return type of the sum
*/
public SumType 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) {
default:
// do nothing
break;
case BARCHART_BINOMIAL:
if (!(p1geo.isDefined() && p2geo.isDefined())) {
return false;
}
int n1 = (int) Math.round(p1.getDouble());
double p = p2.getDouble();
dist = new BinomialDistribution(n1, p);
first = 0;
last = n1;
break;
case BARCHART_PASCAL:
if (!(p1geo.isDefined() && p2geo.isDefined())) {
return false;
}
n1 = (int) Math.round(p1.getDouble());
p = p2.getDouble();
dist = new PascalDistribution(n1, p);
first = 0;
last = (int) Math.max(1, (kernel).getXmax() + 1);
break;
case BARCHART_ZIPF:
if (!(p1geo.isDefined() && p2geo.isDefined())) {
return false;
}
n1 = (int) Math.round(p1.getDouble());
p = p2.getDouble();
dist = new ZipfDistribution(n1, p);
first = 0;
last = n1;
break;
case BARCHART_POISSON:
if (!p1geo.isDefined()) {
return false;
}
double lambda = p1.getDouble();
dist = new PoissonDistribution(lambda);
first = 0;
last = (int) Math.max(1, kernel.getXmax() + 1);
break;
case 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 HypergeometricDistribution(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) {
Log.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);
}
@Override
public void update() {
if (doStopUpdateCascade()) {
return;
}
// do not update input random numbers without label
// counter++;
// startTime = System.currentTimeMillis();
// compute output from input
compute();
// endTime = System.currentTimeMillis();
// computeTime += (endTime - startTime);
// startTime = System.currentTimeMillis();
// update dependent objects
for (int i = 0; i < getOutputLength(); i++) {
getOutput(i).update();
}
// endTime = System.currentTimeMillis();
// updateTime += (endTime - startTime );
}
/*
* This should apply to every subclass. In case it does not, a case per case
* should be used. It produces a GeoNumeric, so beware GeoNumeric will be
* treated differently than points.
*/
}