package lipstone.joshua.parser;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Stack;
import java.util.concurrent.locks.ReentrantLock;
import lipstone.joshua.parser.backend.StepSolveThread;
import lipstone.joshua.parser.exceptions.ParserException;
import lipstone.joshua.parser.exceptions.UndefinedResultException;
import lipstone.joshua.parser.types.BigDec;
import lipstone.joshua.parser.util.ConsCell;
import lipstone.joshua.parser.util.ConsType;
import lipstone.joshua.parser.util.PairedList;
import fileManager.NativeLoader;
public class CAS {
static {
String[] failed = NativeLoader.loadNatives("CAS");
//If any libraries failed to load, we can try to load them from the system path
for (String fail : failed)
System.loadLibrary(fail);
primesLock = new ReentrantLock();
getPrimes(100000);
}
protected BigDec accuracy = new BigDec(0.000001, 0);
private final Parser parser;
public final BigDec defaultDelta = new BigDec(5.0, 0);
private BigDec xMin = BigDec.ZERO, xMax = new BigDec(2000), domainMax = new BigDec(2000), domainMin = new BigDec(-2000), delta = defaultDelta;
private String method = "domain";
public boolean configWindowExists = false;
protected Stack<ConsCell> initialInput = new Stack<ConsCell>();
private String[] substitutions = {"sin", "cos", "tan", "sec", "csc", "cot", "arcsin", "arccos", "arctan", "arcsec", "arccsc", "arccot", "ln",
"e^", "sinh", "cosh", "tanh", "sech", "csch", "coth", "("};
private String[] reverseSubstitutions = {"arcsin(z)", "arccos(z)", "arctan(z)", "arcsec(z)", "arccsc(z)", "arccot(z)", "sin(z)", "cos(z)",
"tan(z)", "sec(z)", "csc(z)", "cot(z)", "e^(z)", "log(z, e)", "arcsinh(z)", "arccosh(z)", "arctanh(z)", "arcsech(z)", "arccsch(z)",
"arccoth(z)", "z"};
private static long primesLimit = 0;
private static long[] primes;
private static final ReentrantLock primesLock;
private ConsCell quadraticEquation = null, linearEquation = null;
private ArrayList<ConsCell>[] quadraticEquationHooks = (ArrayList<ConsCell>[]) Array.newInstance(new ArrayList<ConsCell>().getClass(), 4);
private ConsCell[] linearEquationHooks = new ConsCell[2];
public CAS(Parser parser) {
this.parser = parser;
}
public ConsCell solve(ConsCell input) throws ParserException {
ArrayList<ConsCell> parts = input.splitOnSeparator();
if (parts.size() >= 4)
delta = (BigDec) parser.run(parts.get(3)).getCar();
if (parts.size() >= 3) {
setDomain((BigDec) parser.run(parts.get(1)).getCar(), (BigDec) parser.run(parts.get(2)).getCar());
if (parts.get(1).toString().toLowerCase().contains("infinity") || parts.get(2).toString().toLowerCase().contains("infinity"))
method = "infinite";
}
// Replace keywords with their respective values
input = parser.replaceKeywords(input);
parts = parts.get(0).splitOnIdentifier("=");
if (parts.size() > 2)
throw new UndefinedResultException("Incorrectly formatted equation. Please use only 2-sided equalities.", null);
if (parts.size() > 1) {
if (parser.getVars().size() == 0 || !parser.containsVariables(input, parser.getVars())) {
if (new BigDec(parser.run(parts.get(0)).carToString()).eq(new BigDec(parser.run(parts.get(1)).carToString())))
return new ConsCell("true", ConsType.IDENTIFIER);
else
return new ConsCell("false", ConsType.IDENTIFIER);
}
input = parts.get(0).getLastConsCell().append(invertSign(parts.get(1))).getFirstConsCell();
}
try {
initialInput.push(input.clone());
ArrayList<BigDec> solutionsList = solve(input, true, true);
ConsCell solutions = new ConsCell();
for (BigDec d : solutionsList)
solutions = solutions.append(new ConsCell(",", ConsType.SEPARATOR, new ConsCell(d, ConsType.NUMBER)));
if (!solutions.isNull())
solutions = solutions.getFirstConsCell().remove();
else
solutions = new ConsCell("no solution in", ConsType.IDENTIFIER, Tokenizer.tokenizeString("(" + domainMin + ", " + domainMax + ")"));
return solutions;
}
finally {
initialInput.pop();
}
}
private ArrayList<BigDec> solve(ConsCell input, boolean useSub, boolean useOneTerm) throws ParserException {
parser.setVars(parser.getVariables(input));
ArrayList<BigDec> solutionsList = new ArrayList<BigDec>();
input = simplifyTerms(input);
try {
if (solutionsList.size() == 0 && useOneTerm)
solutionsList = oneTermSolve(factoring(input));
if (solutionsList.size() == 0 && useSub)
solutionsList = substitutionSolve(input);
parser.setVars(parser.getVariables(input));
if (useSub)
input = initialInput.peek().clone();
//input = parser.seekOperations(input);
if (solutionsList.size() == 0)
solutionsList = formSolve(input);
if (solutionsList.size() == 0)
solutionsList = rationalRootsSolve(input);
}
catch (ParserException e) {}
if (solutionsList.size() == 0 && useSub && useOneTerm)
solutionsList = stepSolve(initialInput.peek());
if (useSub && useOneTerm && !solutionsList.contains(BigDec.ZERO) && solutionsList.size() < orderOfEquation(simplifyTerms(input), parser.getVars()).abs().doubleValue()) {
if (isAnswer(input, BigDec.ZERO, BigDec.ZERO, accuracy))
solutionsList.add(BigDec.ZERO);
}
Collections.sort(solutionsList);
method = "domain";
return solutionsList;
}
private ArrayList<BigDec> checkSolutions(ArrayList<BigDec> solutionsList, ConsCell equation, BigDec accuracy) throws ParserException {
for (int i = 0; i < solutionsList.size(); i++)
if (!isAnswer(equation, solutionsList.get(i), BigDec.ZERO, accuracy))
solutionsList.remove(i--);
return solutionsList;
}
private ArrayList<BigDec> stepSolve(ConsCell equation) throws ParserException {
if (method.equalsIgnoreCase("infinite"))
return stepSolveInfinite(equation);
else
return stepSolveDomain(equation, xMin, xMax, delta);
}
public ArrayList<BigDec> stepSolveDomain(ConsCell equation, BigDec xMin, BigDec xMax, BigDec delta) throws ParserException {
ArrayList<BigDec> output = new ArrayList<BigDec>();
StepSolveThread thread = new StepSolveThread(equation, parser, delta, accuracy, xMin, xMax, parser.getVars(), new BigDec(500));
parser.getFjPool().execute(thread);
output = thread.join();
if (thread.error != null)
throw thread.error;
return output;
}
private ArrayList<BigDec> stepSolveInfinite(ConsCell equation) throws ParserException {
BigDec xMin = BigDec.ZERO, xMax = new BigDec(1000);
ArrayList<BigDec> answers = new ArrayList<BigDec>();
while (answers.size() == 0) {
for (BigDec answer : stepSolveDomain(equation, xMin, xMax, delta))
answers.add(answer);
if (xMin.gteq(BigDec.ZERO))
xMin = xMin.add(xMax.multiply(new BigDec(2)));
xMin = xMin.multiply(BigDec.MINUSONE);
}
return answers;
}
/**
* Solves equations using their form (i.e. linear, quadratic)
*
* @param equation
* the equation to check
* @return the solutions to the equation if it could find them, otherwise, an empty ArrayList
* @throws ParserException
*/
private ArrayList<BigDec> formSolve(ConsCell equation) throws ParserException {
ArrayList<BigDec> solutions = new ArrayList<BigDec>();
ArrayList<ConsCell> terms = foldSigns(getTerms(simplifyTerms(equation)));
PairedList<ConsCell, BigDec> orders = new PairedList<>();
BigDec highestOrder = BigDec.ZERO;
for (ConsCell term : terms) {
PairedList<ConsCell, ConsCell> order = orderOfTerm(term, parser.getVars());
if (order.size() > 1)
return new ArrayList<BigDec>();
BigDec o = BigDec.ZERO;
if (order.size() > 0 && order.get(order.getKeys().get(0)).getCarType() == ConsType.NUMBER)
o = ((BigDec) order.get(order.getKeys().get(0)).getCar());
if (o.gt(highestOrder))
highestOrder = o;
orders.put(term, o);
}
BigDec modifier = null;
BigDec[] form = new BigDec[]{BigDec.ONE, BigDec.ZERO};
if ((modifier = matchesForm(orders, highestOrder, form)) != null) { //Linear
if (linearEquation == null) { //Initialize the linear equation structure. This makes subsequent calls faster.
linearEquationHooks[0] = new ConsCell("a", ConsType.IDENTIFIER);
linearEquationHooks[1] = new ConsCell("b", ConsType.IDENTIFIER, new ConsCell('/', ConsType.OPERATOR, linearEquationHooks[0]));
linearEquation = new ConsCell('-', ConsType.OPERATOR, linearEquationHooks[1]);
}
BigDec a = BigDec.ZERO, b = BigDec.ZERO;
form[0] = form[0].subtract(modifier);
form[1] = form[1].subtract(modifier);
for (ConsCell term : orders) {
BigDec order = orders.get(term);
if (order.eq(form[1]))
b = b.add(getCoefficient(term));
else
a = a.add(getCoefficient(term));
}
linearEquationHooks[0].replaceCar(new ConsCell(a, ConsType.NUMBER));
linearEquationHooks[1].replaceCar(new ConsCell(b, ConsType.NUMBER));
solutions.add(new BigDec(parser.run(linearEquation.clone()).toString()));
}
if (solutions.size() == 0 && (modifier = matchesForm(orders, highestOrder, (form = new BigDec[]{new BigDec(2), BigDec.ONE, BigDec.ZERO}))) != null) { //Quadratic
if (quadraticEquation == null) { //Initialize the quadratic equation structure. This makes subsequent calls faster.
quadraticEquation = Tokenizer.tokenizeString("(-b\"+-\"(b^2-4*a*c)^0.5)/(2*a)");
quadraticEquationHooks[0] = quadraticEquation.allInstancesOf(new ConsCell("a", ConsType.IDENTIFIER));
quadraticEquationHooks[1] = quadraticEquation.allInstancesOf(new ConsCell("b", ConsType.IDENTIFIER));
quadraticEquationHooks[2] = quadraticEquation.allInstancesOf(new ConsCell("c", ConsType.IDENTIFIER));
quadraticEquationHooks[3] = quadraticEquation.allInstancesOf(new ConsCell("+-", ConsType.STRING));
}
BigDec a = BigDec.ZERO, b = BigDec.ZERO, c = BigDec.ZERO;
form[0] = form[0].subtract(modifier);
form[1] = form[1].subtract(modifier);
form[2] = form[2].subtract(modifier);
for (ConsCell term : orders) {
BigDec order = orders.get(term);
if (order.eq(form[2]))
c = c.add(getCoefficient(term));
else if (order.eq(form[1]))
b = b.add(getCoefficient(term));
else
a = a.add(getCoefficient(term));
}
for (ConsCell cell : quadraticEquationHooks[0])
cell.replaceCar(new ConsCell(a, ConsType.NUMBER)); //a
for (ConsCell cell : quadraticEquationHooks[1])
cell.replaceCar(new ConsCell(b, ConsType.NUMBER)); //b
quadraticEquationHooks[2].get(0).replaceCar(new ConsCell(c, ConsType.NUMBER)); //c
quadraticEquationHooks[3].get(0).replaceCar(new ConsCell('+', ConsType.OPERATOR)); //+
solutions.add(new BigDec(parser.run(quadraticEquation.clone()).toString()));
quadraticEquationHooks[3].get(0).replaceCar(new ConsCell('-', ConsType.OPERATOR)); //-
solutions.add(new BigDec(parser.run(quadraticEquation.clone()).toString()));
}
return checkSolutions(solutions, equation, accuracy);
}
private BigDec matchesForm(PairedList<ConsCell, BigDec> orders, BigDec highestOrder, BigDec[] form) throws UndefinedResultException {
BigDec modifier = form[0].subtract(highestOrder);
for (BigDec order : orders.getValues()) {
boolean matches = false;
for (BigDec o : form)
if (o.eq(order.add(modifier))) {
matches = true;
break;
}
if (!matches)
return null;
}
return modifier;
}
private ArrayList<BigDec> rationalRootsSolve(ConsCell equation) throws ParserException {
return checkSolutions(getRoots(equation), equation, accuracy);
}
/**
* Gets the roots of an equation using the Rational Roots method
*
* @param equation
* the equation to get the roots of, already simplified (so the first term has the highest order, and the last
* term has the highest order
* @return the roots of the equation
* @throws ParserException
*/
private ArrayList<BigDec> getRoots(ConsCell equation) throws ParserException {
ArrayList<BigDec> roots = new ArrayList<BigDec>();
ArrayList<ConsCell> terms = getTerms(equation);
if (terms.size() < 1)
return roots;
BigDec highestCo = getCoefficient(terms.get(0)), lowestCo = getCoefficient(terms.get(terms.size() - 1));
if (!highestCo.isInt()) {
lowestCo = lowestCo.divide(highestCo);
highestCo = BigDec.ONE;
if (!lowestCo.isInt())
return roots;
}
if (!lowestCo.isInt()) {
highestCo = highestCo.divide(lowestCo);
lowestCo = BigDec.ONE;
if (!highestCo.isInt())
return roots;
}
ArrayList<BigDec> aN = findFactors(highestCo), a0 = findFactors(lowestCo);
for (BigDec i : a0)
for (BigDec a : aN) {
if (new BigDec(i).divide(new BigDec(a)).getInfinity() != 0)
continue;
roots.add(new BigDec(i).divide(new BigDec(a)));
roots.add(new BigDec(i).divide(new BigDec(a)).multiply(BigDec.MINUSONE));
}
return roots;
}
/**
* Main switch for the factoring algorithm.
*
* @param equation
* the equation to factor
* @return the factored form of the equation if possible, otherwise it returns the original.
* @throws ParserException
*/
public ConsCell factoring(ConsCell equation) throws ParserException {
ConsCell output = removeExcessParentheses(rationalRootsFactoring(equation));
if (!output.equals(equation))
return output;
return equation;
}
/**
* Factors an equation using the rational roots method.
*
* @param equation
* the equation must have had the right half negated (so that it = 0) and had simplifyTerms called on it.
* @return the fully factored equation if possible.
* @throws ParserException
*/
private ConsCell rationalRootsFactoring(ConsCell equation) throws ParserException {
ConsCell original = equation.clone();
if (orderOfEquation(equation, parser.getVars()).lt(new BigDec(2)))
return equation;
ArrayList<ConsCell> eqn = foldSigns(getTerms(equation));
BigDec highestCoefficient = getCoefficient(eqn.get(0)), lowestCoefficient = getCoefficient(eqn.get(eqn.size() - 1)), lcm = BigDec.ONE;
if (!highestCoefficient.isInt() || !lowestCoefficient.isInt()) {
lcm = ((BigDec) highestCoefficient.toFraction().getCar(2)).lcm((BigDec) lowestCoefficient.toFraction().getCar(2));
equation = eqn.get(0).getLastConsCell().append(new ConsCell('*', ConsType.OPERATOR, new ConsCell(lcm, ConsType.NUMBER)));
for (int i = 0; i < eqn.size(); i++)
equation = equation.append(new ConsCell('+', ConsType.OPERATOR, eqn.get(i).getLastConsCell().append(new ConsCell('*', ConsType.OPERATOR, new ConsCell(lcm, ConsType.NUMBER))).getFirstConsCell().clone()));
equation = equation.getFirstConsCell();
equation = simplifyTerms(parser.removeDoubles(equation));
}
ArrayList<BigDec> roots = getRoots(equation);
for (BigDec root : roots) {
ConsCell output = polynomialDivision(equation, new ConsCell(parser.getVars().get(0), ConsType.IDENTIFIER, new ConsCell('+', ConsType.OPERATOR, new ConsCell(root, ConsType.NUMBER))),
new ConsCell(parser.getVars().get(0), ConsType.IDENTIFIER), true);
ConsCell last = output.getLastConsCell();
if (last.getCarType() == ConsType.OBJECT && last.getCar().equals("{false}")) { //If the polynomial division succeeded, return the factored form
last.remove();
ConsCell result = parser.removeDoubles(new ConsCell(new ConsCell(parser.getVars().get(0), ConsType.IDENTIFIER, new ConsCell('+', ConsType.OPERATOR, new ConsCell(root, ConsType.NUMBER))),
ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR, new ConsCell(rationalRootsFactoring(output), ConsType.CONS_CELL))));
if (!highestCoefficient.isInt() && lcm.neq(BigDec.ONE))
result = new ConsCell(BigDec.ONE, ConsType.NUMBER, new ConsCell('/', ConsType.OPERATOR, new ConsCell(lcm, ConsType.NUMBER, new ConsCell('*', ConsType.OPERATOR, result))));
return result;
}
}
return original;
}
public ConsCell polynomialDivision(ConsCell dividend, ConsCell divisor, ConsCell var, boolean flagRemainder) throws ParserException {
BigDec[] dividendCoefficients = getSyntheticCoefficients(dividend, var);
if (dividendCoefficients == null)
return new ConsCell("{false}", ConsType.OBJECT);
BigDec[] divisorCoefficients = getSyntheticCoefficients(divisor, var);
if (divisorCoefficients == null)
return new ConsCell("{false}", ConsType.OBJECT);
int resultOrder = dividendCoefficients.length - divisorCoefficients.length;
ConsCell result = new ConsCell();
for (int i = 0; i <= resultOrder; i++) {
BigDec resultCoefficient = dividendCoefficients[i].divide(divisorCoefficients[0]);
dividendCoefficients[i] = BigDec.ZERO;
for (int j = 1; j < divisorCoefficients.length; j++)
dividendCoefficients[i + j] = dividendCoefficients[i + j].subtract(divisorCoefficients[j].multiply(resultCoefficient));
result = result.append(new ConsCell('+', ConsType.OPERATOR, new ConsCell(resultCoefficient, ConsType.NUMBER, new ConsCell('*', ConsType.OPERATOR, new ConsCell(var, ConsType.CONS_CELL,
new ConsCell('^', ConsType.OPERATOR, new ConsCell(new BigDec(resultOrder - i), ConsType.NUMBER)))))));
}
ConsCell remainder = new ConsCell();
for (int i = 0; i < dividendCoefficients.length; i++)
if (dividendCoefficients[i].neq(BigDec.ZERO))
remainder = remainder.append(new ConsCell('+', ConsType.OPERATOR, new ConsCell(dividendCoefficients[i], ConsType.NUMBER, new ConsCell('*', ConsType.OPERATOR,
new ConsCell(var, ConsType.CONS_CELL, new ConsCell('^', ConsType.OPERATOR, new ConsCell(new BigDec(dividendCoefficients.length - 1 - i), ConsType.NUMBER)))))));
remainder = remainder.getFirstConsCell();
if (!remainder.isNull()) {
remainder = remainder.remove();
remainder = new ConsCell(remainder, ConsType.CONS_CELL, new ConsCell('/', ConsType.OPERATOR, new ConsCell(divisor.clone(), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL);
result = result.append(new ConsCell('+', ConsType.OPERATOR, remainder, ConsType.CONS_CELL));
}
result = result.getFirstConsCell();
if (!result.isNull())
result = simplifyTerms(result.remove());
if (flagRemainder)
result.getLastConsCell().append(new ConsCell("{" + !remainder.isNull() + "}", ConsType.OBJECT));
return result;
}
/**
* Returns a list of coefficients in descending order by variable order in each term
*
* @param equation
* the equation to check
* @param var
* the var to use
* @return null if any of the coefficients do not evaluate integers, otherwise the array.
* @throws ParserException
*/
private BigDec[] getSyntheticCoefficients(ConsCell equation, ConsCell var) throws ParserException {
ArrayList<String> vars = new ArrayList<String>();
vars.add(var.toString());
ArrayList<PairedList<ConsCell, ConsCell>> termOrders = new ArrayList<PairedList<ConsCell, ConsCell>>();
ArrayList<ConsCell> terms = foldSigns(getTerms(simplifyTerms(equation)));
BigDec equationOrder = BigDec.ZERO;
for (ConsCell term : terms)
termOrders.add(orderOfTerm(term, vars));
for (PairedList<ConsCell, ConsCell> order : termOrders) {
ConsCell temp = order.containsKey(var) ? parser.run(order.get(var)) : new ConsCell(BigDec.ZERO, ConsType.NUMBER);
if (temp.length() != 1 || temp.getCarType() != ConsType.NUMBER || ((BigDec) temp.getCar()).lt(BigDec.ZERO) || !((BigDec) temp.getCar()).isInt())
return null;
if (((BigDec) temp.getCar()).gt(equationOrder))
equationOrder = (BigDec) temp.getCar();
}
int highestOrder = equationOrder.intValue();
BigDec[] equationCoefficients = new BigDec[highestOrder + 1];
for (int i = 0; i < equationCoefficients.length; i++)
equationCoefficients[i] = BigDec.ZERO;
for (int i = 0; i < terms.size(); i++) {
ConsCell temp = termOrders.get(i).containsKey(var) ? parser.run(termOrders.get(i).get(var)) : new ConsCell(BigDec.ZERO, ConsType.NUMBER);
equationCoefficients[highestOrder - ((BigDec) temp.getCar()).intValue()] = equationCoefficients[highestOrder - ((BigDec) temp.getCar()).intValue()].add(getCoefficient(terms.get(i)));
}
return equationCoefficients;
}
/**
* Returns the prime factorization of the number that is the result of equation
*
* @param equation
* the number, possible not yet in a final form
* @return the prime factorization of this equation
* @throws ParserException
*/
public ConsCell primeFactor(ConsCell equation) throws ParserException {
ArrayList<Long> factors = new ArrayList<>();
BigDec num = (BigDec) parser.run(equation).getCar();
long[] primes = getPrimes(num.getInternal().longValue());
for (int i = 0; i < primes.length && num.gt(BigDec.ONE); i++) {
BigDec prime = new BigDec(primes[i]);
while (num.mod(prime).eq(BigDec.ZERO)) {
factors.add(primes[i]);
num = num.divide(prime);
}
}
ConsCell output = new ConsCell();
for (long integer : factors)
output = output.append(new ConsCell(",", ConsType.SEPARATOR)).append(new ConsCell(new BigDec(integer), ConsType.NUMBER));
output = output.getFirstConsCell();
if (output.getCarType() == ConsType.SEPARATOR)
output = output.remove();
return output;
}
/**
* Convenience method for {@link #findFactors(BigDec num, boolean onlyPrimes) findFactors(num, false)}
*
* @param num
* some integer
* @return an ArrayList<BigDec> of all of the factors of num
* @throws UndefinedResultException
*/
public ArrayList<BigDec> findFactors(BigDec num) throws UndefinedResultException {
return findFactors(num, false);
}
/**
* Finds the factors of some integer. It is only guaranteed to work on numbers up to 20014.
*
* @param num
* some integer
* @param onlyPrimes
* whether to return all factors or only prime factors
* @return an ArrayList<BigDec> of all of the factors of num with only one instance of each factor. Use
* {@link #primeFactor(ConsCell) primeFactor(num)} to get the primeFactorizaiton of the num
* @throws UndefinedResultException
*/
public ArrayList<BigDec> findFactors(BigDec num, boolean onlyPrimes) throws UndefinedResultException {
ArrayList<BigDec> output = new ArrayList<BigDec>();
BigDec number = BigDec.ONE;
long[] primes = getPrimes(num.getInternal().longValue());
ArrayList<BigDec> primeFactors = new ArrayList<>();
for (int i = 0; i < primes.length && num.gt(BigDec.ONE); i++) {
BigDec prime = new BigDec(primes[i]);
while (num.mod(prime).eq(BigDec.ZERO)) {
primeFactors.add(prime);
num = num.divide(prime);
}
}
boolean[] includes = new boolean[primeFactors.size()];
while (true) {
for (int i = 0; i < includes.length; i++)
if (includes[i])
number = number.multiply(primeFactors.get(i));
if (!output.contains(number))
output.add(number);
int i = 0;
includes[i] = !includes[i];
while (!includes[i] && i < includes.length - 1) {
includes[++i] = !includes[i];
}
if (i == includes.length - 1 && !includes[i])
break;
}
if (onlyPrimes) //If the onlyPrimes flag is true, remove non-prime factors from the list
for (int i = 0; i < output.size(); i++)
if (!isPrime(output.get(i)))
output.remove(i--);
Collections.sort(output);
return output;
}
/**
* NOTE: This method only works for numbers up to 20014 and is for internal use only
*
* @param num
* the number to check
* @return whether num is prime
* @throws UndefinedResultException
*/
private boolean isPrime(BigDec num) throws UndefinedResultException {
if (!num.isInt())
return false;
if (num.eq(num.ONE))
return true;
try {
if (new BigDec(num).divide(new BigDec(2)).isInt())
return false;
}
catch (UndefinedResultException e) {}
long[] primes = getPrimes(num.getInternal().longValue());
for (int i = 0; i < primes.length && primes[i] <= num.intValue(); i++)
if (primes[i] == num.intValue())
return true;
return false;
}
private ArrayList<BigDec> substitutionSolve(ConsCell equation) throws ParserException {
ArrayList<String> initialVars = new ArrayList<String>(parser.getVars());
ArrayList<BigDec> tempSolutions = new ArrayList<BigDec>(), solutions = new ArrayList<BigDec>();
ConsCell parts[] = seekSubstitutions(equation.clone());
if (parser.getVariables(parts[0]).size() > 1)
return solutions;
tempSolutions = solve(parts[0], false, false);
if (parts[1].isNull()) {
for (int i = 0; i < tempSolutions.size(); i++)
if (!isAnswer(equation, tempSolutions.get(i), BigDec.ZERO, accuracy)) {
tempSolutions.remove(i);
i--;
}
return tempSolutions;
}
else {
parser.setVars(initialVars);
for (BigDec d : tempSolutions) {
try {
ConsCell deSubEqn = reverseSubstitution(parts[1], d);
initialInput.push(deSubEqn);
for (BigDec solutin : solve(deSubEqn, true, false))
solutions.add(solutin);
}
finally {
initialInput.pop();
}
}
}
return checkSolutions(solutions, equation, accuracy);
}
private ConsCell reverseSubstitution(ConsCell substitution, BigDec z) throws ParserException {
int subID = subID(substitution);
ConsCell deSubEqn = parser.run(reverseSubstitutions[subID].replaceAll("z", z.toString()));
return removeExcessParentheses(subID == substitutions.length - 1 ? substitution.clone() : substitution.getNextConsCell().clone()).getLastConsCell().append(
parser.removeDoubles(new ConsCell('+', ConsType.OPERATOR, invertSign(deSubEqn)))).getFirstConsCell();
}
private int subID(ConsCell substitution) {
if (substitution.getCarType() == ConsType.CONS_CELL && substitution.length() == 1)
return 20;
String sub = substitution.getCar().toString();
for (int i = 0; i < substitutions.length; i++)
if (sub.equalsIgnoreCase(substitutions[i]))
return i;
return -1;
}
private ConsCell[] seekSubstitutions(ConsCell equation) throws ParserException {
return seekSubstitutions(equation, new ConsCell());
}
public ConsCell[] seekSubstitutions(ConsCell equation, ConsCell substitution) throws ParserException {
return seekSubstitutions(equation, substitution, new ConsCell("y", ConsType.IDENTIFIER));
}
private ConsCell[] seekSubstitutions(ConsCell equation, ConsCell substitution, ConsCell u) throws ParserException {
int subID = -1;
ConsCell current = equation;
if (!substitution.isNull())
subID = subID(substitution);
do {
ConsCell previous = current.getPreviousConsCell();
if (substitution.isNull()) {
if (parser.containsVariables(current.singular()) && current.getCarType() == ConsType.CONS_CELL &&
(current.isFirstConsCell() || !isSubstitution(previous.singular().toString()))) { //If the thing before this isn't a substitution operation
substitution = ((ConsCell) current.singular().getCar()).clone();
if (!isOneTerm(substitution))
substitution = new ConsCell(substitution, ConsType.CONS_CELL);
subID = subID(substitution);
}
else if (parser.containsVariables(current.getNextConsCell().singular()) && (subID = subID(current)) >= 0)
substitution = new ConsCell(current.getCar(), current.getCarType(), current.getNextConsCell().singular());
}
if (!substitution.isNull()) {
if (subID == substitutions.length - 1 && current.singular().equals(substitution))
current.replaceCar(u);
else if (subID != 20 && current.singular().append(current.getNextConsCell().singular()).getFirstConsCell().equals(substitution)) {
current = current.remove();
if (current.isFirstConsCell())
equation = current;
current.replaceCar(u);
}
else if (current.getCarType() == ConsType.CONS_CELL)
current.replaceCar(seekSubstitutions((ConsCell) current.getCar(), substitution, u)[0]);
}
} while (!(current = current.getNextConsCell()).isNull());
if (!substitution.isNull())
parser.setVars((String) u.getCar());
return new ConsCell[]{equation, substitution};
}
private boolean isSubstitution(String operation) {
boolean isSub = false;
for (String str : substitutions)
if (str.equalsIgnoreCase(operation))
isSub = true;
return isSub;
}
/**
* Solves a fully factored homogeneous equation
*
* @param equation
* the equation to be solved
* @return the solutions to the equation if there are any via this method
*/
private ArrayList<BigDec> oneTermSolve(ConsCell equation) throws ParserException {
if (!isOneTerm(equation))
return new ArrayList<BigDec>();
ArrayList<ConsCell> segments = getTermSegments(equation);
ArrayList<BigDec> solutions = new ArrayList<BigDec>();
for (ConsCell segment : segments) {
if (segment.getCarType() == ConsType.OPERATOR || !parser.containsVariables(segment))
continue;
solutions.addAll(solve(segment.getCarType() == ConsType.CONS_CELL ? (ConsCell) segment.getCar() : segment, true, false));
}
return checkSolutions(solutions, equation, accuracy);
}
private BigDec getY(ConsCell equation, BigDec x) throws ParserException {
equation.replaceAll(new ConsCell(parser.getVars().get(0), ConsType.IDENTIFIER), new ConsCell(x, ConsType.NUMBER));
ConsCell result = parser.run(equation);
if (result.getCarType() != ConsType.NUMBER)
throw new ParserException("Invalid equation", null);
return (BigDec) result.getCar();
}
private boolean isAnswer(ConsCell equation, BigDec point, BigDec answer, BigDec accuracy) throws ParserException {
equation = equation.clone();
if (parser.getVars().size() > 1)
return false;
try {
if (getY(equation, point).subtract(answer).abs().lteq(accuracy))
return true;
}
catch (UndefinedResultException e) {}
return false;
}
private void setDomain(BigDec dMin, BigDec dMax) throws UndefinedResultException {
if (dMin.gt(dMax)) {
BigDec temp = new BigDec(dMax);
dMax = new BigDec(dMin);
dMin = temp;
}
xMin = (dMax.add(dMin)).divide(new BigDec(2));
xMax = dMax.subtract(xMin);
domainMax = dMax;
domainMin = dMin;
method = "domain";
}
public BigDec getDomain()[] throws UndefinedResultException {
return new BigDec[]{xMin.subtract(xMax), xMin.add(xMax)};
}
/**
* Splits the equation in <tt>equation</tt> into terms.
* <p>
* This leaves the + and - signs in the ArrayList, not folded into each term. Use foldSigns on the output of this method
* to fold the signs into their respective terms:<br>
* <code>NumericalSolver.foldSigns(ns.getTerms(equation))</code>
* </p>
*
* @param equation
* the equation to process
* @return an ArrayList<ConsCell> containing the terms of the equation and the signs between them.
* @throws ParserException
*/
public ArrayList<ConsCell> getTerms(ConsCell equation) {
ArrayList<ConsCell> output = new ArrayList<ConsCell>();
ConsCell current = equation;
do {
if (current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '+' || (Character) current.getCar() == '-')) {
output.add(current.singular());
continue;
}
ConsCell term = new ConsCell();
boolean skipSign = false;
do {
skipSign = current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '*' || (Character) current.getCar() == '/' || (Character) current.getCar() == '^');
term = term.append(current.singular());
} while (!((current = current.getNextConsCell()).isNull()) && (skipSign || !(current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '+' || (Character) current.getCar() == '-'))));
output.add(term.getFirstConsCell());
if (!current.isNull())
output.add(current.singular());
} while (!((current = current.getNextConsCell()).isNull())); //This steps current forward while checking for nulls
return output;
}
public ConsCell condenseCoefficient(ConsCell term, ArrayList<String> vars) throws ParserException {
PairedList<ConsCell, ConsCell> orders = orderOfTerm(term, vars);
ArrayList<ConsCell> segments = getTermSegments(term);
ConsCell numerator = new ConsCell(), denominator = new ConsCell();
if (!parser.containsVariables(segments.get(0), vars))
numerator = numerator.append(new ConsCell('*', ConsType.OPERATOR, segments.get(0)));
for (int i = 2; i < segments.size(); i += 2) {
if (!parser.containsVariables(segments.get(i), vars) && segments.get(i - 1).getCarType() == ConsType.OPERATOR) {
//TODO make this recursive. Maybe use distributive property?
if ((Character) segments.get(i - 1).getCar() == '*')
numerator = numerator.append(new ConsCell('*', ConsType.OPERATOR, segments.get(i)));
if ((Character) segments.get(i - 1).getCar() == '/')
denominator = denominator.append(new ConsCell('*', ConsType.OPERATOR, segments.get(i)));
}
}
numerator = numerator.getFirstConsCell();
denominator = denominator.getFirstConsCell();
BigDec num = new BigDec(parser.run(numerator.length() > 0 ? numerator.remove() : new ConsCell(BigDec.ONE, ConsType.NUMBER)).toString());
BigDec den = new BigDec(parser.run(denominator.length() > 0 ? denominator.remove() : new ConsCell(BigDec.ONE, ConsType.NUMBER)).toString()), gcd = num.gcd(den);
num = num.divide(gcd);
den = den.divide(gcd);
ConsCell output = new ConsCell(num, ConsType.NUMBER, new ConsCell('/', ConsType.OPERATOR, new ConsCell(den, ConsType.NUMBER))).getLastConsCell();
for (ConsCell key : orders) {
ConsCell exp = orders.get(key);
if (exp.length() == 1 && exp.getCarType() == ConsType.NUMBER) { //If the exponent is a real number
if (((BigDec) exp.getCar()).eq(BigDec.ZERO))
continue;
output = output.append(new ConsCell('*', ConsType.OPERATOR)).append(key.clone());
if (((BigDec) exp.getCar()).eq(BigDec.ONE))
continue;
output = output.append(new ConsCell('^', ConsType.OPERATOR)).append(exp.clone());
continue;
}
//For anything else, stick the exponent in parentheses and append it
output = output.append(new ConsCell('*', ConsType.OPERATOR)).append(key.clone()).append(new ConsCell('^', ConsType.OPERATOR)).append(new ConsCell(exp.clone(), ConsType.CONS_CELL));
}
output = output.getFirstConsCell();
return output;
}
/**
* Gets the coefficient of the specified term<br>
* This should only be used after the values for the keywords have been inserted into the term.<br>
* This uses the variables loaded into the parser at the time of the call. Use
* {@link #getCoefficient(ConsCell, ArrayList)} to specify the variables.
*
* @param term
* the term to get the coefficient of
* @return the coefficient of the term
* @throws ParserException
*/
public BigDec getCoefficient(ConsCell term) throws ParserException {
return getCoefficient(term, parser.getVars());
}
/**
* Gets the coefficient of the specified term<br>
* This should only be used after the values for the keywords have been inserted into the term.<br>
* This uses the variables loaded into the parser at the time of the call.
*
* @param term
* the term to get the coefficient of
* @param vars
* the variables to use
* @return the coefficient of the term
* @throws ParserException
*/
public BigDec getCoefficient(ConsCell term, ArrayList<String> vars) throws ParserException {
if (term.length() == 1) {
if (term.getCarType() == ConsType.NUMBER)
return (BigDec) term.getCar();
if (term.getCarType() == ConsType.CONS_CELL && !parser.containsVariables(term, vars))
return (BigDec) parser.run((ConsCell) term.getCar()).getCar();
if (term.getCarType() == ConsType.OPERATOR && (Character) term.getCar() == '-')
return BigDec.MINUSONE;
return BigDec.ONE;
}
BigDec coefficient = BigDec.ONE;
ConsCell current = term;
boolean negative = false;
do {
if (current.getCarType() == ConsType.OPERATOR && (Character) current.getCar() == '-')
negative = !negative;
if (!(current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '*' || (Character) current.getCar() == '/'))) {
boolean divide = current.getPreviousConsCell().getCarType() == ConsType.OPERATOR && (Character) current.getPreviousConsCell().getCar() == '/';
ConsCell eqn = current.singular();
while (!(current = current.getNextConsCell()).isNull() && !(current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '*' || (Character) current.getCar() == '/')))
eqn = eqn.append(current.singular());
eqn = eqn.getFirstConsCell();
if (parser.containsVariables(eqn, vars))
continue;
eqn = parser.run(eqn);
if (eqn.length() == 1 && eqn.getCarType() == ConsType.NUMBER)
coefficient = divide ? coefficient.divide((BigDec) eqn.getCar()) : coefficient.multiply((BigDec) eqn.getCar());
}
} while (!(current = current.getNextConsCell()).isNull());
return negative ? coefficient.multiply(BigDec.MINUSONE) : coefficient;
}
/**
* Gets the segments within a term, where the segments are separated by * or / signs.
*
* @param term
* the term to get the segments of
* @return the segments of the term and the signs between them, in order, in an ArrrayList
*/
public ArrayList<ConsCell> getTermSegments(ConsCell term) {
ArrayList<ConsCell> output = new ArrayList<ConsCell>();
ConsCell segment = new ConsCell();
do {
if (term.getCarType() == ConsType.OPERATOR && ((Character) term.getCar() == '*' || (Character) term.getCar() == '/')) {
output.add(segment.getFirstConsCell());
output.add(term.singular());
segment = new ConsCell();
}
else
segment = segment.append(term.singular());
} while (!((term = term.getNextConsCell()).isNull())); //This steps current forward while checking for nulls
if (!segment.isNull() && segment.length() > 0)
output.add(segment);
return output;
}
/**
* @param term
* the term to check
* @param variables
* the variables that this term can contain
* @return each variable-containing segment of the term paired with its exponent. If any segment is repeated, the
* exponent is updated appropriately, so x*x -> x, 2. Additionally, segments of the zeroth order are removed.
* @throws ParserException
*/
public PairedList<ConsCell, ConsCell> orderOfTerm(ConsCell term, ArrayList<String> variables) throws ParserException {
PairedList<ConsCell, ConsCell> orders = new PairedList<ConsCell, ConsCell>();
if (term.length() < 1)
return orders;
ConsCell current = term;
do {
if (current.getCarType() != ConsType.OPERATOR && !(current.getNextConsCell().getCarType() == ConsType.OPERATOR && (Character) current.getNextConsCell().getCar() == '^')) {
ConsCell eqn = current.singular();
boolean subtract = current.getPreviousConsCell().getCarType() == ConsType.OPERATOR && (Character) current.getPreviousConsCell().getCar() == '/';
while (!(current = current.getNextConsCell()).isNull() && !(current.getCarType() == ConsType.OPERATOR && ((Character) current.getCar() == '*' || (Character) current.getCar() == '/'))) {
if (current.getCarType() == ConsType.OPERATOR) {
if ((Character) current.getCar() == '^')
break;
if ((Character) current.getCar() == '-')
continue;
}
eqn = eqn.append(current.singular());
}
if (current.getCarType() == ConsType.OPERATOR && (Character) current.getCar() == '^') {
current = current.getPreviousConsCell();
continue;
}
eqn = eqn.getFirstConsCell();
if (!orders.containsKey(eqn))
orders.put(eqn, new ConsCell());
orders.put(eqn, orders.get(eqn).getLastConsCell().append(new ConsCell(subtract ? '-' : '+', ConsType.OPERATOR)).append(new ConsCell(BigDec.ONE, ConsType.NUMBER)).getFirstConsCell());
}
if (current.getCarType() == ConsType.OPERATOR && (Character) current.getCar() == '^') {
ConsCell head = current.getPreviousConsCell();
ConsCell left = new ConsCell();
do {
if (!(head.getCarType() == ConsType.OPERATOR && (Character) head.getCar() == '-'))
left = head.singular().append(left.getFirstConsCell());
} while (!(head = head.getPreviousConsCell()).isNull() && !(head.getCarType() == ConsType.OPERATOR && ((Character) head.getCar() == '*' || (Character) head.getCar() == '/')));
left = left.getFirstConsCell();
boolean subtract = left.getCarType() == ConsType.OPERATOR && (Character) left.getCar() == '/';
if (subtract)
left = left.remove();
if (!orders.containsKey(left))
orders.put(left, new ConsCell());
head = current.getNextConsCell();
ConsCell exponent = head.singular();
while (!(head = head.getNextConsCell()).isNull() && !(head.getCarType() == ConsType.OPERATOR && ((Character) head.getCar() == '*' || (Character) head.getCar() == '/')))
exponent = exponent.append(head.singular());
exponent = exponent.getFirstConsCell();
orders.put(left, orders.get(left).getLastConsCell().append(new ConsCell(subtract ? '-' : '+', ConsType.OPERATOR)).append(exponent).getFirstConsCell());
current = head;
}
} while (!(current = current.getNextConsCell()).isNull());
for (int i = 0; i < orders.size(); i++) {
ConsCell key = orders.getKeys().get(i);
if (!parser.containsVariables(key, variables) && !parser.containsVariables(orders.get(key), variables)) {
orders.remove(key);
i--;
continue;
}
if (!parser.containsVariables(orders.get(key), variables))
orders.put(key, parser.run(orders.get(key)));
if (orders.get(key).getCarType() == ConsType.OPERATOR && (Character) orders.get(key).getCar() == '+')
orders.put(key, orders.get(key).remove());
if (orders.get(key).length() == 1 && orders.get(key).getCarType() == ConsType.NUMBER && ((BigDec) orders.get(key).getCar()).eq(BigDec.ZERO)) {
orders.remove(key);
i--;
}
}
return orders;
}
public BigDec orderOfEquation(ConsCell input, ArrayList<String> vars) throws ParserException {
PairedList<ConsCell, PairedList<ConsCell, ConsCell>> orders = new PairedList<ConsCell, PairedList<ConsCell, ConsCell>>();
for (ConsCell term : getTerms(input))
orders.put(term, orderOfTerm(term, vars));
return orderOfEquation(orders, vars);
}
public BigDec orderOfEquation(PairedList<ConsCell, PairedList<ConsCell, ConsCell>> orders, ArrayList<String> vars) throws ParserException {
BigDec highest = null;
ConsCell var = new ConsCell(vars.get(0), ConsType.IDENTIFIER);
for (PairedList<ConsCell, ConsCell> value : orders.getValues()) {
BigDec order = BigDec.ZERO;
if (value.containsKey(var))
order = new BigDec(parser.run(value.get(var)).toString());
if (highest == null || order.gt(highest))
highest = order;
}
return highest;
}
public boolean equalOrders(PairedList<ConsCell, ConsCell> order1, PairedList<ConsCell, ConsCell> order2) {
if (order1.size() != order2.size())
return false;
for (ConsCell order : order1)
if (!order2.containsKey(order) || !order2.get(order).equals(order1.get(order)))
return false;
return true;
}
/**
* Compresses the exponents and constant coefficients within the specified term
*
* @param term
* the term to simplify
* @return the simplified term
* @throws ParserException
*/
public ConsCell simplifyTerm(ConsCell term) throws ParserException {
if (!parser.containsVariables(term, parser.getVars()))
return parser.run(term);
BigDec coefficient = getCoefficient(term);
PairedList<ConsCell, ConsCell> orders = orderOfTerm(term, parser.getVars());
return makeTerm(coefficient, orders);
}
/**
* Simplifies the equation. This includes combining multiple instances of the same function variable, etc. into powers,
* and adding the coefficients of like terms. It applies these rules recursively.
*
* @param input
* the equation to simplify
* @return the equation in a simplified form.
* @throws ParserException
*/
public ConsCell simplifyTerms(ConsCell input) throws ParserException {
ConsCell output = new ConsCell();
input = removeExcessParentheses(input);
ConsCell current = input;
do { //Recursion
if (current.getCarType() == ConsType.CONS_CELL)
current.replaceCar(simplifyTerms((ConsCell) current.getCar()));
} while (!((current = current.getNextConsCell()).isNull()));
ArrayList<ConsCell> terms = foldSigns(getTerms(input));
ArrayList<PairedList<ConsCell, ConsCell>> orders = new ArrayList<PairedList<ConsCell, ConsCell>>();
ArrayList<String> vars = parser.getVars();
for (ConsCell term : terms)
orders.add(orderOfTerm(term, vars));
while (orders.size() > 0) {
ConsCell term = terms.remove(0);
if (term.getCarType() == ConsType.OPERATOR && term.length() == 1) {
output = output.append(term);
orders.remove(0);
continue;
}
PairedList<ConsCell, ConsCell> order = orders.remove(0);
BigDec coefficient = getCoefficient(term);
for (int i = 0; i < terms.size(); i++) {
if (equalOrders(order, orders.get(i))) {
coefficient = coefficient.add(getCoefficient(terms.remove(i)));
orders.remove(i--);
}
}
if (coefficient.eq(BigDec.ZERO))
continue;
ConsCell temp = makeTerm(coefficient, order, false);
output = output.append(temp.getCarType() == ConsType.OPERATOR ? temp : new ConsCell('+', ConsType.OPERATOR, temp, ConsType.CONS_CELL));
}
output = output.getFirstConsCell();
if (output.getCarType() == ConsType.OPERATOR && (Character) output.getCar() == '+')
output = output.remove();
orders = new ArrayList<PairedList<ConsCell, ConsCell>>();
terms = getTerms(output);
for (ConsCell term : terms)
orders.add(orderOfTerm(term, vars));
ArrayList<ConsCell> sortedTerms = sortTerms(terms, orders);
output = new ConsCell();
for (ConsCell term : sortedTerms)
output = output.append(term).getLastConsCell();
output = output.getFirstConsCell();
if (output.getCarType() == ConsType.OPERATOR && (Character) output.getCar() == '+')
output = output.remove();
return output;
}
public ConsCell makeTerm(BigDec coefficient, PairedList<ConsCell, ConsCell> orders) throws UndefinedResultException {
return makeTerm(coefficient, orders, true);
}
public ConsCell makeTerm(BigDec coefficient, PairedList<ConsCell, ConsCell> orders, boolean compressNegative) throws UndefinedResultException {
boolean negate = coefficient.lt(BigDec.ZERO);
if (negate && !compressNegative)
coefficient = coefficient.multiply(BigDec.MINUSONE);
ConsCell output = new ConsCell(coefficient, ConsType.NUMBER);
ConsCell result = output;
for (ConsCell key : orders) {
ConsCell exp = orders.get(key);
if (exp.length() == 1 && exp.getCarType() == ConsType.NUMBER) { //If the exponent is a real number
if (((BigDec) exp.getCar()).eq(BigDec.ZERO))
continue;
output = output.append(new ConsCell('*', ConsType.OPERATOR)).append(key.clone());
if (((BigDec) exp.getCar()).eq(BigDec.ONE))
continue;
output = output.append(new ConsCell('^', ConsType.OPERATOR)).append(exp.clone());
continue;
}
//For anything else, stick the exponent in parentheses and append it
output = output.append(new ConsCell('*', ConsType.OPERATOR)).append(key.clone()).append(new ConsCell('^', ConsType.OPERATOR)).append(new ConsCell(exp.clone(), ConsType.CONS_CELL));
}
if (coefficient.eq(BigDec.ONE) && result.length() >= 3 && result.getCarType(1) == ConsType.OPERATOR && (Character) result.getCar(1) == '*')
result = result.remove().remove();
return (negate && !compressNegative) ? new ConsCell('-', ConsType.OPERATOR, result, ConsType.CONS_CELL) : result;
}
public ConsCell distributive(ConsCell input) throws ParserException {
input = parser.removeDoubles(input);
return simplifyTerms(distributiveRecursion(distributeExponents(input)));
}
private ConsCell distributiveRecursion(ConsCell input) throws ParserException {
ConsCell current = input, result = input;
do {
if (current.getCarType() == ConsType.CONS_CELL)
current.replaceCar(distributiveRecursion((ConsCell) current.getCar()));
} while (!(current = current.getNextConsCell()).isNull());
current = input;
do {
if (current.getCarType() != ConsType.OPERATOR || (Character) current.getCar() == '+' || (Character) current.getCar() == '^' || (Character) current.getCar() == '%')
continue;
ConsCell next = new ConsCell(), previous = new ConsCell();
ConsCell head = current.getNextConsCell();
do {
next = next.append(head.singular());
if ((head = head.remove()).getCarType() == ConsType.OPERATOR && (Character) head.getCar() == '^')
next = next.append(head.singular());
else
break;
} while (!(head = head.remove()).isNull());
next = next.getFirstConsCell();
if ((Character) current.getCar() != '-') {
head = current.getPreviousConsCell();
do {
previous = previous.isNull() ? head.singular() : new ConsCell(head.getCar(), head.getCarType(), previous, ConsType.CONS_CELL);
if ((head = head.remove().getPreviousConsCell()).getCarType() == ConsType.OPERATOR && (Character) head.getCar() == '^')
previous = new ConsCell('^', ConsType.OPERATOR, previous, ConsType.CONS_CELL);
else
break;
} while (!(head = head.remove().getPreviousConsCell()).isNull());
result = current;
}
switch ((Character) current.getCar()) {
case '-': {
//Invert all signs in the next item
ConsCell insert;
if (!parser.containsVariables(next))
insert = parser.run(new ConsCell(BigDec.MINUSONE, ConsType.NUMBER, new ConsCell('*', ConsType.OPERATOR, new ConsCell(next, ConsType.CONS_CELL))));
else if (next.getCarType() == ConsType.CONS_CELL)
insert = new ConsCell(distributiveRecursion(invertSign((ConsCell) next.getCar())), ConsType.CONS_CELL);
else {
current.insert(next);
continue;
}
current.replaceCar(new ConsCell('+', ConsType.OPERATOR));
ConsCell end = insert.getLastConsCell();
current.insert(insert);
if (current.getPreviousConsCell().isNull())
current = current.remove();
result = current = end;
continue;
}
case '*': {
ArrayList<ConsCell> left = new ArrayList<>(), right = new ArrayList<>();
if (previous.length() != 1)
left.add(previous);
else if (previous.getCarType() == ConsType.CONS_CELL)
left = foldSigns(getTerms((ConsCell) previous.getCar()));
else if (previous.getCarType() == ConsType.OPERATOR)
if ((Character) previous.getCar() == '-')
left.add(new ConsCell(BigDec.MINUSONE, ConsType.NUMBER));
else
left.add(new ConsCell(BigDec.ONE, ConsType.NUMBER));
else
left.add(previous);
if (next.length() != 1)
right.add(next);
else if (next.getCarType() == ConsType.CONS_CELL)
right = foldSigns(getTerms((ConsCell) next.getCar()));
else if (next.getCarType() == ConsType.OPERATOR)
if ((Character) next.getCar() == '-')
right.add(new ConsCell(BigDec.MINUSONE, ConsType.NUMBER));
else
right.add(new ConsCell(BigDec.ONE, ConsType.NUMBER));
else
right.add(next);
ConsCell insert = new ConsCell();
for (ConsCell lTerm : left)
for (ConsCell rTerm : right)
insert = insert.append(lTerm.clone()).append(new ConsCell('*', ConsType.OPERATOR)).append(rTerm.clone()).append(new ConsCell('+', ConsType.OPERATOR));
if (!insert.isNull()) {
insert.remove();
insert = insert.getFirstConsCell();
}
ConsCell end = insert.getLastConsCell();
if (isOneTerm(insert) && insert.length() != 1) {
current.replace(insert);
result = current = end;
}
else
current.replaceCar(insert);
continue;
}
case '/': {
ConsCell insert = previous.length() == 1 && next.length() == 1 ? polynomialDivision(previous, next, new ConsCell(parser.getVars().get(0), ConsType.IDENTIFIER), true) :
new ConsCell("{true}", ConsType.OBJECT);
if (((String) insert.getLastConsCell().getCar()).equals("{false}")) {
insert.getLastConsCell().remove();
current.replaceCar(insert);
}
else
current.replaceCar(new ConsCell(previous.getCar(), previous.getCarType(), new ConsCell('/', ConsType.OPERATOR, new ConsCell(next, ConsType.CONS_CELL))));
continue;
}
}
} while (!(current = current.getNextConsCell()).isNull());
return result.getFirstConsCell();
}
private ConsCell distributeExponents(ConsCell input) throws ParserException {
ConsCell current = input, result = input;
do {
if (current.getCarType() == ConsType.CONS_CELL)
current.replaceCar(distributeExponents((ConsCell) current.getCar()));
} while (!(current = current.getNextConsCell()).isNull());
current = input;
while (!(current = current.getNextConsCell()).isNull()) {
if (current.getCarType() == ConsType.OPERATOR && (Character) current.getCar() == '^') {
ConsCell insert = new ConsCell();
ConsCell base = current.getPreviousConsCell().singular();
if (base.getCarType() != ConsType.CONS_CELL)
continue;
ConsCell power = new ConsCell();
ConsCell head = current.getNextConsCell();
if (head.getCarType() == ConsType.OPERATOR && (Character) head.getCar() == '-') {
power = power.append(head.singular());
head.remove();
}
boolean skipCheck = false;
while (!(head = current.getNextConsCell()).isNull() && (skipCheck || !(head.getCarType() == ConsType.OPERATOR && (Character) head.getCar() != '^'))) {
power = power.append(head.singular());
head.remove();
skipCheck = (power.getCarType() == ConsType.OPERATOR || (power.getCarType() == ConsType.IDENTIFIER && !parser.containsVariables(power)));
}
power = parser.run(removeExcessParentheses(power.getFirstConsCell()));
if (parser.containsVariables(power)) {
ArrayList<ConsCell> terms = getTerms(power);
ArrayList<ConsCell> varTerms = new ArrayList<ConsCell>();
int i = 1;
if (terms.get(0).getCarType() != ConsType.OPERATOR && parser.containsVariables(terms.get(0)))
varTerms.add(terms.remove(0));
else
i = 2;
for (; i < terms.size(); i += 2) {
if (parser.containsVariables(terms.get(i))) {
varTerms.add(terms.remove(--i));
varTerms.add(terms.remove(i));
i--;
}
}
power = new ConsCell();
for (ConsCell term : varTerms)
power = power.append(term);
power = power.getFirstConsCell();
if (power.getCarType() == ConsType.OPERATOR && (Character) power.getCar() == '+')
power = power.remove();
insert = insert.append(new ConsCell('*', ConsType.OPERATOR)).append(base.clone()).append(new ConsCell('^', ConsType.OPERATOR)).append(new ConsCell(power, ConsType.CONS_CELL));
power = new ConsCell();
for (ConsCell term : terms)
power = power.append(term).getLastConsCell();
power = power.getFirstConsCell();
if (power.getCarType() == ConsType.OPERATOR && (Character) power.getCar() == '+')
power = power.remove();
}
//if (power.getCarType() != ConsType.NUMBER)
//throw new ParserException("Unsupported exponential format in the equation solver", null);
BigDec pow = new BigDec(power.toString());
int exp = pow.intValue();
pow = pow.subtract(new BigDec(exp));
boolean negative = exp < 0;
for (int i = 0; i < exp; i++)
insert = insert.append(new ConsCell(negative ? '/' : '*', ConsType.OPERATOR)).append(base.clone());
if (!pow.eq(BigDec.ZERO))
insert = insert.append(new ConsCell(negative ? '/' : '*', ConsType.OPERATOR)).append(base.clone()).append(new ConsCell('^', ConsType.OPERATOR)).append(new ConsCell(pow, ConsType.NUMBER));
insert = insert.getFirstConsCell();
if (current.getPreviousConsCell() == input) {
input = current;
if (negative)
insert = new ConsCell(BigDec.ONE, ConsType.NUMBER).append(insert).getFirstConsCell();
else
insert = insert.remove();
}
if (insert.getFirstConsCell().getCarType() == ConsType.OPERATOR && (Character) insert.getCar() == '*')
insert = insert.remove();
current.getPreviousConsCell().remove();
current.insert(insert);
ConsCell temp = current;
current = head.isNull() ? insert.getLastConsCell() : head;
temp.remove();
result = current;
}
}
return result.getFirstConsCell();
}
/**
* Folds the signs +/- in an equation ArrayList into the terms.</br> NOTE: this may leave - signs in the equation if the
* are found before segments that neither numeric nor +/- operators.
*
* @param terms
* the equation ArrayList
* @return the equation ArrayList without any separate +/- signs except for - signs found before segments that neither
* numeric nor +/- operators
* @throws ParserException
*/
public ArrayList<ConsCell> foldSigns(ArrayList<ConsCell> terms) throws ParserException {
for (int i = 0; i < terms.size() - 1; i++) {
if (terms.get(i).getCarType() != ConsType.OPERATOR || terms.get(i).length() > 1)
continue;
if ((Character) terms.get(i).getCar() == '+')
terms.remove(i--);
else if ((Character) terms.get(i).getCar() == '-') {
if (terms.get(i + 1).getCarType() == ConsType.OPERATOR && terms.get(i + 1).length() == 1) {
if ((Character) terms.get(i + 1).getCar() == '+') {
terms.get(i + 1).replaceCar(new ConsCell('-', ConsType.OPERATOR));
terms.remove(i--);
}
else if ((Character) terms.get(i + 1).getCar() == '-') {
terms.get(i + 1).replaceCar(new ConsCell('+', ConsType.OPERATOR));
terms.remove(i--);
}
}
else if (terms.get(i + 1).getCarType() == ConsType.NUMBER) {
terms.get(i + 1).replaceCar(new ConsCell(((BigDec) terms.get(i + 1).getCar()).multiply(BigDec.MINUSONE), ConsType.NUMBER));
terms.remove(i--);
}
else {
terms.set(i + 1, new ConsCell('-', ConsType.OPERATOR, terms.get(i + 1)));
terms.remove(i--);
}
}
}
return terms;
}
/**
* Removes excess parentheses from an equation, effectively, this is done by modifying the depth of entries in the
* <tt>ConsCell</tt>
*
* @param input
* the equation to process as a <tt>ConsCell</tt>
* @return the equation with all excess parentheses removed
*/
public ConsCell removeExcessParentheses(ConsCell input) {
while (input.length() == 1 && input.getCarType() == ConsType.CONS_CELL)
input = (ConsCell) input.getCar();
ConsCell current = input;
do {
if (current.getCarType() != ConsType.CONS_CELL)
continue;
//We can drop the parentheses if the interior has a length of 1 after the recursive call
current.replaceCar(removeExcessParentheses((ConsCell) current.getCar()));
if (current.getCarType() != ConsType.CONS_CELL)
continue;
/* We can also drop the parentheses in the following cases:
* Left: + on any f(x), -,* on one term, ^,/,% on length == 1, not operator on length == 1
* Right: +,- on any f(x), *,/ on one term, ^,% on length == 1, not operator on length == 1
*/
Object left = current.getPreviousConsCell().getCar(), right = current.getNextConsCell().getCar();
boolean isOneTerm = isOneTerm((ConsCell) current.getCar());
int length = ((ConsCell) current.getCar()).length();
if (!(left == null || !(left instanceof Character) && length == 1 || left instanceof Character && ((Character) left == '+' || isOneTerm && ((Character) left == '-' || (Character) left == '*') ||
length == 1 && ((Character) left == '^' || (Character) left == '/' || (Character) left == '%'))))
continue;
if (!(right == null || !(right instanceof Character) && length == 1 || right instanceof Character && ((Character) right == '+' || (Character) right == '-' || isOneTerm && ((Character) right == '*' ||
(Character) right == '/') || length == 1 && ((Character) right == '^' || (Character) right == '%'))))
continue;
current = current.replace((ConsCell) current.getCar());
} while (!((current = current.getNextConsCell()).isNull())); //This steps current forward while checking for nulls
return input;
}
/**
* Determines if the <tt>ConsCell</tt> represents a single term.
*
* @param input
* the <tt>ConsCell</tt> to check
* @return false if any item in the <tt>input</tt> other than the first one is a + or - sign, otherwise true.
*/
public boolean isOneTerm(ConsCell input) {
while (!((input = input.getNextConsCell()).isNull()))
if (input.getCarType() == ConsType.OPERATOR && ((Character) input.getCar() == '+' || (Character) input.getCar() == '-'))
return false;
return true;
}
public ArrayList<ConsCell> sortTerms(ArrayList<ConsCell> terms, ArrayList<PairedList<ConsCell, ConsCell>> orders) {
if (terms.size() < 1)
return terms;
if (terms.get(0).getCarType() != ConsType.OPERATOR) {
terms.add(0, new ConsCell('+', ConsType.OPERATOR));
orders.add(0, new PairedList<ConsCell, ConsCell>());
}
for (int i = 1; i < terms.size(); i += 2) {
if (terms.get(i).getCarType() == ConsType.OPERATOR)
continue;
for (int j = i + 2; j < terms.size(); j += 2) {
if (terms.get(j).getCarType() == ConsType.OPERATOR)
continue;
if (orders.get(i).compareTo(orders.get(j)) < 0) {
ConsCell temp = terms.get(i), tempSign = terms.get(i - 1);
PairedList<ConsCell, ConsCell> tempOrder = orders.get(i), tempOrderSign = orders.get(i - 1);
terms.set(i, terms.get(j));
terms.set(j, temp);
terms.set(i - 1, terms.get(j - 1));
terms.set(j - 1, tempSign);
orders.set(i, orders.get(j));
orders.set(j, tempOrder);
orders.set(i - 1, orders.get(j - 1));
orders.set(j - 1, tempOrderSign);
}
}
}
if (terms.get(0).getCarType() == ConsType.OPERATOR && (Character) terms.get(0).getCar() == '+')
terms.remove(0);
return terms;
}
public ConsCell invertSign(ConsCell input) {
if (input.getCarType() != ConsType.OPERATOR)
input = new ConsCell('-', ConsType.OPERATOR).append(input).getFirstConsCell();
else if ((Character) input.getCar() == '-')
input = input.remove();
else
input.replaceCar(new ConsCell('-', ConsType.OPERATOR));
ConsCell current = input;
while (!(current = current.getNextConsCell()).isNull()) {
if (current.getCarType() == ConsType.OPERATOR) {
if ((Character) input.getCar() == '^') {
current = current.getNextConsCell();
if (current.isNull())
break;
continue;
}
if ((Character) current.getCar() == '-')
current.replaceCar(new ConsCell('+', ConsType.OPERATOR));
else if ((Character) current.getCar() == '+')
current.replaceCar(new ConsCell('-', ConsType.OPERATOR));
}
}
return input.getFirstConsCell();
}
/**
* If the upper bound of primes requested is greater than the last upper bound, this method generates all the primes in
* the range [2, n], and then caches the result. Otherwise, it just returns the cached values. This <i>can</i> result in
* primes larger than n being returned.
*
* @param n
* the upper bound for the list of primes
* @return A <tt>long</tt> array of all the primes in the range [2, n]
*/
public static long[] getPrimes(long n) {
try {
primesLock.lock();
if (n > primesLimit) {
primesLimit = n;
primes = generatePrimes(n);
}
return primes;
}
finally {
primesLock.unlock();
}
}
private native static long[] generatePrimes(long n);
}