package org.geogebra.common.kernel.parser.cashandlers; import org.geogebra.common.kernel.CASException; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.arithmetic.Command; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.GetItem; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.MyList; import org.geogebra.common.kernel.arithmetic.MyNumberPair; import org.geogebra.common.kernel.arithmetic.MyVecNode; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode; import org.geogebra.common.kernel.commands.CmdIf; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.debug.Log; /** * Handles special Giac commands to distinguish them from user defined functions * in the Parser. * * Adapted from CommandDispatcherMPReduce * * @author Michael */ public class CommandDispatcherGiac { /** * Enum for special commands that may be returned by Giac. */ public enum GiacCommands { /** when aka If[] */ when(Operation.NO_OPERATION), /** gamma regularized */ igamma(Operation.NO_OPERATION), /** derivative */ diff(Operation.DERIVATIVE), /** bounded_function */ bounded_function(Operation.NO_OPERATION), /** integral */ integrate(Operation.INTEGRAL), /** rootof */ rootof(Operation.NO_OPERATION), /** exact (convert to fraction) */ exact(Operation.NO_OPERATION), /** psi */ Psi(Operation.PSI), /** sine integral */ Si(Operation.SI), /** cosine integral */ Ci(Operation.CI), /** exp integral */ Ei(Operation.EI), /** Reimann-Zeta function */ Zeta(Operation.ZETA), /** Beta function */ Beta(Operation.NO_OPERATION), /** Gamma function */ Gamma(Operation.GAMMA), /** fractional part */ fPart(Operation.FRACTIONAL_PART), /** fractional part */ conj(Operation.CONJUGATE), /** imaginary part */ im(Operation.IMAGINARY), /** real part */ re(Operation.REAL), /** surd(a,b)=bth root of a */ surd(Operation.NROOT), /** sqrt */ sqrt(Operation.SQRT), /** sign */ sign(Operation.SGN), /** sine */ sin(Operation.SIN), /** cosine */ cos(Operation.COS), /** tan */ tan(Operation.TAN), /** asin */ asin(Operation.ARCSIN), /** acos */ acos(Operation.ARCCOS), /** atan */ atan(Operation.ARCTAN), /** hyperbolic sine */ sinh(Operation.SINH), /** hyperbolic cos */ cosh(Operation.COSH), /** hyperbolic tan */ tanh(Operation.TANH), /** sec */ sec(Operation.SEC), /** cosec */ csc(Operation.CSC), /** cot */ cot(Operation.COT), /** ln */ ln(Operation.LOG), /** exp */ exp(Operation.EXP), /** abs */ abs(Operation.ABS), /** erf */ erf(Operation.ERF), /** symbolic x coord */ xcoord(Operation.XCOORD), /** symbolic y coord */ ycoord(Operation.YCOORD), /** symbolic z coord */ zcoord(Operation.ZCOORD), /** symbolic x coord */ xcoordsymb(Operation.XCOORD), /** symbolic y coord */ ycoordsymb(Operation.YCOORD), /** symbolic z coord */ zcoordsymb(Operation.ZCOORD), /** alt(x) */ altsymb(Operation.ALT), /** GeoGebra vector */ ggbvect(Operation.NO_OPERATION), /** symbolic sum */ sum(Operation.SUM), /** If[] */ piecewise(Operation.IF_ELSE), /** * eg hyperplan({3,5,-1},point[0,0,-37/10]) */ point(Operation.NO_OPERATION), /** * returned from plane(4*x + 3*y + z = 1) * * eg hyperplan({3,5,-1},point[0,0,-37/10]) hyperplan({3,5,-1},{0,0,1}) */ hyperplan(Operation.NO_OPERATION), /** if returned from Giac -> error */ laplace(Operation.NO_OPERATION), /** if returned from Giac -> error */ ilaplace(Operation.NO_OPERATION), /** if returned from Giac -> error */ invlaplace(Operation.NO_OPERATION), /** returned by eg BinomialDist[72,1/7,n,true] -> error */ binomial_cdf(Operation.NO_OPERATION), /** eg binomial_icdf(23,0.9285714285714,-9.999988812822E-013) */ binomial_icdf(Operation.NO_OPERATION), /** if returned from Giac -> error */ fisher_cdf(Operation.NO_OPERATION), /** if returned from Giac -> error */ normald_cdf(Operation.NO_OPERATION), /** if returned from Giac -> error */ student_cdf(Operation.NO_OPERATION), /** if returned from Giac -> error */ chisquare_cdf(Operation.NO_OPERATION), /** polar coordinate */ ggb_ang(Operation.NO_OPERATION), /** if returned from Giac -> error */ poly1(Operation.NO_OPERATION), /** fsolve, shouldn't get returned */ fsolve(Operation.NO_OPERATION), /** solve, shouldn't get returned */ solve(Operation.NO_OPERATION), /** arbitrary constant */ arbconst(Operation.ARBCONST), /** arbitrary integer (comes from trig equations) */ arbint(Operation.ARBINT), /** floor */ floor(Operation.FLOOR), /** ceiling */ ceiling(Operation.CEIL), /** rand(n) gives random integer from [0,n-1] */ rand(Operation.NO_OPERATION), ; private Operation op; private GiacCommands(Operation op) { this.op = op; } /** * @return single variable operation */ public Operation getOperation() { return op; } } /** * @return The result of a special MPReduce command for the given argument * list as needed by the Parser. Returns null when nothing was done. * * @param cmdName * name of the Giac command to process, see * CommandDispatcherGiac.commands * @param args * list of command arguments * @param kernel * kernel */ public static ExpressionNode processCommand(String cmdName, GetItem args, Kernel kernel) { GiacCommands cmd = null; try { cmd = GiacCommands.valueOf(cmdName); } catch (Exception Ex) { Log.error( "Unknown CAS command " + cmdName + ", arguments: " + args); return null; } try { ExpressionValue ret = null; switch (cmd) { case sum: ret = new ExpressionNode(kernel, new MyNumberPair(kernel, args.getItem(0), args.getItem(1)), Operation.SUM, new MyNumberPair(kernel, args.getItem(2), args.getItem(3))); break; case piecewise: if (args.getLength() < 3) { // eg Integral[Function[x^2,-2,3]] return new ExpressionNode(kernel, Double.NaN); } return CmdIf.expandIf(kernel, args); case exact: // just return argument ret = new ExpressionNode(kernel, args.getItem(0)); break; case Psi: if (args.getLength() == 1) { // Psi(x) -> psi(x) ret = new ExpressionNode(kernel, args.getItem(0), Operation.PSI, null); } else { // swap arguments // e.g. Psi(x,3) -> polyGamma(3,x) ret = new ExpressionNode(kernel, args.getItem(1), Operation.POLYGAMMA, args.getItem(0)); } break; case point: switch (args.getLength()) { case 2: double a = args.getItem(0).evaluateDouble(); double b = args.getItem(1).evaluateDouble(); return new ExpressionNode(kernel, new GeoPoint(kernel.getConstruction(), a, b, 1)); case 3: a = args.getItem(0).evaluateDouble(); b = args.getItem(1).evaluateDouble(); double c = args.getItem(2).evaluateDouble(); GeoElementND point = kernel.getManager3D().Point3D(a, b, c, false); return new ExpressionNode(kernel, point); default: throw new CASException( "Giac: bad number of args for point(): " + args.getLength()); } case hyperplan: switch (args.getLength()) { case 2: ExpressionValue item0 = args.getItem(0).unwrap(); ExpressionValue item1 = args.getItem(1).unwrap(); if (!(item0 instanceof MyList)) { Log.error("wrong class: " + item0.getClass()); return new ExpressionNode(kernel, Double.NaN); } MyList list1 = (MyList) item0; double a = list1.getListElement(0).evaluateDouble(); double b = list1.getListElement(1).evaluateDouble(); double c = list1.getListElement(2).evaluateDouble(); double constant; if (item1.isGeoElement() && ((GeoElement) item1).isGeoPoint()) { // hyperplan({3,5,-1},point[0,0,-37/10]) GeoPointND point = (GeoPointND) item1; Coords coords = point.getInhomCoordsInD3(); constant = a * coords.get(1) + b * coords.get(2) + c * coords.get(3); } else if (item1 instanceof MyList) { MyList list2 = (MyList) item1; double d = list2.getListElement(0).evaluateDouble(); double e = list2.getListElement(1).evaluateDouble(); double f = list2.getListElement(2).evaluateDouble(); if (f != 0) { constant = f * c; } else if (e != 0) { // 1000x+100y+0z=3 // hyperplan({1000,100,0},{0,3/100,0}) constant = e * b; } else { constant = d * a; } } else { Log.error("wrong class: " + item0.getClass()); return new ExpressionNode(kernel, Double.NaN); } ExpressionNode expX = new ExpressionNode(kernel, new FunctionVariable(kernel, "x")).multiply(a); ExpressionNode expY = new ExpressionNode(kernel, new FunctionVariable(kernel, "y")).multiply(b); ExpressionNode expZ = new ExpressionNode(kernel, new FunctionVariable(kernel, "z")).multiply(c); ExpressionNode rhs = new ExpressionNode(kernel, new MyDouble(kernel, constant)); ExpressionNode sum = expX.plus(expY).plus(expZ); Equation eq = new Equation(kernel, sum, rhs); return new ExpressionNode(kernel, eq); default: throw new CASException( "Giac: bad number of args for hyperplan(): " + args.getLength()); } case ggbvect: ValidExpression vec; switch (args.getLength()) { case 2: vec = new MyVecNode(kernel, args.getItem(0), args.getItem(1)); ((MyVecNode) vec).setCASVector(); break; case 3: vec = new MyVec3DNode(kernel, args.getItem(0), args.getItem(1), args.getItem(2)); ((MyVec3DNode) vec).setCASVector(); break; default: throw new CASException( "Giac: bad number of args for ggbvect(): " + args.getLength()); } ret = new ExpressionNode(kernel, vec, Operation.NO_OPERATION, null); break; case arbint: case arbconst: case Ci: case Si: case Ei: case Zeta: case fPart: case Gamma: case conj: case sin: case cos: case tan: case asin: case acos: case atan: case sinh: case cosh: case tanh: case sec: case csc: case cot: case ln: case exp: case erf: case abs: case xcoord: case ycoord: case zcoord: case xcoordsymb: case ycoordsymb: case zcoordsymb: case altsymb: case sqrt: case sign: case floor: case ceiling: if (args.getLength() != 1) { // eg Derivative[zeta(x)] -> Zeta(1,x) which GeoGebra // doesn't support ret = new ExpressionNode(kernel, Double.NaN); } else { ret = new ExpressionNode(kernel, args.getItem(0), GiacCommands.valueOf(cmdName).getOperation(), null); } break; case im: if (args.getItem(0).unwrap() instanceof Command) { String cmdname = ((Command) args.getItem(0).unwrap()) .getName(); if (cmdname.startsWith(Kernel.TMP_VARIABLE_PREFIX) && kernel .lookupLabel(Kernel.removeCASVariablePrefix( cmdname)) == null) { ret = new MyDouble(kernel).wrap(); break; } } ret = new ExpressionNode(kernel, args.getItem(0), Operation.IMAGINARY, null); break; case re: if (args.getItem(0).unwrap() instanceof Command) { String cmdname = ((Command) args.getItem(0).unwrap()) .getName(); if (cmdname.startsWith(Kernel.TMP_VARIABLE_PREFIX) && kernel .lookupLabel(Kernel.removeCASVariablePrefix( cmdname)) == null) { ret = args.getItem(0); break; } } ret = new ExpressionNode(kernel, args.getItem(0), Operation.REAL, null); break; case ggb_ang: ret = new MyVecNode(kernel); ((MyVecNode) ret).setPolarCoords(args.getItem(0), args.getItem(1)); break; case igamma: if (args.getLength() == 2) { ret = new ExpressionNode(kernel, args.getItem(0), Operation.GAMMA_INCOMPLETE, args.getItem(1)); } else { // must be 3 // discard 3rd arg (dummy to flag regularized) ret = new ExpressionNode(kernel, args.getItem(0), Operation.GAMMA_INCOMPLETE_REGULARIZED, args.getItem(1)); } break; case when: if (args.getLength() == 2) { // shouldn't get 2 arguments, but just in case ret = new ExpressionNode(kernel, args.getItem(0), Operation.IF, args.getItem(1)); } else if (args.getLength() == 3) { ExpressionValue Else = args.getItem(2); if ("?".equals( Else.toString(StringTemplate.defaultTemplate))) { ret = new ExpressionNode(kernel, args.getItem(0), Operation.IF, args.getItem(1)); } else { ret = new ExpressionNode(kernel, args.getItem(0), Operation.IF_ELSE, new MyNumberPair(kernel, args.getItem(1), Else)); } } else { throw new CASException("Giac: bad number of args for when:" + args.getLength()); } break; case surd: if (args.getLength() == 2) { ExpressionValue arg1 = args.getItem(1); double arg1Num = arg1.evaluateDouble(); if (arg1Num == 3) { ret = new ExpressionNode(kernel, args.getItem(0), Operation.CBRT, null); } else if (arg1Num == 2) { ret = new ExpressionNode(kernel, args.getItem(0), Operation.SQRT, null); } else { ret = new ExpressionNode(kernel, args.getItem(0), Operation.NROOT, arg1); } } else { throw new CASException("Giac: bad number of args for surd:" + args.getLength()); } break; case Beta: switch (args.getLength()) { default: throw new CASException("Giac: bad number of args for beta:" + args.getLength()); case 2: ret = new ExpressionNode(kernel, args.getItem(0), Operation.BETA, args.getItem(1)); break; case 3: MyNumberPair np = new MyNumberPair(kernel, args.getItem(1), args.getItem(2)); ret = new ExpressionNode(kernel, args.getItem(0), Operation.BETA_INCOMPLETE, np); break; case 4: // 4th argument is dummy to flag "regularized" np = new MyNumberPair(kernel, args.getItem(1), args.getItem(2)); ret = new ExpressionNode(kernel, args.getItem(0), Operation.BETA_INCOMPLETE_REGULARIZED, np); break; } break; case rootof: // rootof should get removed by evalfa() Log.warn("'rootof()' returned from giac"); ret = new ExpressionNode(kernel, Double.NaN); break; case binomial_cdf: case binomial_icdf: case fisher_cdf: case normald_cdf: case student_cdf: case chisquare_cdf: case laplace: case ilaplace: case invlaplace: case fsolve: case solve: case poly1: // eg ggbtmpvarp = (ggbtmpvarz)+(((1,2))*(ggbtmpvarz)) case integrate: // eg Integral[exp(x^3)] case bounded_function: // eg Limit[cos(x),infinity] case rand: // eg RandomBetween[0, undefined variable] ret = new ExpressionNode(kernel, Double.NaN); break; case diff: if (args.getLength() == 3 && !"1".equals(args.getItem(2) .toString(StringTemplate.giacTemplate))) { return new ExpressionNode(kernel, new MyNumberPair(kernel, args.getItem(0), args.getItem(1)), Operation.DIFF, args.getItem(2)); } if (ExpressionNode.isConstantDouble(args.getItem(0), 0)) { return new ExpressionNode(kernel, 0); } ret = new ExpressionNode(kernel, args.getItem(0), Operation.DIFF, args.getItem(1)); break; } // no match or ExpressionNode if (ret == null || ret instanceof ExpressionNode) { return (ExpressionNode) ret; } // create ExpressionNode return new ExpressionNode(kernel, ret); } catch (Exception e) { if (cmdName != null && !cmdName.startsWith(Kernel.TMP_VARIABLE_PREFIX)) { e.printStackTrace(); Log.error( "CommandDispatcherGiac: error when processing command: " + cmdName + ", " + args); } } // exception, eg Derivative[f(x)+g(x)] return null; } }