/* 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 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.GeoFunction; import org.geogebra.common.kernel.geos.GeoFunctionNVar; 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.util.debug.Log; /** * Iteration[ f(x), x0, n ] * * IterationList[ f(A), A, {A_lis_val}, n ] * * @author Markus Hohenwarter * @version 15-07-2007 */ public class AlgoIterationList extends AlgoElement { private GeoFunction f; // input private GeoFunctionNVar fNVar; private GeoNumberValue startValue, n; private GeoList startValues; private GeoElement startValueGeo, nGeo; private GeoList list; // output private GeoElement expression; // input expression dependent on var private GeoElement[] vars; // input: local variable private int varCount; private GeoList[] over; private boolean expIsFunctionOrCurve, isEmpty; private AlgoElement expressionParentAlgo; enum Type { /** u(n+1)=f(u(n)) */ SIMPLE, /** u(n+1)=f(u(n),n) */ DOUBLE, /** general type: deeper dependency, arbitrary type */ DEFAULT } private Type type = Type.DEFAULT; // we need to check that some Object[] reference didn't cause infinite // update cycle private boolean updateRunning = false; private int iterationsOld = -1; /** * @param cons * construction * @param label * list label * @param f * function - first argument of IterationList * @param startValue * start value for the function * @param n * number of iterations */ public AlgoIterationList(Construction cons, String label, GeoFunction f, GeoNumberValue startValue, GeoNumberValue n) { super(cons); this.f = f; this.startValue = startValue; startValueGeo = startValue.toGeoElement(); this.n = n; nGeo = n.toGeoElement(); type = Type.SIMPLE; list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } /** * * @param cons * construction * @param label * label * @param fNVar * f(n,u(n)) * @param startValues * {n0,u0} * @param n * number of iterations */ public AlgoIterationList(Construction cons, String label, GeoFunctionNVar fNVar, GeoList startValues, GeoNumberValue n) { super(cons); this.fNVar = fNVar; this.startValueGeo = this.startValues = startValues; this.n = n; nGeo = n.toGeoElement(); type = Type.DOUBLE; list = new GeoList(cons); setInputOutput(); compute(); list.setLabel(label); } /** * Creates a new algorithm to create a sequence of objects that form a list. * * @param cons * construction * @param expression * expression first argument of IterationList * @param vars * variables * @param over * lists from which the variables should be taken * @param n * number of iterations */ public AlgoIterationList(Construction cons, GeoElement expression, GeoElement[] vars, GeoList[] over, GeoNumberValue n) { super(cons); this.expression = expression; this.vars = vars; this.over = over; this.n = n; this.nGeo = n.toGeoElement(); type = Type.DEFAULT; varCount = vars.length; expressionParentAlgo = expression.getParentAlgorithm(); expIsFunctionOrCurve = expression instanceof CasEvaluableFunction; list = new GeoList(cons); setInputOutput(); // for AlgoElement compute(); } @Override public Commands getClassName() { return Commands.IterationList; } @Override protected void setInputOutput() { switch (type) { case SIMPLE: simpleDependency(f); break; case DOUBLE: simpleDependency(fNVar); // done by AlgoElement break; case DEFAULT: default: input = new GeoElement[3 + varCount]; input[0] = expression; for (int i = 0; i < varCount; i++) { input[i + 1] = vars[i]; } input[1 + varCount] = over[0]; input[2 + varCount] = nGeo; setOutputLength(1); setOutput(0, list); list.setTypeStringForXML(expression.getXMLtypeString()); setDependencies(); // done by AlgoElement break; } } private void simpleDependency(GeoElement f2) { input = new GeoElement[3]; input[0] = f2; input[1] = startValueGeo; input[2] = nGeo; super.setOutputLength(1); super.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). */ @Override public GeoElement[] getInputForUpdateSetPropagation() { switch (type) { case SIMPLE: case DOUBLE: return super.getInputForUpdateSetPropagation(); case DEFAULT: default: GeoElement[] realInput = new GeoElement[3]; realInput[0] = expression; realInput[1] = over[0]; realInput[2] = nGeo; return realInput; } } /** * @return resulting list */ public GeoList getResult() { return list; } @Override public final void compute() { switch (type) { case SIMPLE: computeSimple(); return; case DOUBLE: computeDouble(); return; case DEFAULT: default: // done below break; } if (updateRunning) { return; } updateRunning = true; for (int i = 2; i < input.length - 1; i += 2) { if (!input[i].isDefined()) { list.setUndefined(); updateRunning = false; iterationsOld = -1; return; } } list.setDefined(true); int iterations = (int) Math.round(n.getDouble()); if (iterations < 0 || varCount > over[0].size()) { list.setUndefined(); updateRunning = false; iterationsOld = -1; return; } isEmpty = over[0].size() == 0; boolean setValuesOnly = iterations == iterationsOld; setValuesOnly = setValuesOnly && !expIsFunctionOrCurve; boolean oldSuppressLabels = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); // update list if (setValuesOnly) { updateListItems(); } else { createNewList(); } this.iterationsOld = iterations; // revert label creation setting cons.setSuppressLabelCreation(oldSuppressLabels); updateRunning = false; } private void createNewList() { int iterations = (int) Math.round(n.getDouble()); int i = Math.min(over[0].size(), iterations); int oldListSize = list.size(); list.clear(); for (int j = 0; j < over[0].size() && j < iterations + 1; j++) { list.add(over[0].get(j).copyInternal(cons)); if (j + 1 < varCount) { vars[j + 1].set(over[0].get(j)); } } if (iterations + 1 <= over[0].size()) { return; } if (!isEmpty) { // needed capacity list.ensureCapacity(((int) Math.round(n.getDouble())) + 1); // create the sequence int listSize = ((int) Math.round(n.getDouble())) + 1; while (i < listSize) { // 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("AlgoIterationList aborted: free memory reached " + mem); return; } // set local var value updateLocalVar(i, list.get(i - 1)); addElement(i); 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(); } } 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); listElement.doRemove(); // replace old list element by a new one listElement = createNewListElement(); } } else { // create new list element listElement = createNewListElement(); } // return early if it's the first element - we will add the start // position to this if (i == 0) { listElement.set(over[0].get(0)); listElement.update(); list.add(listElement); return; } // copy current expression value to listElement if (!expIsFunctionOrCurve) { listElement.set(expression); if (listElement.isGeoList()) { for (int j = 0; j < varCount; j++) { ((GeoList) listElement).replaceChildrenByValues(vars[j]); } } 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) { for (int j = 0; j < varCount; j++) { ((ReplaceChildrenByValues) algoCopy) .replaceChildrenByValues(vars[j]); } } 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 fun = (CasEvaluableFunction) listElement; for (int i = 0; i < varCount; i++) { fun.replaceChildrenByValues(vars[i]); } } } return listElement; } private void updateListItems() { if (isEmpty) { return; } // int currentVal = 0; int listSize = (int) Math.round(n.getDouble()) + 1; int i = over[0].size(); for (int j = 0; j < over[0].size() && j < listSize; j++) { list.get(j).set(over[0].get(j)); if (j + 1 < vars.length) { vars[j + 1].set(over[0].get(j)); } } if (over[0].size() >= listSize) { return; } while (i < listSize) { 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("AlgoIterationList aborted: free memory reached " + mem); return; } // set local var value // updateLocalVar(currentVal); updateLocalVar(i, list.get(i - 1)); Log.debug(expression + ""); // copy expression value to listElement // if it's undefined, just copy the undefined property if (expression.isDefined()) { listElement.set(expression); if (listElement.isGeoList()) { for (int j = 0; j < varCount; j++) { ((GeoList) listElement) .replaceChildrenByValues(vars[j]); } } } else { listElement.setUndefined(); } copyDrawAlgo(listElement); listElement.update(); i++; } } /** * Sets value of the local loop variable of the sequence and updates all * it's dependencies until we reach the main algo. */ private void updateLocalVar(int index, GeoElement listElement) { // set local variable to given value if (index == 0) { return; } for (int i = 0; i < varCount - 1; i++) { vars[i].set(vars[i + 1]); } vars[varCount - 1].set(listElement); // update var's algorithms until we reach expression if (expressionParentAlgo != null) { // update all dependent algorithms of the local variable var this.setStopUpdateCascade(true); for (int i = 0; i < varCount; i++) { vars[i].getAlgoUpdateSet().updateAllUntil(expressionParentAlgo); } this.setStopUpdateCascade(false); expressionParentAlgo.update(); } } private void computeSimple() { list.setDefined(true); for (int i = 0; i < input.length; i++) { if (!input[i].isDefined()) { list.setUndefined(); return; } } // number of iterations list.clear(); int iterations = (int) Math.round(n.getDouble()); if (iterations < 0) { list.setUndefined(); return; } // perform iteration f(f(f(...(startValue)))) // and fill list with all intermediate results double val = startValue.getDouble(); setListElement(0, val); for (int i = 0; i < iterations; i++) { val = f.value(val); setListElement(i + 1, val); } } private void computeDouble() { list.setDefined(true); for (int i = 0; i < input.length; i++) { if (!input[i].isDefined()) { list.setUndefined(); return; } } // number of iterations int iterations = (int) Math.round(n.getDouble()); if (iterations < 0) { list.setUndefined(); return; } // check if we have 2 start values: integer and double if (startValues.size() != 2) { list.setUndefined(); return; } GeoElement startValue1 = startValues.get(0); if (!(startValue1 instanceof GeoNumberValue)) { list.setUndefined(); return; } double nUdouble = ((GeoNumberValue) startValue1).getDouble(); int nU = (int) Math.round(nUdouble); if (!Kernel.isEqual(nU, nUdouble)) { list.setUndefined(); return; } GeoElement startValue2 = startValues.get(1); if (!(startValue2 instanceof GeoNumberValue)) { list.setUndefined(); return; } double u = ((GeoNumberValue) startValue2).getDouble(); // perform iterations u(n+1)=f(n,u(n)) list.clear(); setListElement(0, u); for (int i = 0; i < iterations; i++) { u = fNVar.evaluate(nU, u); setListElement(i + 1, u); nU++; } } private void setListElement(int index, double value) { GeoNumeric listElement; if (index < list.getCacheSize()) { // use existing list element listElement = (GeoNumeric) list.getCached(index); } else { // create a new list element listElement = new GeoNumeric(cons); listElement.setParentAlgorithm(this); listElement.setConstructionDefaults(); listElement.setUseVisualDefaults(false); } list.add(listElement); listElement.setValue(value); } }