package org.geogebra.cas;
import static org.geogebra.test.util.IsEqualStringIgnoreWhitespaces.equalToIgnoreWhitespaces;
import static org.junit.Assert.assertThat;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.geogebra.cas.logging.CASTestLogger;
import org.geogebra.common.kernel.GeoGebraCasInterface;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.Command;
import org.geogebra.common.kernel.arithmetic.Traversing.CommandCollector;
import org.geogebra.common.kernel.geos.GeoCasCell;
import org.geogebra.common.util.debug.Log;
import org.geogebra.common.util.lang.Unicode;
import org.geogebra.desktop.main.AppDNoGui;
import org.geogebra.desktop.main.LocalizationD;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
public class ArbitraryConstIntegrationTest {
static public boolean silent = false;
static GeoGebraCasInterface cas;
static Kernel kernel;
static AppDNoGui app;
/**
* Logs all tests which don't give the expected but a valid result.
*/
static CASTestLogger logger;
@BeforeClass
public static void setupCas() {
app = new AppDNoGui(new LocalizationD(3), true);
if (silent) {
Log.setLogger(null);
}
// Set language to something else than English to test automatic
// translation.
app.setLanguage(Locale.GERMANY);
// app.fillCasCommandDict();
kernel = app.getKernel();
cas = kernel.getGeoGebraCAS();
logger = new CASTestLogger();
// Setting the general timeout to 9 seconds. Feel free to change this.
kernel.getApplication().getSettings().getCasSettings()
.setTimeoutMilliseconds(9000);
}
/**
* Handles the logs about test warnings.
*/
@AfterClass
public static void handleLogs() {
if (!silent) {
logger.handleLogs();
}
}
/**
* Before every test: Clear the construction list to make sure there is
* nothing already defined.
*/
@Before
public void beforeTest() {
kernel.clearConstruction(true);
}
/**
* @param input
* The input.
* @param expectedResult
* The expected result.
* @param validResults
* Valid, but undesired results.
*/
private static void ta(String input, String expectedResult,
String... validResults) {
String result;
try {
GeoCasCell f = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f, false);
f.setInput(input);
f.computeOutput();
result = getOutput(f);
} catch (Throwable t) {
String sts = "";
StackTraceElement[] st = t.getStackTrace();
for (int i = 0; i < 10 && i < st.length; i++) {
StackTraceElement stElement = st[i];
sts += stElement.getClassName() + ":"
+ stElement.getMethodName() + stElement.getLineNumber()
+ "\n";
}
result = t.getClass().getName() + ":" + t.getMessage() + sts;
}
assertThat(result, equalToIgnoreWhitespaces(logger, input,
expectedResult, validResults));
}
// 100 seconds max per method tested
@Rule
public Timeout globalTimeout = new Timeout(100, TimeUnit.SECONDS);
@Test
public void SolveODE_0() {
ta("SolveODE[y'=7y^2x^3]", "y = 4 / (4 * c_1 - 7 * x^(4))");
}
@Test
public void SolveODE_1() {
ta("SolveODE[y''-5y'+6y=0]", "y = c_1 * " + Unicode.EULER_STRING
+ "^(3 * x) + c_2 * " + Unicode.EULER_STRING + "^(2 * x)");
}
@Test
public void SolveODE_2() {
ta("SolveODE[y'=5y-3]",
"y = c_1 * " + Unicode.EULER_STRING + "^(5*x) + 3 / 5");
}
@Test
public void SolveODE_3() {
ta("SolveODE[y'+y=10]",
"y = c_1 * " + Unicode.EULER_STRING + "^(-x) + 10");
}
@Test
public void SolveODE_4() {
ta("SolveODE[y' = (3 - y) / 2]",
"y = c_1 * " + Unicode.EULER_STRING + "^((-x)/ 2) + 3");
}
@Test
public void SolveODE_5() {
ta("SolveODE[y' = -2 + y]",
"y = c_1 * " + Unicode.EULER_STRING + "^(x) + 2");
}
@Test
public void SolveODE_6() {
ta("SolveODE[y' = y(y - 2)]",
"y = (-2) / (c_1 *" + Unicode.EULER_STRING + "^(2*x) - 1)");
}
@Test
public void SolveODE_7() {
ta("SolveODE[y''=y]", "y = c_1 *" + Unicode.EULER_STRING
+ "^(x) + c_2 *" + Unicode.EULER_STRING + "^(-x)");
}
@Test
public void SolveODE_8() {
ta("SolveODE[2y''+y'-y=0]", "y = c_1 *" + Unicode.EULER_STRING
+ "^(-x) + c_2 *" + Unicode.EULER_STRING + "^(x/2)");
}
@Test
public void SolveODE_9() {
ta("SolveODE[y''-5y=0]",
"y = c_1 *" + Unicode.EULER_STRING + "^(sqrt(5) * x) + c_2 *"
+ Unicode.EULER_STRING + "^(-sqrt(5) * x)");
}
@Test
public void SolveODE_10() {
ta("SolveODE[2y''+3y'=0]",
"y = c_1 *" + Unicode.EULER_STRING + "^(-3 * x / 2) + c_2");
}
@Test
public void SolveODE_11() {
ta("SolveODE[y''+2y' + 101y = 0]",
"y=c_1 * cos(10 * x) *" + Unicode.EULER_STRING
+ "^(-x) + c_2 * " + Unicode.EULER_STRING
+ "^(-x)* sin(10 * x)");
}
@Test
public void SolveODE_12() {
ta("SolveODE[y'' + 4y' + 4y = 0]",
"y=c_1 * x * " + Unicode.EULER_STRING + "^(-2 * x) + c_2 * "
+ Unicode.EULER_STRING + "^(-2 * x)");
}
@Test
public void SolveODE_13() {
ta("SolveODE[y''=2y]",
"y = c_1 * " + Unicode.EULER_STRING + "^(sqrt(2) * x) + c_2 * "
+ Unicode.EULER_STRING + "^(-sqrt(2) * x)");
}
@Test
public void Integral_1() {
ta("Integral[(x+1)/(x+2*sqrt(x)-3)]",
"15 * log(sqrt(x) + 3) + log(abs(sqrt(x) - 1)) + x - 4*sqrt(x) + c_1");
}
@Test
public void Integral_2() {
ta("Integral[2sin(x)cos(x)]", "sin(x)^(2) + c_1");
}
@Test
public void Integral_3() {
ta("Integral[ " + Unicode.EULER_STRING + "^x/(1+ "
+ Unicode.EULER_STRING + "^(2x))]",
"arctan(" + Unicode.EULER_STRING + "^(x)) + c_1");
}
@Test
public void Integral_4() {
ta("Integral[sin(x)(4*cos(x)) " + Unicode.EULER_STRING
+ "^(2*cos(x)+1)]",
"-(2*cos(x) - 1) * " + Unicode.EULER_STRING
+ "^(2*cos(x) + 1) + c_1");
}
@Test
public void Integral_5() {
ta("Integral[x * cos(a * x)]",
"cos(a * x) / a^(2) + x * sin(a * x) / a + c_1");
}
@Test
public void Integral_6() {
ta("Integral[ln(x)/x]", "1 / 2 * log(x)^(2) + c_1");
}
@Test
public void Integral_7() {
ta("Integral[cos(x)^2 sin(x)]", "(-1) / 3 * cos(x)^(3) + c_1");
}
@Test
public void Integral_8() {
ta("Integral[(x^5+x^4+2 x^3+2 x^2+5x+9)/(x^2+1)^3]",
"1 / 4 * (12*x^(3) + 20*x - 4) / (x^(2) + 1)^(2) + 4*arctan(x) + 1 / 2 * log(x^(2) + 1) + c_1");
}
@Test
public void Integral_9() {
ta("Integral[x/(1-sqrt(2+x))]",
"-2 * (1 / 3 * sqrt(x + 2) * (x + 2) + 1 / 2 * (x + 2) - sqrt(x + 2) - log(abs(sqrt(x + 2) - 1))) + c_1");
}
@Test
public void Integral_10() {
ta("Integral[1 / sqrt(x - x^2)]", "arcsin(2*x - 1) + c_1");
}
/**
* @param input
* The input.
* @param inputUpdate
* The input to update the cell.
* @param expectedResult
* The expected result.
* @param validResults
* Valid, but undesired results.
*/
private static void casCellupdate(String input, String inputUpdate,
String expectedResult, String... validResults) {
String result;
try {
GeoCasCell f = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f, false);
f.setInput(input);
f.computeOutput();
f.setInput(inputUpdate);
f.computeOutput();
result = getOutput(f);
} catch (Throwable t) {
String sts = "";
StackTraceElement[] st = t.getStackTrace();
for (int i = 0; i < 10 && i < st.length; i++) {
StackTraceElement stElement = st[i];
sts += stElement.getClassName() + ":"
+ stElement.getMethodName() + stElement.getLineNumber()
+ "\n";
}
result = t.getClass().getName() + ":" + t.getMessage() + sts;
}
assertThat(result, equalToIgnoreWhitespaces(logger, input,
expectedResult, validResults));
}
@Test
public void ArbConst_Integration_1() {
casCellupdate("Integral[x]", "SolveODE[2y''+3y'=0]",
"y = c_1 *" + Unicode.EULER_STRING + "^(-3 * x / 2) + c_2");
}
@Test
public void ArbConst_Integration_2() {
casCellupdate("SolveODE[y''+9y=0]", "SolveODE[y''+4y=0]",
"y = c_1 * cos(2 * x) + c_2 * sin(2 * x)");
}
/**
* Note that first cell is updated after second cell.
*
* @param cell1Input
* The input of first cell.
* @param cell2Input
* The input of second cell.
* @param cell1InputUpdate
* The input to update first cell.
* @param cell2InputUpdate
* The input to update second cell.
* @param expectedResult
* The expected result.
* @param validResults
* Valid, but undesired results.
*/
private static void casCellupdate2(String cell1Input, String cell2Input,
String cell1InputUpdate, String cell2InputUpdate,
String expectedResult1, String expectedResult2) {
String result1, result2;
try {
GeoCasCell f1 = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f1, false);
f1.setInput(cell1Input);
f1.computeOutput();
GeoCasCell f2 = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f2, false);
f2.setInput(cell2Input);
f2.computeOutput();
f2.setInput(cell2InputUpdate);
f2.computeOutput();
result2 = getOutput(f2);
f1.setInput(cell1InputUpdate);
f1.computeOutput();
result1 = getOutput(f1);
} catch (Throwable t) {
String sts = "";
StackTraceElement[] st = t.getStackTrace();
for (int i = 0; i < 10 && i < st.length; i++) {
StackTraceElement stElement = st[i];
sts += stElement.getClassName() + ":"
+ stElement.getMethodName() + stElement.getLineNumber()
+ "\n";
}
result1 = t.getClass().getName() + ":" + t.getMessage() + sts;
result2 = t.getClass().getName() + ":" + t.getMessage() + sts;
}
String[] alternatives = new String[0];
assertThat(result1, equalToIgnoreWhitespaces(logger, cell1Input,
expectedResult1, alternatives));
assertThat(result2, equalToIgnoreWhitespaces(logger, cell2Input,
expectedResult2, alternatives));
}
private static String getOutput(GeoCasCell f2) {
HashSet<Command> commands = new HashSet<Command>();
f2.getInputVE().traverse(CommandCollector.getCollector(commands));
boolean includesNumericCommand = false;
if (!commands.isEmpty()) {
for (Command cmd : commands) {
String cmdName = cmd.getName();
// Numeric used
includesNumericCommand = includesNumericCommand
|| ("Numeric".equals(cmdName)
&& cmd.getArgumentNumber() > 1);
}
}
return f2.getOutputValidExpression() != null
? f2.getOutputValidExpression()
.toString(includesNumericCommand
? StringTemplate.testNumeric
: StringTemplate.testTemplate)
: f2.getOutput(StringTemplate.testTemplate);
}
/**
* Before redefinition: c_1 in first row, c_2 in second.
*
* After redefine 2: c_2 and c_3 in second
*
* After redefine 1: c_1 and c_4 in first
*/
@Test
public void ArbConst_Integration_3() {
casCellupdate2("Integral[x]", "Integral[sin(x)]", "SolveODE[y''+9y=0]",
"SolveODE[y''+4y=0]", "y = c_1 * cos(3 * x) + c_4 * sin(3 * x)",
"y = c_2 * cos(2 * x) + c_3 * sin(2 * x)");
}
/**
* Add first cell, update it, add second cell, update it.
*
* @param cell1Input
* The input of first cell.
* @param cell2Input
* The input of second cell.
* @param cell1InputUpdate
* The input to update first cell.
* @param cell2InputUpdate
* The input to update second cell.
* @param expectedResult
* The expected result.
* @param validResults
* Valid, but undesired results.
*/
private static void casCellupdate3(String cell1Input, String cell2Input,
String cell1InputUpdate, String cell2InputUpdate,
String expectedResult1, String expectedResult2,
String... validResults) {
String result1, result2;
try {
GeoCasCell f1 = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f1, false);
f1.setInput(cell1Input);
f1.computeOutput();
f1.setInput(cell1InputUpdate);
f1.computeOutput();
result1 = getOutput(f1);
GeoCasCell f2 = new GeoCasCell(kernel.getConstruction());
kernel.getConstruction().addToConstructionList(f2, false);
f2.setInput(cell2Input);
f2.computeOutput();
f2.setInput(cell2InputUpdate);
f2.computeOutput();
result2 = getOutput(f2);
} catch (Throwable t) {
String sts = "";
StackTraceElement[] st = t.getStackTrace();
for (int i = 0; i < 10 && i < st.length; i++) {
StackTraceElement stElement = st[i];
sts += stElement.getClassName() + ":"
+ stElement.getMethodName() + stElement.getLineNumber()
+ "\n";
}
result1 = t.getClass().getName() + ":" + t.getMessage() + sts;
result2 = t.getClass().getName() + ":" + t.getMessage() + sts;
}
assertThat(result1, equalToIgnoreWhitespaces(logger, cell1Input,
expectedResult1, validResults));
assertThat(result2, equalToIgnoreWhitespaces(logger, cell2Input,
expectedResult2, validResults));
}
@Test
/**
* First cell: c_1 before redefine, c_1 and c_2 after
*
* Second cell: c_3 before redefine, c_3 and c_4 after
*/
public void ArbConst_Integration_4() {
casCellupdate3("Integral[x]", "Integral[sin(x)]", "SolveODE[y''+9y=0]",
"SolveODE[y''+4y=0]", "y = c_1 * cos(3 * x) + c_2 * sin(3 * x)",
"y = c_3 * cos(2 * x) + c_4 * sin(2 * x)");
}
@Test
public void ConstMulti() {
ta("Simplify[SolveODE[ y*ln(2)]]", "y = c_1 * 2^(x)");
Assert.assertEquals(1, app.getGgbApi().getValue("c_1"), 0.01);
ta("SolveODE[ x]", "y = c_2 + 1 / 2 * x^(2)");
Assert.assertEquals(0, app.getGgbApi().getValue("c_2"), 0.01);
}
@Test
public void ReloadTest() {
ta("f(x):=sin(x)", "sin(x)");
ta("F(x):=Integral[sin(x)]", "-cos(x) + c_1");
for (int i = 0; i < 2; i++) {
app.getKernel().getAlgebraProcessor()
.processAlgebraCommand("P=(1,1)", true);
app.getKernel().getAlgebraProcessor()
.processAlgebraCommand("Q=(1,1)", true);
app.getGgbApi().undo(true);
app.getGgbApi().undo(true);
}
Assert.assertEquals(app.getGgbApi().getValueString("$2"),
"F(x):=-cos(x) + c_1");
String base64 = app.getGgbApi().getBase64();
app.getKernel().clearConstruction(true);
app.getGgbApi().setBase64(base64);
Assert.assertEquals(app.getGgbApi().getValueString("$2"),
"F(x):=-cos(x) + c_1");
}
}