/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package jmathexpr.arithmetic.equation.rule;
import java.util.ArrayList;
import java.util.List;
import jmathexpr.Expression;
import jmathexpr.arithmetic.equation.AbsoluteValueEquation;
import jmathexpr.arithmetic.equation.Equation;
import jmathexpr.arithmetic.equation.EquationSolveException;
import jmathexpr.relation.Equality;
import jmathexpr.set.EmptySet;
import jmathexpr.set.Set;
import jmathexpr.util.context.ExpressionContext;
import jmathexpr.util.context.Statement;
import jmathexpr.util.context.Statement.Command;
import jmathexpr.util.rule.Rule;
/**
* A rule machine can be filled in with rules and the machine executes these rules
* until the terminate rule provides the solution.
*
* @author Elemér Furka
*/
public class RuleMachine {
private final Equation equation;
private final List<Rule> rules = new ArrayList();
private final List<Rule> terminationRules = new ArrayList();
private Rule terminationRule;
private Equality actual;
private Rule rule, lastRule;
/**
* Creates a rule machine that can solve the given equation.
*
* @param equation the equation to be solved
*/
public RuleMachine(Equation equation) {
this.equation = equation;
terminationRules.add(new LinearTerminationRule(equation.variable()));
}
/**
* Adds an applicable rule to this machine.
*
* @param rule an arbitrary rule
*/
public void addRule(Rule rule) {
rules.add(rule);
}
/**
* Adds a termination rule to this machine.
*
* @param rule an arbitrary rule that returns a set
*/
public void addTerminationRule(Rule rule) {
terminationRules.add(rule);
}
/**
* Executes the rules to solve this machine's equation.
*
* @return a set of equation roots
* @throws jmathexpr.number.equation.EquationSolveException iff no more matching
* rule can be found while solving the equation
*/
public Set execute() throws EquationSolveException {
Expression result;
actual = equation;
addToContext(actual);
actual = new Equality(actual.lhs().evaluate(), actual.rhs().evaluate()); // simplify both sides
System.out.printf(" simplified: %s%n", actual);
addToContext(actual);
rule = rules.get(0);
while (!terminated()) {
if (rule == null) {
throw new EquationSolveException("No more matching rule");
}
System.out.printf(" rule: %s%n", rule);
if (isApplicable()) {
if (rule instanceof EquationRule) {
return fork(((EquationRule) rule).convertedEquations());
}
result = rule.apply();
if (result instanceof Equality) {
actual = (Equality) result;
} else if (result instanceof Set) {
System.out.printf(" root(s): %s%n", result);
return (Set) result;
}
if (actual instanceof Equation && !actual.getClass().equals(equation.getClass())) {
return new RuleMachine((Equation) actual).execute();
} else {
System.out.printf(" applied: %s%n", actual);
addToContext(actual);
actual = new Equality(actual.lhs().evaluate(), actual.rhs().evaluate());
System.out.printf(" eval'd: %s%n", actual);
addToContext(actual);
rule = nextRule(true);
}
} else {
rule = nextRule(false);
}
}
return (Set) terminationRule.apply();
}
private boolean isApplicable() {
if (rule instanceof AbsoluteValueEquation.IterativeRule) {
AbsoluteValueEquation.IterativeRule multirule = (AbsoluteValueEquation.IterativeRule) rule;
boolean isApplicable = false;
while (multirule.hasNext()) {
isApplicable = actual.isApplicable(multirule);
if (isApplicable) {
multirule.advance();
}
}
return isApplicable;
} else {
return actual.isApplicable(rule);
}
}
private Set fork(List<Equation> equations) throws EquationSolveException {
List<Set> results = new ArrayList();
for (Equation e : equations) {
System.out.printf(" solving %s: %s%n", e.getClass().getSimpleName(), e);
results.add(e.solve());
}
Set result = new EmptySet();
for (Set s : results) {
result = result.union(s);
}
return result;
}
private Rule nextRule(boolean justApplied) {
Rule next;
if (justApplied) {
lastRule = rule;
}
int lastRuleIndex = rules.indexOf(rule);
if (++lastRuleIndex < rules.size()) {
next = rules.get(lastRuleIndex);
} else if (lastRule == null) { // no more rule and no rule matched
return null;
} else {
next = rules.get(0);
}
next.reset();
if (rule.equals(lastRule) && !justApplied) {
return null;
} else {
return next;
}
}
private boolean terminated() {
for (Rule r : terminationRules) {
if (r.matches(actual)) {
terminationRule = r;
return true;
}
}
return false;
}
private void addToContext(Equality equality) {
ExpressionContext.getInstance().addStatement(
new Statement(Command.Expression, equality));
}
}