1
1
package geogebra.cas;
3
import geogebra.cas.error.CASException;
4
import geogebra.cas.mpreduce.CASmpreduce;
5
import geogebra.kernel.GeoGebraCASInterface;
3
6
import geogebra.kernel.Kernel;
4
7
import geogebra.kernel.arithmetic.ExpressionNode;
8
import geogebra.kernel.arithmetic.ExpressionValue;
9
import geogebra.kernel.arithmetic.ValidExpression;
5
10
import geogebra.main.Application;
6
import jasymca.GeoGebraJasymca;
8
import java.util.HashMap;
10
import org.mathpiper.interpreters.EvaluationResponse;
11
import org.mathpiper.interpreters.Interpreter;
12
import org.mathpiper.interpreters.Interpreters;
11
import geogebra.util.MaxSizeHashMap;
13
import java.util.ArrayList;
16
17
* This class provides an interface for GeoGebra to use the computer algebra
18
* systems Maxima and MathPiper.
19
20
* @author Markus Hohenwarter
21
public class GeoGebraCAS {
22
public class GeoGebraCAS implements GeoGebraCASInterface {
23
private Interpreter ggbMathPiper;
24
private GeoGebraJasymca ggbJasymca;
25
private StringBuffer sbInsertSpecial, sbReplaceIndices, sbPolyCoeffs;
26
24
private Application app;
25
private Kernel kernel;
26
private CASparser casParser;
27
private CASgeneric cas;
28
private CASmpreduce casMPReduce;
29
public int currentCAS = -1;
28
31
public GeoGebraCAS(Kernel kernel) {
29
app = kernel.getApplication();
30
sbInsertSpecial = new StringBuffer(80);
31
sbReplaceIndices = new StringBuffer(80);
32
app = (Application) kernel.getApplication();
33
casParser = new CASparser(kernel);
35
// DO NOT init underlying CAS here to avoid hanging animation,
36
// see http://www.geogebra.org/trac/ticket/1565
40
public CASparser getCASparser() {
44
public synchronized CASgeneric getCurrentCAS() {
46
setCurrentCAS(Kernel.DEFAULT_CAS);
36
private void initCAS() {
39
ggbJasymca = new GeoGebraJasymca();
44
* Evaluates a JASYMCA expression and returns the result as a string,
45
* e.g. exp = "diff(x^2,x)" returns "2*x".
46
* @return result string, null possible
48
final public String evaluateJASYMCA(String exp) {
49
String result = ggbJasymca.evaluate(exp);
51
// to handle x(A) and x(B) they are converted
52
// to unicode strings in ExpressionNode,
53
// we need to convert them back here
54
result = insertSpecialChars(result);
56
//System.out.println("exp for JASYMCA: " + exp);
57
//System.out.println(" result: " + result);
63
* Evaluates a MathPiper expression and returns the result as a string in MathPiper syntax,
64
* e.g. evaluateMathPiper("D(x) (x^2)") returns "2*x".
66
* @return result string (null possible)
53
public int getCurrentCASstringType() {
55
case Application.CAS_MAXIMA:
56
return ExpressionNode.STRING_TYPE_MAXIMA;
58
case Application.CAS_MPREDUCE:
59
return ExpressionNode.STRING_TYPE_MPREDUCE;
62
case Application.CAS_MATHPIPER:
63
return ExpressionNode.STRING_TYPE_MATH_PIPER;
68
* Sets the currently used CAS for evaluateGeoGebraCAS().
69
* @param CAS: use CAS_MATHPIPER or CAS_MAXIMA
68
final synchronized public String evaluateMathPiper(String exp) {
71
public synchronized void setCurrentCAS(int CAS) {
72
// MathPiper has problems with indices like a_3, b_{12}
73
exp = replaceIndices(exp);
75
// evaluate the MathPiper expression
76
Interpreter mathpiper = getMathPiper();
77
response = mathpiper.evaluate(exp);
79
if (response.isExceptionThrown())
81
Application.debug("String for MathPiper: "+exp+"\nException from MathPiper: "+response.getExceptionMessage());
75
case Application.CAS_MAXIMA:
77
((CASmaxima) cas).initialize();
83
app.getSettings().getCasSettings().addListener(cas);
87
case Application.CAS_MATHPIPER:
84
result = response.getResult();
94
}catch (MaximaVersionUnsupportedExecption e){
95
app.showError("CAS.MaximaVersionUnsupported");
96
setCurrentCAS(Application.CAS_MPREDUCE);*/
97
}catch (Exception e) {
103
* Resets the cas and unbinds all variable and function definitions.
105
public void reset() {
106
getCurrentCAS().reset();
110
* Sets the number of signficiant figures (digits) that should be used as print precision for the
111
* output of Numeric[] commands.
113
* @param significantNumbers
115
public void setSignificantFiguresForNumeric(int significantNumbers) {
116
getCurrentCAS().setSignificantFiguresForNumeric(significantNumbers);
120
private CASmathpiper getMathPiper() {
121
if (currentCAS == Application.CAS_MATHPIPER)
122
return (CASmathpiper) cas;
124
return new CASmathpiper(casParser, new CasParserToolsImpl('e'));
128
private CASmaxima getMaxima() {
129
if (currentCAS == Application.CAS_MAXIMA)
130
return (CASmaxima) cas;
132
return new CASmaxima(casParser, new CasParserToolsImpl('b'));
135
private synchronized CASmpreduce getMPReduce() {
136
if (casMPReduce == null)
137
casMPReduce = new CASmpreduce(casParser, new CasParserToolsImpl('e'));
142
// * Returns whether var is a defined variable.
144
// public boolean isVariableBound(String var) {
145
// return cas.isVariableBound(var);
149
* Unbinds (deletes) variable.
153
public void unbindVariable(String var) {
154
getCurrentCAS().unbindVariable(var);
158
* Evaluates a valid expression and returns the resulting String in GeoGebra notation.
159
* @param casInput Input in GeoGebraCAS syntax
160
* @return evaluation result
161
* @throws CASException
163
public String evaluateGeoGebraCAS(ValidExpression casInput) throws CASException {
164
Kernel kernel = app.getKernel();
165
boolean oldDigits = kernel.internationalizeDigits;
166
kernel.internationalizeDigits = false;
168
String result = null;
169
CASException exception = null;
171
result = getCurrentCAS().evaluateGeoGebraCAS(casInput);
173
catch (CASException ce) {
177
kernel.internationalizeDigits = oldDigits;
180
// check if keep input command was successful
181
// e.g. for KeepInput[Substitute[...]]
182
// otherwise return input
183
if (casInput.isKeepInputUsed() &&
184
(exception != null || "?".equals(result)))
186
// return original input
187
return casInput.toString();
191
if (exception != null)
86
// undo special character handling
87
result = insertSpecialChars(result);
89
//Application.debug("String for MathPiper: "+exp+"\nresult: "+result);
92
} catch (Throwable th) {
93
//MathPiper.Evaluate("restart;");
100
* Evaluates a MathPiper expression wrapped in a command and returns the result as a string,
101
* e.g. wrapperCommand = "Factor", exp = "3*(a+b)" evaluates "Factor(3*(a+b)" and
104
* @return result string (null possible)
106
final synchronized public String evaluateMathPiper(String wrapperCommand, String exp) {
107
StringBuffer sb = new StringBuffer(exp.length()+wrapperCommand.length()+2);
108
sb.append(wrapperCommand);
112
return evaluateMathPiper(sb.toString());
116
* Returns the error message of the last MathPiper evaluation.
195
if (result != null) {
196
// get names of escaped global variables right
197
// e.g. "ggbcasvar1a" needs to be changed to "a"
198
// e.g. "ggbtmpvara" needs to be changed to "a"
199
result = app.getKernel().removeCASVariablePrefix(result, " ");
206
* Evaluates an expression in GeoGebraCAS syntax.
208
* @return result string in GeoGebra syntax (null possible)
209
* @throws CASException
211
final public String evaluateGeoGebraCAS(String exp) throws CASException {
214
ValidExpression inVE = casParser.parseGeoGebraCASInput(exp);
215
return evaluateGeoGebraCAS(inVE);
216
} catch (Throwable t)
218
throw new CASException(t);
223
* Evaluates an expression in the syntax of the currently active CAS
224
* (MathPiper or Maxima).
225
* @return result string (null possible)
228
final public String evaluateRaw(String exp) throws Throwable {
229
return getCurrentCAS().evaluateRaw(exp);
233
* Returns the error message of the last call of evaluateGeoGebraCAS().
117
234
* @return null if last evaluation was successful.
119
final synchronized public String getMathPiperError() {
120
if (response != null)
121
return response.getExceptionMessage();
126
EvaluationResponse response ;
128
private synchronized Interpreter getMathPiper() {
129
if (ggbMathPiper == null) {
130
// where to find MathPiper scripts
131
//eg docBase = "jar:http://www.geogebra.org/webstart/alpha/geogebra_cas.jar!/";
132
String scriptBase = "jar:" + app.getCodeBase().toString() +
133
Application.CAS_JAR_NAME + "!/";
135
ggbMathPiper = Interpreters.getSynchronousInterpreter(scriptBase);
136
boolean success = initMyMathPiperFunctions();
139
System.err.println("MathPiper creation failed with scriptbase: " + scriptBase);
140
ggbMathPiper = Interpreters.getSynchronousInterpreter(scriptBase);
141
success = initMyMathPiperFunctions();
143
System.out.println("MathPiper creation failed again with null scriptbase");
151
* Initialize special commands needed in our ggbMathPiper instance,e.g.
152
* getPolynomialCoeffs(exp,x).
154
private synchronized boolean initMyMathPiperFunctions() {
155
// Expand expression and get polynomial coefficients using MathPiper:
156
// getPolynomialCoeffs(expr,x) :=
157
// If( CanBeUni(expr),
159
// Coef(MakeUni(expr,x),x, 0 .. Degree(expr,x)); ],
162
String strGetPolynomialCoeffs = "getPolynomialCoeffs(expr,x) := If( CanBeUni(expr),[ Coef(MakeUni(expr,x),x, 0 .. Degree(expr,x));],{});";
163
EvaluationResponse resp = ggbMathPiper.evaluate(strGetPolynomialCoeffs);
164
if (resp.isExceptionThrown()) {
168
// make sure we get (x^n)^2 not x^n^2
170
ggbMathPiper.evaluate("LeftPrecedence(\"^\",19);");
172
// make sure Factor((((((((9.1) * ((x)^(7))) - ((32) * ((x)^(6)))) + ((48) * ((x)^(5)))) - ((40) * ((x)^(4)))) + ((20) * ((x)^(3)))) - ((6) * ((x)^(2)))) + (x))] works
173
String initFactor = "Factors(p_IsRational)_(Denom(p) != 1) <-- {{Factor(Numer(p)) / Factor(Denom(p)) , 1}};";
174
ggbMathPiper.evaluate(initFactor);
176
// define constanct for Degree
177
response = ggbMathPiper.evaluate("Degree := 180/pi;");
183
final public String simplifyMathPiper(String exp) {
184
return evaluateMathPiper("Simplify", exp );
187
final public String factorMathPiper(String exp) {
188
return evaluateMathPiper("Factor", exp );
191
final public String expandMathPiper(String exp) {
192
return evaluateMathPiper("ExpandBrackets", exp );
195
HashMap getPolynomialCoeffsCache = new HashMap(50);
196
StringBuffer getPolynomialCoeffsSB = new StringBuffer();
199
* Expands the given MathPiper expression and tries to get its polynomial
236
final synchronized public String getGeoGebraCASError() {
237
return getCurrentCAS().getEvaluateGeoGebraCASerror();
241
* Evaluates an expression in MathPiper syntax.
242
* @return result string (null possible)
243
* @deprecated since GeoGebra 4.0
245
final public String evaluateMathPiper(String exp) {
246
return getMathPiper().evaluateMathPiper(exp);
250
* Evaluates an expression in Maxima syntax.
251
* @return result string (null possible)
253
final public String evaluateMaxima(String exp) {
254
return getMaxima().evaluateMaxima(exp);
256
* @throws Throwable */
258
final public String evaluateMPReduce(String exp) throws CASException
260
return getMPReduce().evaluateMPReduce(exp);
264
// these variables are cached to gain some speed in getPolynomialCoeffs
265
private Map<String, String[]> getPolynomialCoeffsCache = new MaxSizeHashMap<String, String[]>(Kernel.GEOGEBRA_CAS_CACHE_SIZE);
266
private StringBuilder getPolynomialCoeffsSB = new StringBuilder();
267
private StringBuilder sbPolyCoeffs = new StringBuilder();
270
* Expands the given MPreduce expression and tries to get its polynomial
200
271
* coefficients. The coefficients are returned in ascending order. If exp is
201
272
* not a polynomial, null is returned.
203
274
* example: getPolynomialCoeffs("3*a*x^2 + b"); returns ["b", "0", "3*a"]
205
final public String[] getPolynomialCoeffs(String MathPiperExp, String variable) {
206
//return ggbJasymca.getPolynomialCoeffs(MathPiperExp, variable);
276
final public String[] getPolynomialCoeffs(String polyExpr, String variable) {
208
277
getPolynomialCoeffsSB.setLength(0);
209
getPolynomialCoeffsSB.append(MathPiperExp);
210
getPolynomialCoeffsSB.append(':');
278
getPolynomialCoeffsSB.append(polyExpr);
279
getPolynomialCoeffsSB.append(',');
211
280
getPolynomialCoeffsSB.append(variable);
213
String result = (String)(getPolynomialCoeffsCache.get(getPolynomialCoeffsSB.toString()));
215
if (result != null) {
216
//Application.debug("using cached result: "+result);
217
// remove { } to get "b, 0, 3*a"
218
result = result.substring(1, result.length()-1);
220
// split to get coefficients array ["b", "0", "3*a"]
221
String [] coeffs = result.split(",");
226
if (sbPolyCoeffs == null)
227
sbPolyCoeffs = new StringBuffer();
229
sbPolyCoeffs.setLength(0);
232
/* replaced Michael Borcherds 2009-02-08
233
* doesn't seem to work properly polyCoeffsbug.ggb
235
sbPolyCoeffs.append("getPolynomialCoeffs(");
236
sbPolyCoeffs.append(MathPiperExp);
237
sbPolyCoeffs.append(',');
238
sbPolyCoeffs.append(variable);
282
String[] result = getPolynomialCoeffsCache.get(getPolynomialCoeffsSB.toString());
286
sbPolyCoeffs.setLength(0);
287
sbPolyCoeffs.append("coeff(");
288
sbPolyCoeffs.append(getPolynomialCoeffsSB.toString());
239
289
sbPolyCoeffs.append(')');
242
// Expand expression and get polynomial coefficients using MathPiper:
244
// exp := ExpandBrackets( 3*a*x^2 + b ),
245
// Coef(exp, x, 0 .. Degree(exp, x))
247
//sbPolyCoeffs.append("Prog( Local(exp), exp := ExpandBrackets(");
248
//sbPolyCoeffs.append(MathPiperExp);
249
//sbPolyCoeffs.append("), Coef(exp, x, 0 .. Degree(exp, x)))");
252
292
// expand expression and get coefficients of
253
293
// "3*a*x^2 + b" in form "{ b, 0, 3*a }"
254
result = evaluateMathPiper(sbPolyCoeffs.toString());
256
// empty list of coefficients -> return null
257
if ("{}".equals(result) || "".equals(result) || result == null)
261
//Application.debug("caching result: "+result);
294
String tmp = evaluateMPReduce(sbPolyCoeffs.toString());
297
if ("{}".equals(tmp) || "".equals(tmp) || tmp == null)
300
// not a polynomial: result still includes the variable, e.g. "x"
301
if (tmp.indexOf(variable) >= 0)
304
// get names of escaped global variables right
305
// e.g. "ggbcasvara" needs to be changed to "a"
306
tmp = app.getKernel().removeCASVariablePrefix(tmp);
308
tmp = tmp.substring(1, tmp.length()-1); // strip '{' and '}'
309
result = tmp.split(",");
262
311
getPolynomialCoeffsCache.put(getPolynomialCoeffsSB.toString(), result);
264
//Application.debug(sbPolyCoeffs+"");
265
//Application.debug(result+"");
267
// remove { } to get "b, 0, 3*a"
268
result = result.substring(1, result.length()-1);
270
// split to get coefficients array ["b", "0", "3*a"]
271
String [] coeffs = result.split(",");
275
315
Application.debug("GeoGebraCAS.getPolynomialCoeffs(): " + e.getMessage());
276
316
//e.printStackTrace();
284
* Converts all index characters ('_', '{', '}') in the given String
285
* to "unicode" + charactercode + DELIMITER Strings. This is needed because
286
* MathPiper does not handle indices correctly.
288
private synchronized String replaceIndices(String str) {
289
int len = str.length();
290
sbReplaceIndices.setLength(0);
292
boolean foundIndex = false;
294
// convert every single character and append it to sb
295
for (int i = 0; i < len; i++) {
296
char c = str.charAt(i);
299
boolean replaceCharacter = false;
301
case '_': // start index
303
replaceCharacter = true;
308
replaceCharacter = true;
314
replaceCharacter = true;
315
foundIndex = false; // end of index
320
replaceCharacter = false;
323
if (replaceCharacter) {
324
sbReplaceIndices.append(ExpressionNode.UNICODE_PREFIX);
325
sbReplaceIndices.append(code);
326
sbReplaceIndices.append(ExpressionNode.UNICODE_DELIMITER);
328
sbReplaceIndices.append(c);
332
return sbReplaceIndices.toString();
336
* Reverse operation of removeSpecialChars().
337
* @see ExpressionNode.operationToString() for XCOORD, YCOORD
339
private String insertSpecialChars(String str) {
340
int len = str.length();
341
sbInsertSpecial.setLength(0);
343
// convert every single character and append it to sb
344
char prefixStart = ExpressionNode.UNICODE_PREFIX.charAt(0);
345
int prefixLen = ExpressionNode.UNICODE_PREFIX.length();
347
for (int i = 0; i < len; i++) {
348
char c = str.charAt(i);
351
// first character of prefix found
352
if (c == prefixStart) {
356
for (int k = 0; k < prefixLen; k++, j++) {
357
if (ExpressionNode.UNICODE_PREFIX.charAt(k) != str
365
// try to get the unicode
368
while (j < len && Character.isDigit(digit = str.charAt(j))) {
369
code = 10 * code + (digit - 48);
373
if (code > 0 && code < 65536) { // valid unicode
374
sbInsertSpecial.append((char) code);
377
sbInsertSpecial.append(ExpressionNode.UNICODE_PREFIX);
381
sbInsertSpecial.append(c);
384
sbInsertSpecial.append(c);
387
return sbInsertSpecial.toString();
392
* public static void main(String [] args) {
394
* GeoGebraCAS cas = new GeoGebraCAS();
396
* Application.debug("GGBCAS"); // Read/eval/print loop int i=1;
397
* while(true){ Application.debug( "(In"+i+") "); // Prompt try{ String line =
398
* readLine(System.in); //String result = MathPiper.Evaluate(line);
400
* String result = cas.evaluateJASYMCA(line);
402
* Application.debug( "(Out"+i+") "+result ); i++; }catch(Exception e){
403
* Application.debug("\n"+e); } } }
322
final private String toString(ExpressionValue ev, boolean symbolic) {
324
return ev.toString();
326
return ev.toValueString();
331
* Returns the CAS command for the currently set CAS using the given key and command arguments.
332
* For example, getCASCommand("Expand.1", {"3*(a+b)"}) returns "ExpandBrackets( 3*(a+b) )" when
333
* MathPiper is the currently used CAS.
335
final synchronized public String getCASCommand(String name, ArrayList args, boolean symbolic) {
336
StringBuilder sbCASCommand = new StringBuilder(80);
338
// build command key as name + ".N"
339
sbCASCommand.setLength(0);
340
sbCASCommand.append(name);
341
sbCASCommand.append(".N");
343
String translation = getCurrentCAS().getTranslatedCASCommand(sbCASCommand.toString());
345
// check for eg Sum.N=sum(%)
346
if (translation != null) {
347
sbCASCommand.setLength(0);
348
for (int i = 0; i < translation.length(); i++) {
349
char ch = translation.charAt(i);
351
if (args.size() == 1) { // might be a list as the argument
352
ExpressionValue ev = (ExpressionValue) args.get(0);
353
String str = toString(ev, symbolic);
354
if (ev.isListValue()) {
355
// is a list, remove { and }
356
// resp. list( ... ) in mpreduce
357
if (currentCAS==Application.CAS_MPREDUCE)
358
sbCASCommand.append(str.substring(22, str.length() - 2));
360
sbCASCommand.append(str.substring(1, str.length() - 1));
362
// not a list, just append
363
sbCASCommand.append(str);
367
for (int j=0; j < args.size(); j++) {
368
ExpressionValue ev = (ExpressionValue) args.get(j);
369
sbCASCommand.append(toString(ev, symbolic));
370
sbCASCommand.append(',');
373
sbCASCommand.setLength(sbCASCommand.length() - 1);
376
sbCASCommand.append(ch);
381
return sbCASCommand.toString();
384
// build command key as name + "." + args.size()
386
sbCASCommand.setLength(sbCASCommand.length() - 1);
388
sbCASCommand.append(args.size());
390
// get translation ggb -> MathPiper/Maxima
391
translation = getCurrentCAS().getTranslatedCASCommand(sbCASCommand.toString());
392
sbCASCommand.setLength(0);
394
// no translation found:
395
// use key as function name
396
if (translation == null) {
398
// convert command names x, y, z to xcoord, ycoord, ycoord to protect it in CAS
399
// see http://www.geogebra.org/trac/ticket/1440
400
boolean handled = false;
401
if (name.length() == 1) {
402
char ch = name.charAt(0);
403
if (ch == 'x' || ch == 'y' || ch == 'z') {
404
sbCASCommand.append(ch);
405
sbCASCommand.append("coord");
410
// standard case: add ggbcasvar prefix to name for CAS
412
sbCASCommand.append(app.getKernel().printVariableName(name));
414
sbCASCommand.append('(');
415
for (int i=0; i < args.size(); i++) {
416
ExpressionValue ev = (ExpressionValue) args.get(i);
417
sbCASCommand.append(toString(ev,symbolic));
418
sbCASCommand.append(',');
420
sbCASCommand.setCharAt(sbCASCommand.length()-1, ')');
423
// translation found:
424
// replace %0, %1, etc. in translation by command arguments
426
for (int i = 0; i < translation.length(); i++) {
427
char ch = translation.charAt(i);
429
// get number after %
431
int pos = translation.charAt(i) - '0';
432
if (pos >= 0 && pos < args.size()) {
433
// success: insert argument(pos)
434
ExpressionValue ev = (ExpressionValue) args.get(pos);
436
sbCASCommand.append(ev.toString());
438
sbCASCommand.append(ev.toValueString());
441
sbCASCommand.append(ch);
442
sbCASCommand.append(translation.charAt(i));
445
sbCASCommand.append(ch);
450
return sbCASCommand.toString();
454
* Returns true if the two input expressions are structurally equal.
455
* For example "2 + 2/3" is structurally equal to "2 + (2/3)"
456
* but unequal to "(2 + 2)/3"
457
* @param internalInput includes internal command names
458
* @param localizedInput includes localized command names
460
public boolean isStructurallyEqual(ValidExpression inputVE, String localizedInput) {
463
String input1normalized = casParser.toString(inputVE, ExpressionNode.STRING_TYPE_GEOGEBRA_XML);
466
ValidExpression ve2 = casParser.parseGeoGebraCASInputAndResolveDummyVars(localizedInput);
467
String input2normalized = casParser.toString(ve2, ExpressionNode.STRING_TYPE_GEOGEBRA_XML);
469
// compare if the parsed expressions are equal
470
return input1normalized.equals(input2normalized);
472
catch (Throwable th) {
473
System.err.println("GeoGebraCAS.isStructurallyEqual: " + th.getMessage() );
b'\\ No newline at end of file'