package calculus;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lipstone.joshua.parser.Tokenizer;
import lipstone.joshua.parser.exceptions.InvalidOperationException;
import lipstone.joshua.parser.exceptions.ParserException;
import lipstone.joshua.parser.exceptions.PluginConflictException;
import lipstone.joshua.parser.exceptions.UndefinedResultException;
import lipstone.joshua.parser.plugin.ParserPlugin;
import lipstone.joshua.parser.plugin.helpdata.Operation;
import lipstone.joshua.parser.plugin.helpdata.Parameter;
import lipstone.joshua.parser.plugin.types.OperationPlugin;
import lipstone.joshua.parser.types.BigDec;
import lipstone.joshua.parser.util.ConsCell;
import lipstone.joshua.parser.util.ConsType;
import lipstone.joshua.parser.util.PairedList;
public class Calculus extends ParserPlugin implements OperationPlugin {
private static double accuracy = 0.00000001;
private int N = 10000, maxN = N * 16;
private String var = "[xyz]";
private static String[] functionDerivatives = {"sin", "cos(n)", "cos", "-sin(n)", "tan", "(sec(n))^2", "sec", "sec(n)*tan(n)", "csc", "-csc(n)*cot(n)", "cot", "-(csc(n))^2",
"arcsin", "1/((1-(n)^2)^(1/2))", "arccos", "-1/((1-(n)^2)^(1/2))", "arctan", "1/(1+(n)^2)", "arcsec", "1/(abs(n)*(1+(n)^2)^(1/2))", "arccsc", "-1/(abs(n)*(1+(n)^2)^(1/2))", "arccot", "-1/(1+(n)^2)",
"sinh", "cosh(n)", "cosh", "sinh(n)", "tanh", "(sech(n))^2", "sech", "-sech(n)*tanh(n)", "csch", "-csch(n)*coth(n)", "coth", "-(csch(n))^2",
"arcsinh", "1/(((n)^2+1)^(1/2))", "arccosh", "1/(((n)^2-1)^(1/2))", "arctanh", "1/(1-(n)^2)", "arcsech", "-1/((n)*(1-(n)^2)^(1/2))", "arccsch", "-1/(abs(n)*(1+(n)^2)^(1/2))", "arccoth", "1/(1-(n)^2)",
"ln", "1/(n)", "log", "ln(10)/(n)"};
//private static String[] integralTable = {"sin({VAR})", "-cos({VAR})", "(sin({VAR}))^({CONST1})", "-(sin({VAR}))^({CONST1}-1)*cos({VAR})/{CONST1}+({CONST1}-1)/{CONST1}*{INTEGRATE:(sin({VAR}))^({CONST1}-2)}"};
private BigDec integrate(ConsCell input) throws ParserException {
return integrate(input, "trapezoidal");
}
private BigDec integrate(ConsCell input, String method) throws ParserException {
ArrayList<String> vars = parser.getVariables(input);
ArrayList<ConsCell> parts = input.splitOnSeparator();
if (parts.size() < 3)
return BigDec.ZERO;
if (!parser.containsVariables(parts.get(0)))
var = "[xyz]";
ConsCell temp = parts.get(0);
Pattern p = Pattern.compile("d[xyz" + vars.get(0) + "]");
do {
if (temp.getCarType() != ConsType.IDENTIFIER)
continue;
Matcher m = p.matcher((String) temp.getCar());
if (m.find())
temp = temp.remove();
if (temp == null)
break;
} while (!(temp = temp.getNextConsCell()).isNull());
BigDec limits[] = {BigDec.ZERO, BigDec.ZERO};
temp = parser.run(parts.get(1));
if (temp.getCarType() != ConsType.NUMBER)
throw new InvalidOperationException("The second and third parameters for the integrate operation must both evaluate to a number", this, "integrate", parts);
limits[0] = (BigDec) temp.getCar();
temp = parser.run(parts.get(2));
if (temp.getCarType() != ConsType.NUMBER)
throw new InvalidOperationException("The second and third parameters for the integrate operation must both evaluate to a number", this, "integrate", parts);
limits[1] = (BigDec) temp.getCar();
if (limits[1] == limits[0])
return BigDec.ZERO;
if (limits[1].lt(limits[0])) {
BigDec tempLim = limits[0];
limits[0] = limits[1];
limits[1] = tempLim;
};
BigDec answer = BigDec.ZERO;
if (method.equalsIgnoreCase("trapezoidal"))
answer = trapezoidal(parts.get(0), limits[0], limits[1], N);
var = "[xyz]";
return answer;
}
private BigDec trapezoidal(ConsCell equation, BigDec min, BigDec max, int N) throws ParserException {
BigDec output = BigDec.ZERO;
NIThread thread = new NIThread(equation, parser, N, maxN, accuracy, min, max, new BigDec(500), var, this);
output = parser.getFjPool().invoke(thread);
if (thread.error != null)
throw thread.error;
return output;
}
private ConsCell derivative(ConsCell input) throws ParserException {
BigDec slope = BigDec.ZERO, slopePos = BigDec.ZERO, slopeNeg = BigDec.ZERO, x = BigDec.ZERO;
ArrayList<ConsCell> parts = input.splitOnSeparator();
ConsCell temp = parser.run(parts.get(1));
if (temp.getCarType() != ConsType.NUMBER) {
return symbolicDerivative(input);
}
x = (BigDec) temp.getCar();
slopePos = pointDerivative(parts.get(0).clone(), x, 1.0);
slopeNeg = pointDerivative(parts.get(0), x, -1.0);
slope = slopePos.add(slopeNeg).divide(new BigDec(2));
return new ConsCell(slope, ConsType.NUMBER);
}
private BigDec pointDerivative(ConsCell equation, BigDec x, double direction) throws ParserException {
var = parser.getVariables(equation).get(0);
ArrayList<ConsCell> varCells = equation.allInstancesOf(new ConsCell(var, ConsType.IDENTIFIER));
for (ConsCell varCell : varCells)
varCell.replaceCar(new ConsCell(x, ConsType.NUMBER));
ConsCell temp = parser.run(equation);
if (temp.getCarType() != ConsType.NUMBER)
throw new UndefinedResultException("The second argument to the derivative operation must cause the first to evaluate to a number.", this);
BigDec slope = BigDec.ZERO, prevSlope = BigDec.ONE, deltaX = new BigDec(0.0001), y1 = (BigDec) temp.getCar();
BigDec x2 = x.add(deltaX.multiply(new BigDec(direction)));
for (ConsCell varCell : varCells)
varCell.replaceCar(new ConsCell(x2, ConsType.NUMBER));
temp = parser.run(equation);
if (temp.getCarType() != ConsType.NUMBER)
throw new UndefinedResultException("The second argument to the derivative operation must cause the first to evaluate to a number.", this);
//In basic types: slope = (y1 - Double.parseDouble(parser.run(equation.replaceAll(var, new Double(x+deltaX*direction).toString())))/(x-(x+deltaX*direction)));
slope = y1.subtract((BigDec) temp.getCar()).divide(deltaX.multiply(new BigDec(direction)));
while (slope.subtract(prevSlope).abs().doubleValue() > accuracy) {
prevSlope = slope;
deltaX = deltaX.divide(new BigDec(10.0));
x2 = x.add(deltaX.multiply(new BigDec(direction)));
for (ConsCell varCell : varCells)
varCell.replaceCar(new ConsCell(x2, ConsType.NUMBER));
temp = parser.run(equation);
if (temp.getCarType() != ConsType.NUMBER)
throw new UndefinedResultException("The second argument to the derivative operation must cause the first to evaluate to a number.", this);
//In basic types: slope = (y1 - Double.parseDouble(parser.run(equation.replaceAll(var, new Double(x+deltaX*direction).toString())))/(x-(x+deltaX*direction)));
slope = y1.subtract((BigDec) temp.getCar()).divide(deltaX.multiply(new BigDec(direction)));
}
if ((slope.abs().doubleValue() < accuracy) && equation.length() < 4)
slope = BigDec.ZERO;
var = "[xyz]";
return slope.multiply(BigDec.MINUSONE);
}
/**
* This is for the inner recursion DO NOT USE FOR INITIAL PASSTHROUGH
*
* @param section
* the section of the equation
* @param vars
* the variables
* @return the symbolic derivative of the part of the equation in section
*/
private ConsCell symbolicDerivative(ConsCell section, ArrayList<String> vars) throws ParserException {
parser.setVars(vars);
ArrayList<ConsCell> equation = parser.getCAS().getTerms(section);
equation = parser.getCAS().foldSigns(equation);
for (int i = 0; i < equation.size(); i++) {
ConsCell term = equation.get(i);
term = stopCases(term, vars);
boolean stopped = ((String) term.getLastConsCell().getCar()).equals("{true}"); //Guaranteed by stopCases that the last ConsCell in term will be an OBJECT cell,
term.getLastConsCell().remove();
if (parser.containsVariables(term, vars) && !stopped)
term = rules(term, vars);
equation.set(i, term);
}
section = new ConsCell();
for (ConsCell term : equation)
if (term.getCarType() != ConsType.OPERATOR)
section = section.append(new ConsCell('+', ConsType.OPERATOR, term, ConsType.CONS_CELL));
else
section = section.append(term);
section = parser.getCAS().simplifyTerms(parser.removeDoubles(section.getFirstConsCell()));
if (section.getCarType() == ConsType.OPERATOR && ((Character) section.getCar()) == '+')
section = section.remove();
if (section == null)
return new ConsCell();
if (section.getLastConsCell().getCarType() == ConsType.OPERATOR && ((Character) section.getLastConsCell().getCar()) == '+')
section.getLastConsCell().remove();
return section;
}
private ConsCell stopCases(ConsCell term, ArrayList<String> vars) throws ParserException {
BigDec coefficient = parser.getCAS().getCoefficient(term, vars);
if (!parser.containsVariables(term, vars) || coefficient.eq(BigDec.ZERO))
return new ConsCell(BigDec.ZERO, ConsType.NUMBER, new ConsCell("{true}", ConsType.OBJECT), ConsType.CONS_CELL);
PairedList<ConsCell, ConsCell> orders = parser.getCAS().orderOfTerm(term, vars);
int remaining = orders.size();
for (String str : vars) { //Basically, if a variable's power is 1, taking its derivative gets rid of it.
if (!orders.containsKey(new ConsCell(str, ConsType.IDENTIFIER)))
continue;
ConsCell result = parser.run(orders.get(new ConsCell(str, ConsType.IDENTIFIER)));
if (orders.containsKey(new ConsCell(str, ConsType.IDENTIFIER)) && result.getCarType() == ConsType.NUMBER && result.length() == 1 && ((BigDec) result.getCar()).eq(BigDec.ONE))
remaining--;
}
if (remaining == 0)
return new ConsCell(coefficient, ConsType.NUMBER, new ConsCell("{true}", ConsType.OBJECT), ConsType.CONS_CELL);
if (orders.size() == 1)
return chainFunctions(parser.getCAS().makeTerm(coefficient, orders, false), vars);
return term.getLastConsCell().append(new ConsCell("{false}", ConsType.OBJECT)).getFirstConsCell();
}
private ConsCell chainFunctions(ConsCell term, ArrayList<String> vars) throws ParserException {
term = parser.getCAS().removeExcessParentheses(term);
int type = -1, offset = 1;
if (term.getCarType() == ConsType.OPERATOR && term.getCdrType() == ConsType.CONS_CELL)
offset = 2;
String check = term.getNextConsCell(offset - 1).singular().toString().trim();
for (int i = 0; i < functionDerivatives.length; i += 2)
if (check.equalsIgnoreCase(functionDerivatives[i])) {
type = i;
break;
}
BigDec coefficient = parser.getCAS().getCoefficient(term, vars);
ConsCell result = coefficient.neq(BigDec.ONE) ? new ConsCell(coefficient, ConsType.NUMBER, new ConsCell('*', ConsType.OPERATOR), ConsType.CONS_CELL).getLastConsCell() : new ConsCell();
if (type > -1 && term.getNextConsCell(offset).length() == 1) {
ConsCell inner = term.getNextConsCell(offset).getCarType() == ConsType.CONS_CELL ? (ConsCell) term.getNextConsCell(offset).getCar() : term.getNextConsCell(offset);
ConsCell functionOutput = Tokenizer.tokenizeString(functionDerivatives[type + 1]);
ArrayList<ConsCell> cells = functionOutput.allInstancesOf(new ConsCell("n", ConsType.IDENTIFIER));
for (ConsCell cell : cells)
cell.replaceCar(inner.clone()); //Switch out any instances of n with the inner part of the chain function.
result = result.append(functionOutput);
ConsCell chain = symbolicDerivative(inner, vars);
if (chain.length() == 1 && chain.getCarType() == ConsType.NUMBER && ((BigDec) chain.getCar()).eq(BigDec.ONE))
return result.append(new ConsCell("{true}", ConsType.OBJECT)).getFirstConsCell();
return result.append(new ConsCell('*', ConsType.OPERATOR, chain, ConsType.CONS_CELL)).append(new ConsCell("{true}", ConsType.OBJECT)).getFirstConsCell();
}
term.getLastConsCell().append(new ConsCell("{false}", ConsType.OBJECT));
return term;
}
private ConsCell rules(ConsCell term, ArrayList<String> vars) throws ParserException {
for (int priority = 0; priority < 2; priority++) {
ConsCell current = term;
while (!(current = current.getNextConsCell()).isNull() && !current.getNextConsCell().isNull()) {
if (current.getCarType() != ConsType.OPERATOR || !parser.containsVariables(current.getPreviousConsCell(), vars) && !parser.containsVariables(current.getNextConsCell(), vars))
continue;
if ((Character) current.getCar() == '*' && priority == 0)
return multiplicationRule(current, vars);
else if ((Character) current.getCar() == '/' && priority == 0)
return divisionRule(current, vars);
else if ((Character) current.getCar() == '^' && priority == 1) {
current.replaceCar(powerRule(current, vars));
term = current.getFirstConsCell();
}
}
}
return term;
}
private ConsCell multiplicationRule(ConsCell current, ArrayList<String> vars) throws ParserException {
ConsCell head = current.getPreviousConsCell(), left = head.singular(), right = current.getNextConsCell().clone();
while (!(head = head.getPreviousConsCell()).isNull() && !(head.getCarType() == ConsType.OPERATOR && ((Character) head.getCar() == '*' || (Character) head.getCar() == '/')))
left = head.singular().append(left.getFirstConsCell());
left = left.getFirstConsCell();
for (int i = 0; i < left.length(); i++)
current.getPreviousConsCell().remove();
ConsCell derivative = new ConsCell();
if (parser.containsVariables(left, vars))
derivative = new ConsCell(symbolicDerivative(left, vars), ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR, new ConsCell(right.clone(), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL).getLastConsCell();
if (parser.containsVariables(right, vars))
derivative = derivative.append(new ConsCell('+', ConsType.OPERATOR, new ConsCell(left.clone(), ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR, new ConsCell(symbolicDerivative(right, vars),
ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL));
current.getNextConsCell().removeAll();
current = current.replace(derivative.getFirstConsCell()).getFirstConsCell();
return current;
}
private ConsCell divisionRule(ConsCell current, ArrayList<String> vars) throws ParserException {
ConsCell head = current.getPreviousConsCell(), left = head.singular(), right = current.getNextConsCell().clone();
while (!(head = head.getPreviousConsCell()).isNull() && !(head.getCarType() == ConsType.OPERATOR && ((Character) head.getCar() == '*' || (Character) head.getCar() == '/')))
left = head.singular().append(left.getFirstConsCell());
left = left.getFirstConsCell();
for (int i = 0; i < left.length(); i++)
current.getPreviousConsCell().remove();
ConsCell derivative = new ConsCell();
if (parser.containsVariables(left, vars))
derivative = new ConsCell(symbolicDerivative(left, vars), ConsType.CONS_CELL, new ConsCell('*', ConsType.CONS_CELL, new ConsCell(right.clone(), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL);
if (parser.containsVariables(right, vars))
derivative = derivative.append(new ConsCell('-', ConsType.OPERATOR, new ConsCell(left.clone(), ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR, new ConsCell(symbolicDerivative(right, vars),
ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL));
derivative = new ConsCell(derivative.getFirstConsCell(), ConsType.CONS_CELL);
derivative.append(new ConsCell('/', ConsType.OPERATOR, new ConsCell(right.clone(), ConsType.CONS_CELL, new ConsCell('^', ConsType.OPERATOR, new ConsCell(new BigDec(2),
ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL));
current.getNextConsCell().removeAll();
current.replace(derivative);
return current.getFirstConsCell();
}
private ConsCell powerRule(ConsCell current, ArrayList<String> vars) throws ParserException {
ConsCell derivative = new ConsCell();
ConsCell left = current.getPreviousConsCell().singular(), right = current.getNextConsCell().singular();
current.getPreviousConsCell().remove();
current.getNextConsCell().remove();
ConsCell temp = current;
while (!(temp = temp.getPreviousConsCell()).isNull() && temp.getCarType() != ConsType.OPERATOR) {
left = temp.singular().append(left).getFirstConsCell();
temp = temp.remove();
}
temp = current;
while (!(temp = temp.getNextConsCell()).isNull() && temp.getCarType() != ConsType.OPERATOR) {
right = right.append(temp.singular());
temp = temp.remove();
}
right = right.getFirstConsCell();
if (parser.containsVariables(right, vars)) {
derivative = derivative.append(new ConsCell(symbolicDerivative(right, vars), ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR), ConsType.CONS_CELL));
derivative = derivative.append(new ConsCell(left.clone(), ConsType.CONS_CELL, new ConsCell('^', ConsType.OPERATOR, new ConsCell(right.clone(), ConsType.CONS_CELL), ConsType.CONS_CELL), ConsType.CONS_CELL));
derivative = derivative.append(new ConsCell('*', ConsType.OPERATOR, new ConsCell("ln", ConsType.IDENTIFIER, new ConsCell(left.clone(), ConsType.CONS_CELL),
ConsType.CONS_CELL), ConsType.CONS_CELL));
}
if (parser.containsVariables(left, vars)) {
if (!derivative.isNull())
derivative = derivative.append(new ConsCell('+', ConsType.OPERATOR));
derivative = derivative.append(new ConsCell(right.clone(), ConsType.CONS_CELL, new ConsCell('*', ConsType.OPERATOR, left.clone(), ConsType.CONS_CELL), ConsType.CONS_CELL));
derivative = derivative.append(new ConsCell('^', ConsType.OPERATOR, new ConsCell(parser.run(right.clone().append(
new ConsCell('-', ConsType.OPERATOR, new ConsCell(BigDec.ONE, ConsType.NUMBER), ConsType.CONS_CELL)).getFirstConsCell()), ConsType.CONS_CELL), ConsType.CONS_CELL));
derivative = derivative.append(new ConsCell('*', ConsType.OPERATOR, new ConsCell(symbolicDerivative(left, vars), ConsType.CONS_CELL), ConsType.CONS_CELL));
}
derivative = parser.getCAS().simplifyTerm(parser.getCAS().removeExcessParentheses(derivative.getFirstConsCell()));
return derivative;
}
/**
* Takes the symbolic derivative of a function
*
* @param input
* the function
* @return the symbolic derivative of the function
*/
private ConsCell symbolicDerivative(ConsCell input) throws ParserException {
ArrayList<ConsCell> parts = input.splitOnSeparator();
ArrayList<String> vars = new ArrayList<String>();
if (parts.size() < 1)
return input;
if (parts.size() > 1) {
vars = new ArrayList<String>(parts.size() - 1);
for (int i = 1; i < parts.size(); i++)
vars.set(i - 1, parts.get(i).toString());
}
else
vars = parser.getVariables(input);
parser.setVars(vars);
parts.set(0, parser.preProcess(parts.get(0)));
ConsCell output = symbolicDerivative(parts.get(0), vars);
if (output.toString().equalsIgnoreCase(""))
return new ConsCell(BigDec.ZERO, ConsType.NUMBER);
return output;
}
public String symbolicIntegration(String input) throws UnableToIntegrateException {
String output = new String(input);
if (output.equals(input))
throw new UnableToIntegrateException("Unable to integrate " + input, this);
return output;
}
@Override
public void loadOperations() throws PluginConflictException {
ArrayList<Parameter> params = new ArrayList<Parameter>();
params.add(new Parameter("equation", "the equation to be integrated", false));
params.add(new Parameter("min", "the lower bound of the integration, any real number", false));
params.add(new Parameter("max", "the upper bound of the integration, any real number greater than min", false));
addOperation(new Operation("integrate", params, "The numeric value of the integral of the function between min and max",
"Numerically integrates the function over [min, max] via the trapezoid rule.", new String[]{"integral"}, this));
params.clear();
params.add(new Parameter("equation", "the equation to be derived", false));
params.add(new Parameter("x-coordinate", "the point at which to calculate the slope of the tangent line", true));
addOperation(new Operation("derivative", params, "The symbolic derivative of the function, or, if the x-coordinate is provided, the slope of the tangent line at that point.",
"Finds the symbolic derivative of the equation using the complete chain rule (x^n, n^x, x^x), product, quoteint, and logrithmic derivation formulae.", this, true));
}
@Override
public ConsCell runOperation(String operation, ConsCell input) throws ParserException {
if (operation.equals("integrate"))
return new ConsCell(integrate(input), ConsType.NUMBER);
ArrayList<ConsCell> parts = input.splitOnSeparator();
if (operation.equals("derivative") && parts.size() == 2)
return new ConsCell(derivative(input), ConsType.NUMBER);
if (operation.equals("derivative") && parts.size() >= 1)
return symbolicDerivative(input);
return null;
}
@Override
public void unloadOperations() {/* Don't need to do anything here */}
}