package org.geogebra.common.cas.giac;
import java.math.BigInteger;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import org.geogebra.common.cas.CASparser;
import org.geogebra.common.kernel.AsynchronousCommand;
import org.geogebra.common.kernel.CASException;
import org.geogebra.common.kernel.CASGenericInterface;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.AssignmentType;
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.FunctionNVar;
import org.geogebra.common.kernel.arithmetic.MyArbitraryConstant;
import org.geogebra.common.kernel.arithmetic.MyList;
import org.geogebra.common.kernel.arithmetic.MyVecNDNode;
import org.geogebra.common.kernel.arithmetic.Traversing;
import org.geogebra.common.kernel.arithmetic.Traversing.ArbconstReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.DiffReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.PowerRootReplacer;
import org.geogebra.common.kernel.arithmetic.Traversing.PrefixRemover;
import org.geogebra.common.kernel.arithmetic.Traversing.VariableReplacer;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.arithmetic.Variable;
import org.geogebra.common.kernel.geos.GeoCasCell;
import org.geogebra.common.kernel.geos.GeoDummyVariable;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.prover.polynomial.PPolynomial;
import org.geogebra.common.kernel.prover.polynomial.PVariable;
import org.geogebra.common.main.settings.AbstractSettings;
import org.geogebra.common.main.settings.CASSettings;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.StringUtil;
import org.geogebra.common.util.debug.Log;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/**
* Platform (Java / GWT) independent part of giac CAS
*/
public abstract class CASgiac implements CASGenericInterface {
/** parser tools */
/**
* Random number generator
*/
protected static final Random rand = new Random();
/**
* string to put Giac into GeoGebra mode (not affected by 'restart')
*
*/
public final static String initString = "caseval(\"init geogebra\")";
/**
* In web we need to skip caseval because of emcscripten
*/
public final static String initStringWeb = "init geogebra";
/**
* string to put Giac off GeoGebra mode
*/
// public final static String closeString = "caseval(\"close geogebra\")";
// public final static String closeStringWeb = "close geogebra";
public static enum CustomFunctions {
/**
*
*/
RESTART(null, "restart"),
/**
* suppress warning about = vs :=
*/
DISABLE_WARNING(null, "warn_equal_in_prog(0)"),
/**
*
*/
PROBA_EPSILON(null, "proba_epsilon:=0;"),
/**
* used for sorting output of Solve/Solutions/NSolve/NSolutions sort()
* doesn't work for list of lists null -> always sent (used by other
* definitions)
*
* now implemented natively in Giac. This is just for reference /
* testing
*/
// SORT(null,
// "ggbsort(x):=when(length(x)==0,{},when(type(x[0])==DOM_LIST,x,sort(x)))"),
/**
* these both give 3 // @size(point(1,2,3)[1]) gives 3
* // @size(point((-(5))+(ggbtmpvark),(-(5))+(ggbtmpvark))[1]) gives 3
* // so need to check subtype(x[1])==20 to distinguish 2D and 3D
*
* null -> always sent (used by other definitions)
*/
// IS_3D_POINT(null,
// "is_3dpoint(x):=when(size(x[1])==3 &&
// subtype(x[1])==20,true,false)"),
/**
* check whether a is polynomial special cases like y^2=1 also handled
* now implemented natively in Giac. This is just for reference /
* testing
*/
// IS_POLYNOMIAL("ispolynomial",
// "ispolynomial(a):=when(a[0] == '=' ,is_polynomial(a[1]) &&
// is_polynomial(a[2]),"
// + "when (is_polynomial(a) == 1, true, false ) )"),
/**
*
*/
// IS_POLYNOMIAL2("ispolynomial2",
// "ispolynomial2(a,b):= is_polynomial(a) && is_polynomial(b)"),
/**
* NOTE: works for max 2 variable
*/
GGBIS_POLYNOMIAL("ggbisPolynomial", "ggbisPolynomial(a):= when (size(lname(a)) == 1, is_polynomial(a,lname(a)[0])," + "when (size(lname(a)) == 2, is_polynomial(a,lname(a)[0]) && is_polynomial(a,lname(a)[1]), ?))"),
/**
* now implemented natively in Giac. This is just for reference /
* testing
*/
// GGBALT("ggbalt", "ggbalt(x):=when(type(x)==DOM_IDENT,altsymb(x),"
// +
// "when(x[0]=='pnt',when(is_3dpoint(x),atan2(x[1][2],sqrt(x[1][0]^2+x[1][1]^2)),0),?))"),
/**
* xcoordsymb(A) converted back to x(A) in CommandDispatcherGiac
*
* check for type(evalf(a)) needed as type(exact(-2.24)+i*exact(-1.54))
* gives DOM_RAT
*
*/
XCOORD("xcoord", "xcoord(a):=when(type(evalf(a))==DOM_COMPLEX, real(a), when(type(a)==DOM_IDENT,xcoordsymb(a),when(a[0]=='pnt',when(is_3dpoint(a),a[1][0],real(a[1])),when(a[0]=='=',coeff(a[1]-a[2],x,1),a[0]))))"),
/**
* altsymb(P) converted back to alt(P) in CommandDispatcherGiac
*/
YCOORD("ycoord", "ycoord(a):=when(type(evalf(a))==DOM_COMPLEX, im(a), when(type(a)==DOM_IDENT,ycoordsymb(a),when(a[0]=='pnt',when(is_3dpoint(a),a[1][1],im(a[1])),when(a[0]=='=',coeff(a[1]-a[2],y,1),a[1]))))"),
/**
* make sure z((1,2)) = 0
*/
ZCOORD("zcoord", "zcoord(a):=when(type(a)==DOM_IDENT,zcoordsymb(a),when(a[0]=='pnt',when(is_3dpoint(a),a[1][2],0),when(length(a)<3 && a[0] != '=',0,when(a[0]=='=',coeff(a[1]-a[2],z,1),a[2]))))"),
/**
* unicode0176u passes unaltered through Giac then gets decoded to
* degree sign in GeoGebra needed for
* "return angle from inverse trig function" see ExpressionNode.degFix()
*/
DEG_ASIN("asind", "asind(x):=normal(asin(x)/pi*180)*unicode0176u"),
/**
* unicode0176u passes unaltered through Giac then gets decoded to
* degree sign in GeoGebra needed for
* "return angle from inverse trig function" see ExpressionNode.degFix()
*/
DEG_ACOS("acosd", "acosd(x):=normal(acos(x)/pi*180)*unicode0176u"),
/**
* unicode0176u passes unaltered through Giac then gets decoded to
* degree sign in GeoGebra needed for
* "return angle from inverse trig function" see ExpressionNode.degFix()
*/
DEG_ATAN("atand", "atand(x):=normal(atan(x)/pi*180)*unicode0176u"),
/**
* unicode0176u passes unaltered through Giac then gets decoded to
* degree sign in GeoGebra needed for
* "return angle from inverse trig function" see ExpressionNode.degFix()
*/
DEG_ATAN2("atan2d", "atan2d(y,x):=normal(arg(x+i*y)/pi*180)*unicode0176u"),
/**
* subtype 27 is ggbvect[]
*
* abs() in Giac now works for vectors so this isn't needed
*
*/
// ABS("ggbabs",
// "ggbabs(x):=when(x[0]=='pnt' || (type(x)==DOM_LIST &&
// subtype(x)==27),l2norm(x),abs(x))"),
/**
* check list before equation to avoid out of bounds. flatten helps for
* {} and {{{0}}}
*
* used for EQUAL_BOOLEAN in ExpressionNode
* sb.append("when(ggbIsZero(simplify(");
*
* eg sin(x)^2+cos(x)^2==1
*/
IS_ZERO("ggbIsZero", "ggbIsZero(x):=when(x==0,true,when(type(x)=='DOM_LIST',max(flatten({x,0}))==min(flatten({x,0}))&&min(flatten({x,0}))==0,when(x[0]=='=',lhs(x)==0&&rhs(x)==0,x[0]=='pnt' && x[1] == ggbvect[0,0,0])))"),
/**
* Convert the polys into primitive polys in the input list (contains
* temporary fix for primpart also):
*/
PRIM_POLY("primpoly", "primpoly(x):=begin local pps,ii; if (x==[0]) return [0]; pps:=[]; for ii from 0 to size(x)-1 do pps[ii]:=primpart(x[ii],lvar(x[ii])); od return pps end"),
/**
* Compute squarefree factorization of the input poly p. Strange why
* sommet(-x)!='-' (so we do an ugly hack here, FIXME)
*/
FACTOR_SQR_FREE("factorsqrfree", "factorsqrfree(p):=begin local pf,r,ii; pf:=factor(p); if (sommet(pf)!='*') begin if (sommet(pf)=='^') return op(pf)[0]; else begin if (sommet(pf)!=sommet(-x)) return pf; else return factorsqrfree(-pf); end; end; opf:=op(pf); r:=1; for ii from 0 to size(opf)-1 do r:=r*factorsqrfree(opf[ii]); od return r end"),
/**
* Remove zeroes or linear dependencies from a list (workaround for
* buggy eliminate). Probably obsolete and can be removed and
* substituted by eliminate().
*/
ELIMINATE2("eliminate2", "eliminate2(x,y):=eliminate(eliminate(x,y),y);"),
/**
* Eliminate variables from a polynomial ideal. If the result is a set
* of discrete points, then convert the linear polynomials to a product
* of circle definitions with zero radius.
*/
GEOM_ELIM("geomElim", "geomElim(polys,elimvars,precision):=begin local ee, ll, ff, gg, ii; ee:=eliminate(polys,revlist(elimvars)); ll:=lvar(ee); if (size(ee)>1) begin ff:=round(fsolve(ee,ll)*precision)/precision; gg:=1; for ii from 0 to size(ff)-1 do gg:=gg*(((ll[0]-ff[ii,0])^2+(ll[1]-ff[ii,1])^2)); od; ee:=[expand(lcm(denom(coeff(gg)))*gg)]; end; if (size(ee)==0) return 0; else return primpoly(ee)[0]; end;"),
/**
* Help simplifying the input when computing the Jacobian matrix in the
* Envelope command. Input: a list of polynomials and a list of
* variables which will not be used as derivatives. Output: another list
* of polynomials (a shorter list) which does not contain the linear
* ones and equivalent with the input. Note that
* op(solve(polys[ii]=0,linvar)[0])[1] is required in GeoGebra mode, in
* standard Giac here solve(polys[ii],linvar)[0] should be written.
*
* The algorithm finds the polys which have one variable and it is
* linear. After solving such a poly=0 equation, the solution will be
* substituted into all other polys. After doing this for all one
* variable linear polys recursively, the resulted polys will be used in
* the Jacobian matrix in jacobiDet().
*
* Used internally.
*/
JACOBI_PREPARE("jacobiPrepare", "jacobiPrepare(polys,excludevars):=begin local ii, degrees, pos, vars, linvar; vars:=lvar(polys); ii:=0; while (ii<size(polys)-1) do degrees:=degree(polys[ii],vars); if (sum(degrees)=1) begin pos:=find(1,degrees); linvar:=vars[pos[0]]; if (!is_element(linvar,excludevars)) begin substval:=op(solve(polys[ii]=0,linvar)[0])[1]; polys:=remove(0,expand(subs(polys,[linvar],[substval]))); /* print(polys); */ ii:=-1; end; end; ii:=ii+1; od; return polys; end"),
/**
* Compute the Jacobian determinant of the polys with respect to
* excludevars. Used internally.
*/
JACOBI_DET("jacobiDet", "jacobiDet(polys,excludevars):=begin local J, ii, vars, s, j, k; vars:=lvar(polys); for ii from 0 to size(excludevars)-1 do vars:=remove(excludevars[ii], vars); od; s:=size(vars); J:=matrix(s,s,(j,k)->diff(polys[j],vars[k])); return det_minor(J); end"),
/**
* Compute the Jacobian determinant of the polys with respect to
* excludevars, but first some geometrical preparations are performed to
* simplify the result. Used internally.
*/
GEOM_JACOBI_DET("geomJacobiDet",
"geomJacobiDet(polys,excludevars):=begin local J; J:=jacobiPrepare(polys,excludevars); return jacobiDet(J,excludevars); end"),
/**
* Compute the coefficients of the envelope equation for the input
* polys, elimvars with given precision for the curve variables x and y.
* Used publicly.
*/
ENVELOPE_EQU("envelopeEqu",
"envelopeEqu(polys,elimvars,precision,curvevarx,curvevary):=begin local D; D:=geomJacobiDet(polys,[curvevarx,curvevary]); polys:=append(polys,D); return locusEqu(polys,elimvars,precision,curvevarx,curvevary); end"),
/**
* Compute the coefficients of the locus equation for the input polys,
* elimvars with given precision for the curve variables x and y. Used
* publicly.
*/
LOCUS_EQU("locusEqu", "locusEqu(polys,elimvars,precision,curvevarx,curvevary):=implicitCurveCoeffs(subst(geomElim(polys,elimvars,precision),[curvevarx=x,curvevary=y]))"),
/**
* Compute coefficient matrix of the input polynomial. The output is a
* flattened variant of the matrix: the elements are returned row by
* row, starting with the sizes of the matrix: height and width. Used
* internally.
*/
COEFF_MATRIX("coeffMatrix", "coeffMatrix(aa):=begin local bb, sx, sy, ii, jj, ee, cc, kk; bb:=coeffs(aa,x); sx:=size(bb); sy:=size(coeffs(aa,y)); cc:=[sx,sy]; for ii from sx-1 to 0 by -1 do dd:=coeff(bb[ii],y); sd:=size(dd); for jj from sd-1 to 0 by -1 do ee:=dd[jj]; cc:=append(cc,ee); od; for kk from sd to sy-1 do ee:=0; cc:=append(cc,ee); od; od; return cc; end"),
/**
* Compute the coefficient matrices for the factors of the input
* polynomial. The first number in the flattened output is the number of
* the coefficient matrices, then each coefficient matrix is added. Used
* internally.
*/
COEFF_MATRICES("coeffMatrices", "coeffMatrices(aa):=begin local ff, bb, ccf, ll, aaf; ff:=factors(aa); ccf:=[size(ff)/2]; for ll from 0 to size(ff)-1 by 2 do aaf:=ff[ll]; bb:=coeffMatrix(aaf); ccf:=append(ccf,bb); od; return flatten(ccf); end"),
/**
* Compute the flattened coefficient matrix as it is directly used when
* the algebraic curve is plotted as an implicit poly. Used publicly.
*/
IMPLICIT_CURVE_COEFFS("implicitCurveCoeffs", "implicitCurveCoeffs(aa):=begin local bb; bb:=factorsqrfree(aa); return [coeffMatrix(bb),coeffMatrices(bb)]; end");
/** function name */
final public String functionName;
/** definition string */
final public String definitionString;
CustomFunctions(String functionName, String definitionString) {
this.functionName = functionName;
this.definitionString = definitionString;
}
@Override
public String toString() {
return functionName;
}
private static List<Entry<CustomFunctions, CustomFunctions>> CustomFunctionsDependencies;
private static void setDependency(CustomFunctions cf1,
CustomFunctions cf2) {
Entry<CustomFunctions, CustomFunctions> pair = new SimpleEntry<CustomFunctions, CustomFunctions>(
cf1, cf2);
CustomFunctionsDependencies.add(pair);
}
/**
* Create dependencies between two CAS custom functions. This is
* required to ensure that all dependencies will be loaded when a custom
* function is loaded.
*/
public static void setDependencies() {
CustomFunctionsDependencies = new ArrayList<Entry<CustomFunctions, CustomFunctions>>();
setDependency(IMPLICIT_CURVE_COEFFS, COEFF_MATRIX);
setDependency(IMPLICIT_CURVE_COEFFS, COEFF_MATRICES);
setDependency(IMPLICIT_CURVE_COEFFS, FACTOR_SQR_FREE);
setDependency(GEOM_ELIM, PRIM_POLY);
setDependency(LOCUS_EQU, IMPLICIT_CURVE_COEFFS);
setDependency(LOCUS_EQU, GEOM_ELIM);
setDependency(ENVELOPE_EQU, LOCUS_EQU);
setDependency(ENVELOPE_EQU, GEOM_JACOBI_DET);
setDependency(GEOM_JACOBI_DET, JACOBI_PREPARE);
setDependency(GEOM_JACOBI_DET, JACOBI_DET);
}
/**
* Create the list of prerequisites of a custom command. TODO: Currently
* we don't have a complex tree of dependencies. Later we may add a more
* sophisticated algorithm here to remove duplicates, be faster etc.
*
* @param cf
* the custom command
* @return the prerequisites
*/
public static ArrayList<CustomFunctions> prereqs(
CustomFunctions cf) {
ArrayList<CustomFunctions> list = new ArrayList<CustomFunctions>();
for (Entry<CustomFunctions, CustomFunctions> pair : CustomFunctionsDependencies) {
CustomFunctions key = pair.getKey();
CustomFunctions value = pair.getValue();
if (key.equals(cf)) {
list.add(0, value);
list.addAll(0, prereqs(value));
}
}
return list;
}
}
/** CAS parser */
public CASparser casParser;
private static int nrOfReplacedConst = 0;
/**
* Creates new Giac CAS
*
* @param casParser
* parser
*/
public CASgiac(CASparser casParser) {
this.casParser = casParser;
}
/**
* @param exp
* Giac command
* @return value returned from CAS
*/
@Override
public abstract String evaluateCAS(String exp);
@Override
final public String evaluateRaw(final String input) throws Throwable {
String exp = input;
String result = evaluate(exp, getTimeoutMilliseconds());
// FIXME: This check is too heuristic: in giac.js we can get results
// starting with \"
// and they are still correct (e.g. from eliminateFactorized).
// TODO: Find a better way for checking, now we assume that \"[ start is
// OK (or \"\").
Log.debug("input = " + input);
String rtrimmed = result.trim();
if (rtrimmed.startsWith("\"") && rtrimmed.endsWith("\"")) {
result = result.substring(1, result.length() - 1); // removing
}
Log.debug("result = " + result);
return result;
}
/**
* @param exp
* expression string
* @param timeoutMilliseconds
* timeout in milliseconds
* @return result in Giac syntax
* @throws Throwable
* for CAS error
*/
protected abstract String evaluate(String exp, long timeoutMilliseconds)
throws Throwable;
@Override
final public synchronized String evaluateGeoGebraCAS(
final ValidExpression inputExpression, MyArbitraryConstant arbconst,
StringTemplate tpl, GeoCasCell cell, Kernel kernel)
throws CASException {
ValidExpression casInput = inputExpression;
Command cmd = casInput.getTopLevelCommand();
boolean keepInput = (cell != null && cell.isKeepInputUsed())
|| (cmd != null && "KeepInput".equals(cmd.getName()));
String plainResult = getPlainResult(casInput, kernel);
if (keepInput) {
// remove KeepInput[] command and take argument
if (cmd != null && cmd.getName().equals("KeepInput")) {
// use argument of KeepInput as casInput
if (cmd.getArgumentNumber() > 0) {
casInput = cmd.getArgument(0);
}
}
}
// convert result back into GeoGebra syntax
if (casInput instanceof FunctionNVar) {
// delayed function definition f(x)::= Derivative[x^2] should return
// Derivative[x^2]
if (cell != null
&& cell.getAssignmentType() == AssignmentType.DELAYED) {
return casInput.toString(StringTemplate.numericNoLocal);
}
// function definition f(x) := x^2 should return x^2
// f(x):=Derivative[x^2] should return 2x
// do not return directly, must check keepinput
/*
* plainResult = evaluateMPReduce(plainResult + "(" +
* ((FunctionNVar)
* casInput).getVarString(StringTemplate.casTemplate) + ")");
*/
}
String result = plainResult;
if (keepInput && (cell == null || !cell.isSubstitute())) {
// assume keepinput was not treated in CAS
return casParser.toGeoGebraString(casInput, tpl);
}
if (keepInput && (cell != null && cell.isSubstitute())) {
// assume keepinput was not treated in CAS
ExpressionValue substList = casInput.getTopLevelCommand()
.getArgument(1).unwrap();
ExpressionValue substArg = casInput.getTopLevelCommand()
.getArgument(0);
if (substList instanceof MyList) {
for (int i = 0; i < ((MyList) substList).size(); i++) {
substArg = subst(substArg, ((MyList) substList).getItem(i),
cell.getKernel());
}
} else {
substArg = subst(substArg, substList, cell.getKernel());
}
if (substArg != null) {
return casParser.toGeoGebraString(substArg, tpl);
}
}
// standard case
if (result == null || result.isEmpty()) {
return null;
}
return toGeoGebraString(result, arbconst, tpl, kernel);
}
private static ExpressionValue subst(ExpressionValue substArg,
ExpressionValue item0, Kernel kernel) {
ExpressionValue item = item0.unwrap();
if (item instanceof Equation) {
ExpressionValue lhs = ((Equation) item).getLHS().unwrap();
if (lhs instanceof GeoDummyVariable || lhs instanceof Variable) {
ExpressionValue rhs = ((Equation) item).getRHS();
ExpressionValue copy = substArg.deepCopy(kernel);
copy.traverse(VariableReplacer.getReplacer(
lhs.toString(StringTemplate.defaultTemplate), rhs,
kernel));
return copy;
}
}
return null;
}
@Override
final public synchronized ExpressionValue evaluateToExpression(
final ValidExpression inputExpression, MyArbitraryConstant arbconst,
Kernel kernel) throws CASException {
String result = getPlainResult(inputExpression, kernel);
// standard case
if ("".equals(result)) {
return null;
}
return replaceRoots(casParser.parseGiac(result), arbconst, kernel);
}
private String getPlainResult(ValidExpression casInput, Kernel kernel) {
// KeepInput[] command should set flag keepinput!!:=1
// so that commands like Substitute can work accordingly
Command cmd = casInput.getTopLevelCommand();
if (cmd != null && "Delete".equals(cmd.getName())) {
ExpressionValue toDelete = cmd.getArgument(0).unwrap();
if (toDelete.isExpressionNode() && (((ExpressionNode) toDelete)
.getOperation() == Operation.FUNCTION
|| ((ExpressionNode) toDelete)
.getOperation() == Operation.FUNCTION_NVAR)) {
toDelete = ((ExpressionNode) toDelete).getLeft();
}
String label = toDelete.toString(StringTemplate.defaultTemplate);
GeoElement geo = kernel.lookupLabel(label);
if (geo == null) {
geo = kernel.lookupCasCellLabel(label);
}
if (geo != null) {
geo.remove();
}
return "true";
}
// convert parsed input to Giac string
String giacInput = casParser.translateToCAS(casInput,
StringTemplate.giacTemplate, this);
// evaluate in Giac
String plainResult = evaluateCAS(giacInput);
// get initial nr of vars
int nrOfVars = casParser.getNrOfVars();
StringBuilder newPlainResult = new StringBuilder();
// case we need to process the result
if (nrOfVars > 0) {
// get array of potential results
String[] partsOfResult = plainResult.split("},");
for (int i = 0; i < partsOfResult.length; i++) {
// get array of solutions
String[] partsOfCurrSol = partsOfResult[i].split(",");
// append only asked solutions
for (int j = 0; j < nrOfVars; j++) {
if (j == nrOfVars - 1) {
newPlainResult.append(partsOfCurrSol[j]);
newPlainResult.append("},");
} else {
newPlainResult.append(partsOfCurrSol[j]);
newPlainResult.append(",");
}
}
}
newPlainResult.setLength(newPlainResult.length() - 1);
newPlainResult.append("}");
// reset nrOfVars
casParser.setNrOfVars(0);
return newPlainResult.toString();
}
return plainResult;
}
/**
* Tries to parse a given Giac string and returns a String in GeoGebra
* syntax.
*
* @param giacString
* String in Giac syntax
* @param arbconst
* arbitrary constant handler
* @param tpl
* template that should be used for serialization. Should be
* casCellTemplate for CAS and defaultTemplate for input bar
* @param kernel
* kernel
* @return String in Geogebra syntax.
* @throws CASException
* Throws if the underlying CAS produces an error
*/
final public synchronized String toGeoGebraString(String giacString,
MyArbitraryConstant arbconst, StringTemplate tpl,
final Kernel kernel)
throws CASException {
ExpressionValue ve = replaceRoots(casParser.parseGiac(giacString),
arbconst, kernel);
// replace rational exponents by roots or vice versa
ve = ve.traverse(new Traversing() {
public ExpressionValue process(ExpressionValue ev) {
if (ev instanceof MyVecNDNode
&& ((MyVecNDNode) ev).isCASVector()) {
return new ExpressionNode(kernel,
new Variable(kernel, "ggbvect"), Operation.FUNCTION,
ev);
}
return ev;
}
});
return casParser.toGeoGebraString(ve, tpl);
}
private static ExpressionValue replaceRoots(ExpressionValue ve0,
MyArbitraryConstant arbconst, Kernel kernel) {
ExpressionValue ve = ve0;
if (ve != null) {
boolean toRoot = kernel.getApplication().getSettings()
.getCasSettings().getShowExpAsRoots();
ve = ve.traverse(DiffReplacer.INSTANCE);
ve.traverse(PowerRootReplacer.getReplacer(toRoot));
if (arbconst != null) {
arbconst.reset();
ve.traverse(ArbconstReplacer.getReplacer(arbconst));
}
PrefixRemover pr = PrefixRemover.getCollector();
ve.traverse(pr);
}
return ve;
}
/**
* Timeout for CAS in milliseconds. This can be changed in the CAS options.
*/
public long timeoutMillis = 5000;
/**
* @return CAS timeout in seconds
*/
protected long getTimeoutMilliseconds() {
return timeoutMillis;
}
@Override
public void settingsChanged(AbstractSettings settings) {
CASSettings s = (CASSettings) settings;
timeoutMillis = s.getTimeoutMilliseconds();
}
@Override
public String translateAssignment(final String label, final String body) {
return label + " := " + body;
}
/**
* This method is called when asynchronous CAS call is finished. It tells
* the calling algo to update itself and adds the result to cache if
* suitable.
*
* @param exp
* parsed CAS output
* @param result2
* output as string (for caching)
* @param exception
* exception which stopped the computation (null if there wasn't
* one)
* @param c
* command that called the CAS asynchronously
* @param input
* input string (for caching)
* @param cell
* cas cell
*/
public void CASAsyncFinished(ValidExpression exp, String result2,
Throwable exception, AsynchronousCommand c, String input,
GeoCasCell cell) {
String result = result2;
// pass on exception
if (exception != null) {
c.handleException(exception, input.hashCode());
return;
}
// check if keep input command was successful
// e.g. for KeepInput[Substitute[...]]
// otherwise return input
if (cell.isKeepInputUsed() && ("?".equals(result))) {
// return original input
c.handleCASoutput(exp.toString(StringTemplate.maxPrecision),
input.hashCode());
}
// success
if (result2 != null) {
c.getKernel();
// get names of escaped global variables right
// e.g. "ggbcasvar1a" needs to be changed to "a"
// e.g. "ggbtmpvara" needs to be changed to "a"
result = Kernel.removeCASVariablePrefix(result, " ");
}
c.handleCASoutput(result, input.hashCode());
if (c.useCacheing()) {
c.getKernel().putToCasCache(input, result);
}
}
@Override
public void appendListStart(StringBuilder sbCASCommand) {
sbCASCommand.append("[");
}
@Override
public void appendListEnd(StringBuilder sbCASCommand) {
sbCASCommand.append("]");
}
@Override
public String createEliminateFactorizedScript(String polys,
String elimVars) {
/*
* Some examples to understand the code below:
*
* [[aa:=eliminate([-1*v1,-1*v11*v10+v12*v9+v11*v8+-1*v9*v8+-1*v12*v7+
* v10 *v7,v13*v8+-1*v14*v7,-1*v13+v13*v10+v9+-1*v14*v9,
* -1*v15*v10+v16*v9+v15
* *v2+-1*v9*v2+-1*v16*v1+v10*v1,v15*v12+-1*v16*v11,
* v17+-1*v17*v12+-1*v11+v18*v11,
* -1*v17*v8+v18*v7+v17*v2+-1*v7*v2+-1*v18
* *v1+v8*v1,-1+-1*v19*v17*v16+v19
* *v18*v15+v19*v17*v14+-1*v19*v15*v14+-1*v19*v18*v13+v19*v16*v13],
* [v17,v16,v19,v1,v18,v8,v13,v14,v15])],[bb:=size(aa)],[for ii from 0
* to bb-1 do cc[ii]:=factors(aa[ii]); od], cc][3]
*
* table( 1 = [v2-1,1,v7,1,v12-1,1], 2 = [v2,1,v9,1,v12,1], 3 =
* [v7,1,v10-1,1], 4 = [v12,1,v12-1,1,-1,1,v2,1,v10-1,1,v10-v2,1], 5 =
* [-v2+1,1,v7,1,v11,1], 6 = [v2,1,v9,1,v11,1], 7 =
* [-v11*v10+v11*v2+v12*v9,1], 0 = [v7,1,v9,1] )
*
* But we need the same output as Singular does, so we use this code
* instead:
*
* [[aa:=eliminate([-1*v1,-1*v11*v10+v12*v9+v11*v8+-1*v9*v8+-1*v12*v7+
* v10 *v7,v13*v8+-1*v14*v7,-1*v13+v13*v10+v9+-1*v14*v9,
* -1*v15*v10+v16*v9+v15
* *v2+-1*v9*v2+-1*v16*v1+v10*v1,v15*v12+-1*v16*v11,
* v17+-1*v17*v12+-1*v11+v18*v11,
* -1*v17*v8+v18*v7+v17*v2+-1*v7*v2+-1*v18
* *v1+v8*v1,-1+-1*v19*v17*v16+v19
* *v18*v15+v19*v17*v14+-1*v19*v15*v14+-1*v19*v18*v13+v19*v16*v13],
* [v17,v16,v19,v2,v18,v8,v13,v14,v15])],[bb:=size(aa)],[for ii from 0
* to bb-1 do print("["+(ii+1)+"]:");print(" [1]:");print(" _[1]=1"
* );cc: =factors(aa[ii]);dd:=size(cc); for jj from 0 to dd-1 by 2 do
* print(" _["+(jj/2+2)+"]="+(cc[jj])); od; print(" [2]:"); print(" "
* +cc[1]);for kk from 1 to dd-1 by 2 do print(" ,"
* +cc[kk]);od;od],0][3]
*
* which gives
*
* [1]: [1]: _[1]=1 _[2]=v7 _[3]=v9 [2]: 1 ,1 ,1 [2]: [1]: _[1]=1
* _[2]=v7 _[3]=v10-1 [2]: 1 ,1 ,1 [3]: [1]: _[1]=1 _[2]=v9 _[3]=-1
* _[4]=v11*v10-v9*v12 [2]: 1 ,1 ,1 ,1 [4]: [1]: _[1]=1 _[2]=v1 [2]: 1
* ,1 0
*
* in giac with CoCoA support on command line and runs forever in
* giac.js.
*/
StringBuilder script = new StringBuilder();
/*
* return script.append("[[aa:=eliminate(["). append(polys).
* append("],["). append(elimVars). append(
* "])],[bb:=size(aa)],[for ii from 0 to bb-1 do print(\"[\"+(ii+1)+\"]:\");print(\" [1]:\");"
* ). append("print(\" _[1]=1\");cc:=factors(aa[ii]);dd:=size(cc);").
* append(
* "for jj from 0 to dd-1 by 2 do print(\" _[\"+(jj/2+2)+\"]=\"+(cc[jj])); od; print(\" [2]:\");"
* ). append(
* "print(\" \"+cc[1]);for kk from 1 to dd-1 by 2 do print(\" ,\"+cc[kk]);od;od],0][3]"
* )
*
* .toString();
*/
String eliminateCommand = CustomFunctions.ELIMINATE2 + "([" + polys
+ "],revlist(["
+ elimVars + "]))";
return script.append("[" + "[ff:=\"\"],[aa:=").append(eliminateCommand)
.append("],")
.append("[bb:=size(aa)],[for ii from 0 to bb-1 do ff+=(\"[\"+(ii+1)+\"]: [1]: ")
.append(" _[1]=1\");cc:=factors(aa[ii]);dd:=size(cc);")
.append("for jj from 0 to dd-1 by 2 do ff+=(\" _[\"+(jj/2+2)+\"]=\"+cc[jj]); od; ff+=(\" [2]: ")
.append("\"+cc[1]);for kk from 1 to dd-1 by 2 do ff+=(\",\"+cc[kk]);od;od],[if(ff==\"\") begin ff:=[0] end],ff][5]")
.toString();
// We return text from the CAS here.
}
/**
* Create a script which eliminates variables from a set of polynomials.
*
* @param polys
* the input polynomials
* @param elimVars
* the variables to be eliminated
* @param oneCurve
* if the output consists of more polynomials, consider the
* intersections of them as points with real coordinates and
* convert them to a single product
* @return the Giac program which creates the output ideal
*/
@Override
public String createEliminateScript(String polys, String elimVars,
boolean oneCurve, Long precision) {
if (!oneCurve) {
return CustomFunctions.PRIM_POLY + "(" + CustomFunctions.ELIMINATE2
+ "([" + polys
+ "],revlist([" + elimVars + "])))";
}
String PRECISION = Long.toString(precision);
Log.debug("PRECISION = " + PRECISION);
String retval;
/*
* Exact approach. This will not work if there are irrationals since
* sqrt(...) cannot be directly converted to a number.
*/
/*
* retval = "primpoly([[ee:=eliminate([" + polys + "],revlist([" +
* elimVars +
* "]))],[ll:=lvar(ee)],[if(size(ee)>1) begin ff:=solve(ee,ll);" +
* "gg:=1;for ii from 0 to size(ff)-1 do gg:=gg*right(((ll[0]-ff[ii,0])^2+(ll[1]-ff[ii,1])^2)-"
* + FAKE_NULL + ");" +
* "od ee:=[lcm(denom(coeff(gg)))*gg]; end],ee][3])";
*/
/*
* Rounded approach. This works in general, but we should check how
* fsolve is implemented. The best would be to use symbolical
* computation during solve(...) and then do the numerical
* approximation. TODO: Check how giac implements fsolve and use a
* different method if needed (and available).
*/
retval = CustomFunctions.PRIM_POLY + "([[ee:="
+ CustomFunctions.ELIMINATE2 + "([" + polys
+ "],revlist([" + elimVars
+ "]))],[ll:=lvar(ee)],[if(size(ee)>1) begin ff:=round(fsolve(ee,ll)*"
+ PRECISION + ")/" + PRECISION + ";"
+ "gg:=1;for ii from 0 to size(ff)-1 do gg:=gg*(((ll[0]-ff[ii,0])^2+(ll[1]-ff[ii,1])^2));"
+ "od ee:=[expand(lcm(denom(coeff(gg)))*gg)]; end],ee][3])";
return retval;
}
@Override
public String createGroebnerSolvableScript(
HashMap<PVariable, BigInteger> substitutions, String polys,
String freeVars, String dependantVars, boolean transcext) {
/*
* Example syntax (from Groebner basis tester; but in GeoGebra v1, v2,
* ... are used for variables):
*
* [[ii:=gbasis(subst([2*d1-b1-c1, 2*d2-b2-c2,2*e1-a1-c1,
* 2*e2-a2-c2,2*f1-a1-b1, 2*f2-a2-b2 ,
* (d1-o1)*(b1-c1)+(d2-o2)*(b2-c2),(e1-o1)*(c1-a1)+(e2-o2)*(c2-a2),
* s1*d2
* +a1*(s2-d2)-d1*s2-a2*(s1-d1),s1*e2+b1*(s2-e2)-e1*s2-b2*(s1-e1),(a1
* -m1)*(b1-c1)+(a2-m2)*(b2-c2),
* (b1-m1)*(c1-a1)+(b2-m2)*(c2-a2),z1*(b1*c2
* +a1*(b2-c2)-c1*b2-a2*(b1-c1))-1, z2
* *(s1*m2+o1*(s2-m2)-m1*s2-o2*(s1-m1
* ))-1],[d1=0,b1=3]),[a1,a2,b1,b2,c1,c2,d1,d2,e1,e2,f1,f2,o1,
* o2,s1,s2,m1,m2,z1,z2],revlex)],(degree(ii[0])!=0)||(ii[0]==0)][1]
*
* In the last part we check if the Groebner basis is a constant neq 0,
* i.e. its degree is 0 but it is not 0. If yes, there is no solution.
*
* The giac implementation does not handle the case for request for
* polynomial ring with coefficients from a transcendental extension. We
* silently use a polynomial ring instead.
*/
String idealVar = "ii";
String ret = "[[" + idealVar + ":=gbasis(";
if (substitutions != null) {
ret += "subst(";
}
ret += "[" + polys + "]";
if (substitutions != null) {
String substParams = substitutionsString(substitutions);
ret += ",[" + substParams + "])";
}
String vars = freeVars + PPolynomial.addLeadingComma(dependantVars);
// ret += ",[" + vars + "],revlex)],(degree(" +
// idealVar + "[0])!=0)||(" + idealVar + "[0]==0)][2]";
ret += ",[" + vars + "],revlex)],(" + idealVar + "[0]!=1)&&(" + idealVar
+ "[0]!=-1)][1]";
return ret;
}
/**
* Converts substitutions to giac strings
*
* @param substitutions
* input as a HashMap
* @return the parameters for giac (e.g. "v1=0,v2=0,v3=0,v4=1")
*
* Taken from prover.Polynomial, one character difference. Maybe
* commonize.
*/
static String substitutionsString(
HashMap<PVariable, BigInteger> substitutions) {
StringBuilder ret = new StringBuilder();
Iterator<Entry<PVariable, BigInteger>> it = substitutions.entrySet()
.iterator();
while (it.hasNext()) {
Entry<PVariable, BigInteger> v = it.next();
ret.append(",");
ret.append(v.getKey().toString());
ret.append("=");
ret.append(v.getValue());
}
if (ret.length() > 0) {
return ret.substring(1);
}
return "";
}
/**
* Combine non-factorized and factorized results as a 3 dimensional array.
*
* The input is like this example:
* {{6,5,-184,304,-160,28,-2,52,-136,96,-14,1,-2,-12,-12,0,0,27,2,2,0,0,-10,
* 0,0,0,0,1,0,0,0,0},{2,2,1,-2,1,5,5,92,-152,80,-14,1,20,-8,-8,0,0,11,2,2,0
* ,0,-8,0,0,0,0,1,0,0,0,0}} describes a Steiner deltoid
* (http://tube.geogebra.org/m/xWCP09dk) by its expanded polynomial
* x^5+x*y^4+2*x^3*y^2-10*x^4-2*y^4-14*x*y^3-12*x^2*y^2+2*x^3*y+27*x^3+28*y^
* 3+96*x*y^2-12*x^2*y-2*x^2-160*y^2-136*x*y+52*x+304*y-184 which is the
* first list {6,5,...} and its two factors: x-2=0 and
* y^4-14*y^3+2*y^2*x^2-8*y^2*x+80*y^2+2*y*x^2-8*y*x-152*y+x^4-8*x^3+11*x^2+
* 20*x+92=0. In the second list {2,2,...} the first element tells the
* number of factors (2), then the coefficients are listed (2,1,-2,1 for
* x-2=0, here the first two elements describe the size of the coefficient
* matrix) and the rest for the other one.
*
* In the output the 0. element in the 1. dimension contains the
* non-factorized values, the next elements contain the factorized ones.
*
* @param rawResult
* input string of coefficients of a polynomial and its factors
* in a custom format
* @return the output array of coefficients in GeoImplicitCurve's format
*/
@Override
public double[][][] getBivarPolyCoefficientsAll(String rawResult) {
double[][] coeff = getBivarPolyCoefficients(rawResult);
double[][][] coeffSquarefree = getBivarPolySquarefreeCoefficients(
rawResult);
double[][][] retval = new double[coeffSquarefree.length + 1][][];
retval[0] = coeff;
for (int i = 0; i < coeffSquarefree.length; ++i) {
retval[i + 1] = coeffSquarefree[i];
}
return retval;
}
private static double[][][] getBivarPolySquarefreeCoefficients(
String rawResult) {
int firstClosingBracket = rawResult.indexOf('}');
String numbers = rawResult.substring(firstClosingBracket + 3,
rawResult.length() - 2);
String[] flatData = numbers.split(",");
int factors = Integer.parseInt(flatData[0]);
double[][][] result = new double[factors][][];
int counter = 1;
for (int factor = 0; factor < factors; ++factor) {
int xLength = Integer.parseInt(flatData[counter++]);
int yLength = Integer.parseInt(flatData[counter++]);
result[factor] = new double[xLength][yLength];
for (int x = 0; x < xLength; x++) {
for (int y = 0; y < yLength; y++) {
result[factor][x][y] = Double
.parseDouble(flatData[counter]);
Log.trace("[LocusEqu] result[" + factor + "][" + x + "," + y
+ "]=" + result[factor][x][y]);
++counter;
}
}
}
return result;
}
private static double[][] getBivarPolyCoefficients(String rawResult) {
int firstClosingBracket = rawResult.indexOf('}');
String numbers = rawResult.substring(2, firstClosingBracket);
String[] flatData = numbers.split(",");
int xLength = Integer.parseInt(flatData[0]);
int yLength = Integer.parseInt(flatData[1]);
double[][] result = new double[xLength][yLength];
int counter = 2;
for (int x = 0; x < xLength; x++) {
for (int y = 0; y < yLength; y++) {
result[x][y] = Double.parseDouble(flatData[counter]);
Log.trace("[LocusEqu] result[" + x + "," + y + "]="
+ result[x][y]);
++counter;
}
}
return result;
}
// eg {(ggbtmpvarx>(-sqrt(110)/5)) && ((sqrt(110)/5)>ggbtmpvarx)}
// eg {(ggbtmpvarx>=(-sqrt(110)/5)) && ((sqrt(110)/5)>=ggbtmpvarx)}
// eg (ggbtmpvarx>3) && (4>ggbtmpvarx)
// private final static RegExp inequality =
// RegExp.compile("(.*)\\((ggbtmpvar[^,}\\(\\)]+)>(=*)(.+)\\) &&
// \\((.+)>(=*)(ggbtmpvar[^,}\\(\\)]+)\\)(.*)");
// works only for variables in form [A-Za-z]+
/** expression with at most 3 levels of brackets */
public final static String expression = "(([^\\(\\)]|\\([^\\(\\)]+\\)|\\(([^\\(\\)]|\\([^\\(\\)]+\\))+\\))+)";
/**
* inequality a >=? ex1 && ex1 >=? b where a,b are literals and ex1, ex2 are
* expressions with at most 3 brackets
*/
public final static RegExp inequality = RegExp
.compile("^(.*)\\(([A-Za-z]+)>(=*)" + expression + "\\) && \\("
+ expression + ">(=*)([A-Za-z]+)\\)(.*)$", "");
// eg 3.7 > ggbtmpvarx
// eg (37/10) > ggbtmpvarx
// eg 333 > ggbtmpvarx
// eg (-33) > ggbtmpvarx
// private final static RegExp inequalitySimple =
// RegExp.compile("([-0-9.E/\\(\\)]+)>(=*)(ggbtmpvar.+)");
// works only for variables in form [A-Za-z]+
private final static RegExp inequalitySimple = RegExp
.compile("^([-0-9.E/\\(\\)]+)>(=*)([A-Za-z]+)$");
// eg {3, 3>ggbtmpvarx, x^2}
// eg {3, 3>ggbtmpvarx}
// eg {3>ggbtmpvarx, x^2}
// eg {3>ggbtmpvarx}
// works only for variables in form [A-Za-z]+ and if it's a simple number
private final static RegExp inequalitySimpleInList = RegExp.compile(
"(.*)([,{])(\\(*)?([-0-9.E]+)(\\)*)?>(=*)([A-Za-z]+)([,}\\)])(.*)");
// old version, causes problems with eg Solve[exp(x)<2]
// private final static RegExp inequalitySimpleInList =
// RegExp.compile("(.*)([,{\\(])([-0-9.E/\\(\\)]+)>(=*)([A-Za-z]+)([,}\\)])(.*)");
/**
* convert x>3 && x<7 into 3<x<7 convert 3>x into x<3 convert {3>x} into
* {x<3} eg output from Solve[x (x-1)(x-2)(x-3)(x-4)(x-5) < 0]
*
* Giac's normal command converts inequalities to > or >= so we don't need
* to check <, <=
*
* @param exp
* expression
* @return converted expression if changed
*/
protected String checkInequalityInterval(String exp) {
String ret = exp;
MatchResult matcher = inequalitySimple.exec(ret);
// swap 3>x into x<3
if (matcher != null && exp.startsWith(matcher.getGroup(1))) {
// Log.debug(matcher.getGroup(1));
// Log.debug(matcher.getGroup(2));
// Log.debug(matcher.getGroup(3));
// Log.debug(matcher.getGroup(4));
ret = matcher.getGroup(3) + "<" + matcher.getGroup(2)
+ matcher.getGroup(1);
Log.debug("giac output (with simple inequality converted): " + ret);
return ret;
}
// swap 5 > x && x > 3 into 3<x<5
while ((matcher = inequality.exec(ret)) != null &&
// TODO: check not x<3 && x<4
// check variable the same
// ie not x>5 && y<4
matcher.getGroup(2).equals(matcher.getGroup(11))) {
ret = matcher.getGroup(1) + matcher.getGroup(4) + "<"
+ matcher.getGroup(3) + matcher.getGroup(2) + "<"
+ matcher.getGroup(10) + matcher.getGroup(7)
+ matcher.getGroup(12);
}
// swap {3>x, 6>y} into {x<3, y<6}
while ((matcher = inequalitySimpleInList.exec(ret)) != null) {
// matcher.getGroup(6) is either "" or "="
// Log.debug("1 "+matcher.getGroup(1));
// Log.debug("2 "+matcher.getGroup(2));
// Log.debug("3XX"+matcher.getGroup(3)+"XX");
// Log.debug(""+matcher.getGroup(3).equals("undefined"));
// Log.debug("4 "+matcher.getGroup(4));
// Log.debug("5XX"+matcher.getGroup(5)+"XX");
// Log.debug("6 "+matcher.getGroup(6));
// Log.debug("7 "+matcher.getGroup(7));
// Log.debug("8 "+matcher.getGroup(8));
// Log.debug("9 "+matcher.getGroup(9));
String g3 = matcher.getGroup(3);
String g5 = matcher.getGroup(5);
// GWT regex bug? eg Solve[(2exp(x)-4)/(exp(x)-1) > 1],
// Solve[(x^2-x-2)/(-x^3+7x^2-14x+8)<2]
// #4710 GWT 2.7 beta 1 & RC1 needs the +"" as well
if ((g3 + "").equals("undefined")) {
g3 = "";
}
if ((g5 + "").equals("undefined")) {
g5 = "";
}
// Log.debug("g3= "+g3);
// Log.debug("g5= "+g5);
// eg "(" + "-2" + ")"
String g345 = g3 + matcher.getGroup(4) + g5;
String g7 = matcher.getGroup(7);
ret = matcher.getGroup(1) + matcher.getGroup(2) + g7 + "<"
+ matcher.getGroup(6) + g345 + matcher.getGroup(8)
+ matcher.getGroup(9);
}
if (!exp.equals(ret)) {
Log.debug("giac output (with inequality converted): " + ret);
}
return ret;
}
/**
* various improvements and hack for Giac's output
*
* @param s
* output from Giac
* @return result that GeoGebra can parse
*/
protected String postProcess(String s) {
// #5099 / TRAC-3566
// catch "dotvecteur Error: Invalid dimension"
// and "GIAC_ERROR:"
// (small bug in giac.js, not all errors have GIAC_ERROR:
if (StringUtil.toLowerCase(s).indexOf("error:") > -1) {
// GIAC_ERROR: canonical_form(3*ggbtmpvarx^4+ggbtmpvarx^2) Error:
// Bad Argument Value
Log.debug("error from Giac: " + s);
return "?";
}
String ret = s.trim();
// output from ifactor can be wrapped to stop simplification
// eg js giac output:-('3*5')
int primeOpen = ret.indexOf('\'');
while (primeOpen >= 0) {
int primeClose = ret.indexOf('\'', primeOpen + 1);
if (primeClose < 0) {
break;
}
// ((a')') -- delete brackets
if (primeClose == primeOpen + 2
&& ret.charAt(primeOpen + 1) == ')') {
int bracketOpen = ret.lastIndexOf('(', primeOpen);
if (bracketOpen >= 0) {
StringBuilder sb = new StringBuilder(ret);
sb = sb.replace(primeOpen + 1, primeOpen + 2, "");
sb = sb.replace(bracketOpen, bracketOpen + 1, "");
ret = sb.toString();
}
// primeOpen = primeClose;
} else {
int check = StringUtil.checkBracketsBackward(
ret.substring(primeOpen, primeClose));
// -('3*5') will have check = -1
if (check < 0) {
StringBuilder sb = new StringBuilder(ret);
sb = sb.replace(primeOpen, primeOpen + 1, "");
sb = sb.replace(primeClose - 1, primeClose, "");
ret = sb.toString();
primeOpen = ret.indexOf('\'', primeClose);
} else {
primeOpen = primeClose;
}
}
}
// #5099 / TRAC-3566 GIAC_ERROR: string missing
// if (ret.indexOf("Unable to solve differential equation") > 0) {
// return "?";
// }
if (ret.indexOf("integrate(") > -1) {
// eg Integral[sqrt(sin(x))]
return "?";
}
if (ret.indexOf("c_") > -1) {
nrOfReplacedConst += ret.length() / 3; // upper bound on number of
// constants in result
Log.debug("replacing arbitrary constants in " + ret);
ret = ret.replaceAll("c_([0-9]*)",
"arbconst($1+" + nrOfReplacedConst + ")");
}
if (ret.indexOf("n_") > -1) {
Log.debug("replacing arbitrary integers in " + ret);
ret = ret.replaceAll("n_([0-9]*)", "arbint($1)");
}
// convert Giac's scientific notation from e.g. 3.24e-4 to
// 3.2E-4
// not needed, Giac now outputs E
// ret = parserTools.convertScientificFloatNotation(ret);
ret = casParser.insertSpecialChars(ret); // undo special character
// handling
// don't do check for long strings eg 7^99999
if (ret.length() < 200) {
// convert x>3 && x<7 into 3<x<7
ret = checkInequalityInterval(ret);
}
return ret;
}
final private static String EVALFA = "evalfa(";
private StringBuilder expSB = new StringBuilder(EVALFA);
/**
* evalfa makes sure rootof() converted to decimal
* eg @rootof({{-4,10,-440,2025},{1,0,10,-200,375}})
*
* @param s
* input
* @return "evalfa(" + s + ")"
*/
protected String wrapInevalfa(String s) {
expSB.setLength(EVALFA.length());
expSB.append(s);
expSB.append(")");
return expSB.toString();
}
/**
* Test if Giac is up and running. Overridden in CASGiacW
*
* @return true if Giac is already loaded
*/
@Override
public boolean isLoaded() {
return true;
}
}