Package lipstone.joshua.parser

Source Code of lipstone.joshua.parser.CAS

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);
}
TOP

Related Classes of lipstone.joshua.parser.CAS

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.