/* 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. */ /* * AlgoSlope.java * * Created on 30. August 2001, 21:37 */ package org.geogebra.common.kernel.algos; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.arithmetic.ReplaceChildrenByValues; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.CasEvaluableFunction; import org.geogebra.common.kernel.geos.GeoElement; 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.kernelND.GeoElementND; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; /** * Algorithm for the Sequence[ expression of var, var, from-value, to-value, * step ] command. * * @author Markus Hohenwarter */ public class AlgoSequence extends AlgoElement { private enum SequenceType { SIMPLE, RANGE, FULL } private GeoElementND expression; // input expression dependent on var private GeoNumeric var; // input: local variable private GeoNumberValue var_from, var_to, var_step; private GeoElement var_from_geo, var_to_geo, var_step_geo; private GeoList list; // output private SequenceType type; private double last_from = Double.MIN_VALUE, last_to = Double.MIN_VALUE, last_step = Double.MIN_VALUE; private boolean expIsFunctionOrCurve, isEmpty; private AlgoElement expressionParentAlgo; // we need to check that some Object[] reference didn't cause infinite // update cycle private boolean updateRunning = false; /** * Creates a new algorithm to create a sequence of objects that form a list. * * @param cons * construction * @param label * label for the list * @param expression * expression * @param var * variable * @param var_from * lower bound * @param var_to * upper bound * @param var_step * step */ public AlgoSequence(Construction cons, String label, GeoElementND expression, GeoNumeric var, GeoNumberValue var_from, GeoNumberValue var_to, GeoNumberValue var_step) { this(cons, expression, var, var_from, var_to, var_step); list.setLabel(label); } /** * Creates a new algorithm to create a sequence of objects that form a list. * * @param cons * construction * @param expression * expression * @param var * variable * @param var_from * lower bound * @param var_to * upper bound * @param var_step * step */ public AlgoSequence(Construction cons, GeoElementND expression, GeoNumeric var, GeoNumberValue var_from, GeoNumberValue var_to, GeoNumberValue var_step) { super(cons); this.expression = expression; this.var = var; this.var_from = var_from; var_from_geo = var_from.toGeoElement(); this.var_to = var_to; var_to_geo = var_to.toGeoElement(); this.var_step = var_step; if (var_step != null) { var_step_geo = var_step.toGeoElement(); } expressionParentAlgo = expression.getParentAlgorithm(); expIsFunctionOrCurve = expression instanceof CasEvaluableFunction; type = SequenceType.FULL; // Application.debug("expression: " + expression); // Application.debug(" parent algo: " + // expression.getParentAlgorithm()); // // Application.debug(" parent algo input is var?: " + // (expression.getParentAlgorithm().getInput()[0] == var)); // Application.debug(" variable: " + var); // Application.debug(" expIsGeoFunction: " + expIsGeoFunction); list = new GeoList(cons); setInputOutput(); // for AlgoElement compute(); } /** * Creates simple sequence start..upTo * * @param cons * construction * @param label * label * @param from * lower bound * @param upTo * upper bound */ public AlgoSequence(Construction cons, String label, GeoNumberValue from, GeoNumberValue upTo) { super(cons); type = SequenceType.RANGE; var_from = from; var_from_geo = var_from.toGeoElement(); var_to = upTo; var_to_geo = var_to.toGeoElement(); list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } /** * Creates simple sequence 1..upTo * * @param cons * construction * @param label * label * @param upTo * upper bound */ public AlgoSequence(Construction cons, String label, GeoNumberValue upTo) { super(cons); type = SequenceType.SIMPLE; var_from = new GeoNumeric(cons, 1); var_from_geo = (GeoElement) var_from; var_to = upTo; var_to_geo = var_to.toGeoElement(); list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } @Override public Commands getClassName() { return Commands.Sequence; } // for AlgoElement @Override protected void setInputOutput() { switch (type) { case SIMPLE: input = new GeoElement[1]; input[0] = var_to_geo; list.setTypeStringForXML(StringUtil .toLowerCase(var_to_geo.getGeoClassType().xmlName)); break; case RANGE: input = new GeoElement[2]; input[0] = var_from_geo; input[1] = var_to_geo; list.setTypeStringForXML(StringUtil .toLowerCase(var_to_geo.getGeoClassType().xmlName)); break; default: // make sure that x(Element[list,1]) will work even if the output // list's length is zero list.setTypeStringForXML(expression.getXMLtypeString()); int len = var_step == null ? 4 : 5; input = new GeoElement[len]; input[0] = expression.toGeoElement(); input[1] = var; input[2] = var_from_geo; input[3] = var_to_geo; if (len == 5) { input[4] = var_step_geo; } break; } setOutputLength(1); setOutput(0, list); setDependencies(); // done by AlgoElement } /** * Returns contents of input array excluding var (var is not input object, * but must be in input array because of GetCommandDescription method). see * ticket #72 2010-05-13 null pointer error fixed * * @author Zbynek Konecny * @version 2010-05-13 */ @Override public GeoElement[] getInputForUpdateSetPropagation() { if (!type.equals(SequenceType.FULL)) { return input; } // if expression and var are the same, skip both int skip = expression == var ? 2 : 1; GeoElement[] realInput = new GeoElement[input.length - skip]; if (skip == 1) { realInput[0] = expression.toGeoElement(); } realInput[2 - skip] = var_from_geo; realInput[3 - skip] = var_to_geo; if (input.length == 5) { realInput[4 - skip] = var_step_geo; } return realInput; } /** * Returns list of all contained elements. * * @return list of elements */ GeoList getList() { return list; } @Override public final void compute() { switch (type) { case SIMPLE: computeSimple(); return; case RANGE: computeRange(); return; } if (updateRunning) { return; } updateRunning = true; for (int i = 1; i < input.length; i++) { if (input[i] != var && !input[i].isDefined()) { // don't check the // var itself (maybe // undefined at last // loop) list.setUndefined(); updateRunning = false; return; } } list.setDefined(true); // create sequence for expression(var) by changing var according to the // given range double from = var_from.getDouble(); double to = var_to.getDouble(); double step = var_step == null ? 1 : var_step.getDouble(); isEmpty = (to - from) * step <= -Kernel.MIN_PRECISION; // an update may be necessary because another variable in expression // has changed. However, the range (from, to, step) may not have // changed: // in this case it is much more efficient not to create all objects // for the list again, but just to set their new values boolean setValuesOnly = (from == last_from && to == last_to && step == last_step); // setValues does not work for functions setValuesOnly = setValuesOnly && !expIsFunctionOrCurve; // avoid label creation, might happen e.g. in boolean oldSuppressLabels = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); // update list if (setValuesOnly) { updateListItems(from, to, step); } else { createNewList(from, to, step); } // revert label creation setting cons.setSuppressLabelCreation(oldSuppressLabels); updateRunning = false; } // use doubles // ef Sequence[9007199254000027, 9007199254000187] private void computeRange() { double from = Math.round(var_from.getDouble()); double to = Math.round(var_to.getDouble()); if (from > MyMath.LARGEST_INTEGER || from < -MyMath.LARGEST_INTEGER || to > MyMath.LARGEST_INTEGER || to < -MyMath.LARGEST_INTEGER) { list.setUndefined(); return; } list.clear(); // also see Operation.java case Sequence: if (from < to) { // increasing list for (double k = from; k <= to; k++) { list.addNumber(k, null); } } else { // decreasing list for (double k = from; k >= to; k--) { list.addNumber(k, null); } } } private void computeSimple() { int to = (int) Math.round(var_to.getDouble()); if (last_to < to) { for (int k = (int) last_to; k < to; k++) { if (k >= 0) { list.addNumber(k + 1, null); } } } if (last_to > to) { for (int k = (int) last_to; k > to; k--) { if (k >= 1) { GeoElement ge = list.get(k - 1); ge.remove(); list.remove(k - 1); } } } last_to = to; } private void createNewList(double from, double to, double step) { // clear list if defined int i = 0; int oldListSize = list.size(); list.clear(); if (!isEmpty) { // needed capacity if (Double.isInfinite((to - from) / step)) { list.setUndefined(); return; } int n = (int) Math.ceil((to - from) / step) + 1; list.ensureCapacity(n); // create the sequence double currentVal = from; while ((step > 0 && currentVal <= to + Kernel.MIN_PRECISION) || (step < 0 && currentVal >= to - Kernel.MIN_PRECISION)) { // check we haven't run out of memory if (kernel.getApplication().freeMemoryIsCritical()) { long mem = kernel.getApplication().freeMemory(); list.clearCache(); kernel.initUndoInfo(); // clear all undo info Log.debug( "AlgoSequence aborted: free memory reached " + mem); return; } // set local var value updateLocalVar(currentVal); addElement(i); currentVal += step; if (Kernel.isInteger(currentVal)) { currentVal = Math.round(currentVal); } i++; } } // if the old list was longer than the new one // we need to set some cached elements to undefined for (int k = oldListSize - 1; k >= i; k--) { GeoElement oldElement = list.getCached(k); oldElement.setUndefined(); oldElement.update(); } // remember current values last_from = from; last_to = to; last_step = step; } private void addElement(int i) { // only add new objects GeoElement listElement = null; int cacheListSize = list.getCacheSize(); if (i < cacheListSize) { // we reuse existing list element from cache listElement = list.getCached(i); if (expIsFunctionOrCurve) { // for functions we always need a new element listElement.setParentAlgorithm(null); // replace old list element by a new one listElement = createNewListElement(); } } else { // create new list element listElement = createNewListElement(); } // copy current expression value to listElement if (!expIsFunctionOrCurve) { listElement.set(expression); if (listElement.isGeoList()) { ((GeoList) listElement).replaceChildrenByValues(var); } copyDrawAlgo(listElement); } // set the value of our element listElement.update(); list.add(listElement); } private void copyDrawAlgo(GeoElement listElement) { AlgoElement drawAlgo = expression.getDrawAlgorithm(); if (listElement instanceof GeoNumeric && drawAlgo instanceof DrawInformationAlgo) { DrawInformationAlgo algoCopy = ((DrawInformationAlgo) drawAlgo) .copy(); if (algoCopy instanceof ReplaceChildrenByValues) { ((ReplaceChildrenByValues) algoCopy) .replaceChildrenByValues(var); } listElement.setDrawAlgorithm(algoCopy); listElement.setEuclidianVisible(true); } } private GeoElement createNewListElement() { GeoElement listElement = expression.copyInternal(cons); listElement.setParentAlgorithm(this); listElement.setConstructionDefaults(); listElement.setUseVisualDefaults(false); // functions and curves use the local variable var // so we have to replace var and all dependent objects of var // by their current values if (expIsFunctionOrCurve) { // GeoFunction if (listElement instanceof CasEvaluableFunction) { CasEvaluableFunction f = (CasEvaluableFunction) listElement; f.replaceChildrenByValues(var); } } return listElement; } private void updateListItems(double from, double to, double step) { if (isEmpty || list.size() == 0) { return; } double currentVal = from; int i = 0; while ((step > 0 && currentVal <= to + Kernel.MIN_PRECISION) || (step < 0 && currentVal >= to - Kernel.MIN_PRECISION)) { GeoElement listElement = list.get(i); // check we haven't run out of memory if (kernel.getApplication().freeMemoryIsCritical()) { long mem = kernel.getApplication().freeMemory(); list.clearCache(); kernel.initUndoInfo(); // clear all undo info Log.debug("AlgoSequence aborted: free memory reached " + mem); return; } // set local var value updateLocalVar(currentVal); // copy expression value to listElement // if it's undefined, just copy the undefined property if (expression.isDefined()) { listElement.set(expression); if (listElement.isGeoList()) { ((GeoList) listElement).replaceChildrenByValues(var); } } else { listElement.setUndefined(); } copyDrawAlgo(listElement); listElement.update(); currentVal += step; if (Kernel.isInteger(currentVal)) { currentVal = Math.round(currentVal); } i++; } } /** * Sets value of the local loop variable of the sequence and updates all * it's dependencies until we reach the sequence algo. */ private void updateLocalVar(double varVal) { // set local variable to given value var.setValue(varVal); // update var's algorithms until we reach expression if (expressionParentAlgo != null) { // update all dependent algorithms of the local variable var this.setStopUpdateCascade(true); // needed for eg Sequence[If[liste1(i) < a boolean oldLabelStatus = cons.isSuppressLabelsActive(); kernel.getConstruction().setSuppressLabelCreation(true); var.getAlgoUpdateSet().updateAllUntil(expressionParentAlgo); kernel.getConstruction().setSuppressLabelCreation(oldLabelStatus); this.setStopUpdateCascade(false); expressionParentAlgo.update(); } } }