package lipstone.joshua.parser.backend;
import java.util.ArrayList;
import java.util.concurrent.RecursiveTask;
import lipstone.joshua.parser.Parser;
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;
public class StepSolveThread extends RecursiveTask<ArrayList<BigDec>> {
private ConsCell equation, slopeEquation;
private ArrayList<ConsCell> slopeCells, variableCells;
private static boolean containsFactorial;
private static ArrayList<String> vars = null;
private static Parser parser;
private BigDec xMin, xMax, delta, domainMax;
private static BigDec answer, accuracy, range;
public ParserException error;
public StepSolveThread(ConsCell equation, Parser parser, BigDec delta, BigDec accuracy, BigDec xMin, BigDec xMax, ArrayList<String> vars, BigDec range) throws ParserException {
this(equation, delta, xMin, xMax);
this.parser = parser;
this.accuracy = accuracy;
this.vars = new ArrayList<String>(vars);
this.range = range;
ArrayList<ConsCell> equationParts = equation.splitOnIdentifier("=");
if (equationParts.size() == 1)
equationParts.add(new ConsCell(BigDec.ZERO, ConsType.NUMBER));
if (parser.containsVariables(equationParts.get(0), vars) && parser.containsVariables(equationParts.get(1), vars))
equation = equationParts.get(0).append(parser.getCAS().invertSign(equation));
else {
if (parser.containsVariables(equationParts.get(0), vars)) {
equation = equationParts.get(0);
ConsCell temp = parser.run(parser.preProcess(equationParts.get(1)));
if (temp.getCarType() != ConsType.NUMBER || temp.length() != 1)
throw new UndefinedResultException("The NumericalSolver requires that the equation contain only one variable.", null);
answer = (BigDec) temp.getCar();
}
if (parser.containsVariables(equationParts.get(1), vars)) {
equation = equationParts.get(1);
ConsCell temp = parser.run(parser.preProcess(equationParts.get(0)));
if (temp.getCarType() != ConsType.NUMBER || temp.length() != 1)
throw new UndefinedResultException("The NumericalSolver requires that the equation contain only one variable.", null);
answer = (BigDec) temp.getCar();
}
}
this.equation = parser.preProcess(equation).clone();
variableCells = new ArrayList<ConsCell>();
containsFactorial = this.equation.containsIdentifier("factorial");
initVariableCells(this.equation);
}
private StepSolveThread(ConsCell equation, BigDec delta, BigDec xMin, BigDec xMax) throws ParserException {
super();
//Yes, I know that this looks horrible, but it works.
slopeCells = new ArrayList<ConsCell>();
//First, create the slope structure. This creates: (a - b)/(c - d)
slopeEquation = new ConsCell(new ConsCell("a", ConsType.IDENTIFIER, new ConsCell('-', ConsType.OPERATOR, new ConsCell("b", ConsType.IDENTIFIER), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL,
new ConsCell('/', ConsType.OPERATOR, new ConsCell(new ConsCell("c", ConsType.IDENTIFIER, new ConsCell('-', ConsType.OPERATOR, new ConsCell("d", ConsType.IDENTIFIER), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL);
slopeCells.add((ConsCell) slopeEquation.cadr("a").getElementA()); //This saves a's spot.
slopeCells.add((ConsCell) slopeEquation.cadr("add").getElementA()); //This saves b's spot.
slopeCells.add((ConsCell) slopeEquation.cadr("dda").getElementA()); //This saves c's spot.
slopeCells.add((ConsCell) slopeEquation.cadr("ddadd").getElementA()); //This saves d's spot.
this.delta = delta;
this.xMin = xMin;
this.xMax = xMax;
domainMax = xMin.add(xMax);
if (vars != null) {
this.equation = equation.clone();
variableCells = new ArrayList<ConsCell>();
initVariableCells(this.equation);
}
}
private void initVariableCells(ConsCell equation) {
ConsCell current = equation;
do {
if (current.getCarType() == ConsType.CONS_CELL)
initVariableCells((ConsCell) current.getCar());
if (current.getCarType() == ConsType.IDENTIFIER && ((String) current.getCar()).equals(vars.get(0)))
variableCells.add(current);
} while (!(current = current.getNextConsCell()).isNull());
}
private ArrayList<BigDec> stepSolveDomain() throws ParserException {
ArrayList<BigDec> solutions = new ArrayList<BigDec>();
BigDec accuracy = this.accuracy;
if ((answer.abs().gteq(new BigDec(10000000)) || answer.abs().lteq(new BigDec(0.00000001))) && answer.neq(BigDec.ZERO))
accuracy = answer.multiply(accuracy).abs();
int i = 0;
if (containsFactorial) {
delta = BigDec.ONE;
xMin = BigDec.ZERO;
xMax = new BigDec(50);
i = 6;
}
while ((solutions.size() == 0 && i < 4) || i == 6) {
int maxIterations = (int) Math.round(xMax.subtract(xMin).divide(delta).doubleValue());
for (int sign = 1; sign >= -1; sign -= 2) {
int iterationsSinceDirectionChange = 0;
delta = delta.multiply(new BigDec(sign));
BigDec slope = BigDec.ZERO, prevSlope = BigDec.ZERO;
BigDec point1[] = {xMin, getY(xMin)}, point2[] = {xMin.add(delta), getY(xMin.add(delta))};
slope = getSlope(point1, point2);
int prevDirection = getDirection(prevSlope, point1[1], sign), direction = getDirection(slope, point2[1], sign);
//Check if either of the starting points is a solution
if (point1[1].lteq(answer.add(accuracy)) && point1[1].gteq(answer.subtract(accuracy)))
solutions.add(new BigDec(point1[0]));
if (point2[1].lteq(answer.add(accuracy)) && point2[1].gteq(answer.subtract(accuracy)))
solutions.add(new BigDec(point2[0]));
if (solutions.size() > 0) //If the equation has solutions from the starting points, stop
return solutions;
while (iterationsSinceDirectionChange < maxIterations) {
if (direction != prevDirection && (prevDirection != 0 || (point1[0].eq(xMin) && !solutions.contains(xMin))) && (direction != 0 || (point2[0].eq(xMax) && !solutions.contains(xMax)))) {
BigDec possibleSolution = oscillator(point1, point2, slope, prevDirection, direction, sign, delta, accuracy);
if (possibleSolution != null) {
System.out.println("possibleSolution: (" + possibleSolution + ", " + getY(possibleSolution) + ")");
if (isAnswer(possibleSolution, accuracy))
solutions.add(possibleSolution);
}
}
else if (direction == 2) //Only need to check direction because all prevDirections except for the first one (which is checked elsewhere) were first stored in direction
solutions.add(point2[0]);
//Iteration
point1[0] = point2[0];
point1[1] = point2[1];
point2[0] = point2[0].add(delta);
point2[1] = getY(point2[0]);
prevSlope = new BigDec(slope);
slope = getSlope(point1, point2);
prevDirection = direction;
direction = getDirection(slope, point2[1], sign);
iterationsSinceDirectionChange++;
if (direction != prevDirection)
iterationsSinceDirectionChange = 0;
if ((containsFactorial && point2[0].multiply(new BigDec(sign)).gteq(xMax.multiply(new BigDec(sign)).add(xMin))) || solutions.size() >= 10 || point2[0].gt(domainMax.abs().multiply(new BigDec(3))))
break;
}
}
i++;
if (i < 4)
delta = delta.divide(new BigDec(2.0));
}
return solutions;
}
/**
* Finds a possible answer by reversing direction and shrinking the jump after each slope change
*
* @return a possible answer
* @throws ParserException
*/
private BigDec oscillator(BigDec point1[], BigDec point2[], BigDec slope, int prevDirection, int direction, int sign, BigDec delta, BigDec accuracy) throws ParserException {
BigDec solution = null;
int numSwitches = 0, maxSteps = 20, steps = 0;
while (numSwitches <= 20 && steps < maxSteps) {
if (direction == 0 && prevDirection == 0) //If it is a straight line
break;
if (direction != prevDirection) {
delta = delta.multiply(new BigDec(-0.25));
numSwitches++;
steps = 0;
if (isAnswer(point1[0], accuracy))
solution = point1[0];
if (isAnswer(point2[0], accuracy))
solution = point2[0];
}
//Iteration
point1[0] = point2[0];
point1[1] = point2[1];
point2[0] = point2[0].add(delta);
point2[1] = getY(point2[0]);
slope = getSlope(point1, point2);
prevDirection = direction;
direction = getDirection(slope, point2[1], sign);
steps++;
}
if (isAnswer(point1[0], accuracy))
solution = point1[0];
if (isAnswer(point2[0], accuracy))
solution = point2[0];
return solution;
}
/**
* -1 = away, 0 = neutral, 1 = towards, 2 = answer
*
* @param slope
* the slope at the given point
* @param y
* the y-value at the given point
* @param sign
* the direction that the algorithm is looking
* @return -1 if it is sloping away from the answer, 0 if it is neutral but not on the answer (slope == 0), 1 if it is
* sloping towards the answer, 2 if it is at the answer
* @throws UndefinedResultException
*/
private int getDirection(BigDec slope, BigDec y, int sign) throws UndefinedResultException {
slope = slope.multiply(new BigDec(sign));
if (y.eq(answer))
return 2;
if (slope.eq(BigDec.ZERO))
return 0;
if (slope.lt(BigDec.ZERO))
return y.gt(answer) ? 1 : -1;
//If the slope is neither equal to or less than 0, it must be greater than 0, so no if statement is needed
return y.lt(answer) ? 1 : -1;
}
private BigDec getY(BigDec x) throws ParserException {
for (ConsCell c : variableCells)
c.replaceCar(new ConsCell(x, ConsType.NUMBER));
ConsCell result = parser.run(equation);
if (result.getCarType() != ConsType.NUMBER || result.length() != 1)
throw new UndefinedResultException("The NumericalSolver requires that the equation contain only one variable.", null);
return (BigDec) result.getCar();
}
private BigDec getSlope(BigDec point1[], BigDec point2[]) throws ParserException {
//First, replace the needed parts of the slope equation with the new numbers
slopeCells.get(0).replaceCar(new ConsCell(point1[1], ConsType.NUMBER));
slopeCells.get(1).replaceCar(new ConsCell(point2[1], ConsType.NUMBER));
slopeCells.get(2).replaceCar(new ConsCell(point1[0], ConsType.NUMBER));
slopeCells.get(3).replaceCar(new ConsCell(point2[0], ConsType.NUMBER));
ConsCell result = parser.run(equation);
if (result.getCarType() != ConsType.NUMBER || result.length() != 1)
throw new UndefinedResultException("The NumericalSolver requires that the equation contain only one variable.", null);
return (BigDec) result.getCar();
}
private boolean isAnswer(BigDec point, BigDec accuracy) throws ParserException {
try { //Try-Catch here because an UndefinedResultException means false, not error.
BigDec ans = getY(point);
//The slightly awkward comparison set is used here because it is faster than creating a new object for the absolute value method
return ans.lteq(answer.add(accuracy)) && ans.gteq(answer.subtract(accuracy));
}
catch (UndefinedResultException e) {
return false;
}
}
@Override
public ArrayList<BigDec> compute() {
ArrayList<BigDec> answers = new ArrayList<BigDec>();
if (equation.length() == 1) {
answers.add(new BigDec(answer));
return answers;
}
try {
if (xMax.subtract(xMin).lteq(range)) {
try {
answers.addAll(stepSolveDomain());
}
catch (ParserException pe) {
error = pe;
}
}
else {
StepSolveThread left = new StepSolveThread(equation, delta, xMin, xMin.add(xMax.subtract(xMin).divide(new BigDec(2))));
StepSolveThread right = new StepSolveThread(equation, delta, xMin.add(xMax.subtract(xMin).divide(new BigDec(2))), xMax);
left.fork();
answers.addAll(right.compute());
if (right.error != null) {
error = right.error;
return answers;
}
answers.addAll(left.join());
if (left.error != null) {
error = left.error;
return answers;
}
}
}
catch (ParserException e) {
error = e;
}
return answers;
}
}