package jscl.math.function; import com.google.common.collect.Lists; import jscl.CustomFunctionCalculationException; import jscl.JsclMathEngine; import jscl.NumeralBase; import jscl.math.*; import jscl.text.ParseException; import jscl.text.msg.JsclMessage; import jscl.text.msg.Messages; import org.solovyev.common.math.MathEntity; import org.solovyev.common.msg.MessageType; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class CustomFunction extends Function implements IFunction { private final static AtomicInteger counter = new AtomicInteger(0); private final int id; @Nonnull private Expression content; @Nullable private String description; @Nonnull private List<String> parameterNames = Collections.emptyList(); @Nullable private List<ConstantData> parameterConstants; private CustomFunction(@Nonnull String name, @Nonnull List<String> parameterNames, @Nonnull Expression content, @Nullable String description) { super(name, new Generic[parameterNames.size()]); this.parameterNames = parameterNames; this.content = content; this.description = description; this.id = counter.incrementAndGet(); } @Nonnull private List<ConstantData> makeParameterConstants(@Nonnull List<String> names) { return new ArrayList<>(Lists.transform(names, new com.google.common.base.Function<String, ConstantData>() { @Nullable @Override public ConstantData apply(@Nullable String name) { return new ConstantData(name); } })); } private CustomFunction(@Nonnull String name, @Nonnull List<String> parameterNames, @Nonnull String content, @Nullable String description) throws CustomFunctionCalculationException { super(name, new Generic[parameterNames.size()]); this.parameterNames = parameterNames; final JsclMathEngine engine = JsclMathEngine.getInstance(); final NumeralBase nb = engine.getNumeralBase(); if (nb != NumeralBase.dec) { // numbers in functions are only supported in decimal base engine.setNumeralBase(NumeralBase.dec); } try { this.content = Expression.valueOf(content); ensureNoImplicitFunctions(); } catch (ParseException e) { throw new CustomFunctionCalculationException(this, e); } finally { if (nb != NumeralBase.dec) { engine.setNumeralBase(nb); } } this.description = description; this.id = counter.incrementAndGet(); } private void ensureNoImplicitFunctions() { for (int i = 0; i < this.content.size(); i++) { final Literal literal = this.content.literal(i); for (int j = 0; j < literal.size(); j++) { final Variable variable = literal.getVariable(j); if (variable instanceof ImplicitFunction) { throw new CustomFunctionCalculationException(this, new JsclMessage(Messages.msg_13, MessageType.error, variable.getName())); } } } } @Override public int getMinParameters() { return parameterNames == null ? 0 : parameterNames.size(); } @Override public int getMaxParameters() { return parameterNames == null ? Integer.MAX_VALUE: parameterNames.size(); } @Override public Generic substitute(@Nonnull Variable variable, @Nonnull Generic generic) { return super.substitute(variable, generic); } @Override public Generic numeric() { return selfExpand().numeric(); } @Override public Generic expand() { return selfExpand().expand(); } @Override public Generic elementary() { return selfExpand().elementary(); } @Override public Generic factorize() { return selfExpand().factorize(); } @Override public Generic selfExpand() { Generic content = this.content; final List<ConstantData> parameterConstants = getParameterConstants(); for (ConstantData cd : parameterConstants) { content = content.substitute(cd.local, cd.globalExpression); } for (int i = 0; i < parameterConstants.size(); i++) { final ConstantData cd = parameterConstants.get(i); content = content.substitute(cd.global, parameters[i]); } for (ConstantData cd : parameterConstants) { content = content.substitute(cd.global, cd.localExpression); } return content; } @Nonnull private List<ConstantData> getParameterConstants() { if(parameterConstants == null) { parameterConstants = makeParameterConstants(parameterNames); } return parameterConstants; } @Override public void copy(@Nonnull MathEntity mathEntity) { super.copy(mathEntity); if (mathEntity instanceof CustomFunction) { final CustomFunction that = (CustomFunction) mathEntity; this.content = that.content; this.parameterNames = new ArrayList<String>(that.parameterNames); this.description = that.description; } } @Override public Generic selfElementary() { throw new ArithmeticException(); } @Override public Generic selfSimplify() { return expressionValue(); } @Override public Generic selfNumeric() { throw new ArithmeticException(); } @Override public Generic antiDerivative(@Nonnull Variable variable) throws NotIntegrableException { if (getParameterForAntiDerivation(variable) < 0) { throw new NotIntegrableException(this); } else { return this.content.antiDerivative(variable); } } @Override public Generic antiDerivative(int n) throws NotIntegrableException { throw new NotIntegrableException(this); } @Nonnull @Override public Generic derivative(@Nonnull Variable variable) { Generic result = JsclInteger.valueOf(0); for (int i = 0; i < parameters.length; i++) { // chain rule: f(x) = g(h(x)) => f'(x) = g'(h(x)) * h'(x) // hd = h'(x) // gd = g'(x) final Generic hd = parameters[i].derivative(variable); final Generic gd = this.content.derivative(variable); result = result.add(hd.multiply(gd)); } return result; } @Override public Generic derivative(int n) { throw new ArithmeticException(); } @Nonnull public String getContent() { return this.content.toString(); } @Nullable public String getDescription() { return this.description; } @Nonnull public List<String> getParameterNames() { return Collections.unmodifiableList(parameterNames); } @Nonnull @Override protected String formatUndefinedParameter(int i) { if (i < this.parameterNames.size()) { return parameterNames.get(i); } else { return super.formatUndefinedParameter(i); } } @Nonnull @Override public CustomFunction newInstance() { return new CustomFunction(name, parameterNames, content, description); } public static class Builder { private final boolean system; @Nonnull private String content; @Nullable private String description; @Nonnull private List<String> parameterNames; @Nonnull private String name; @Nullable private Integer id; public Builder(@Nonnull String name, @Nonnull List<String> parameterNames, @Nonnull String content) { this.system = false; this.content = content; this.parameterNames = parameterNames; this.name = name; } public Builder(@Nonnull IFunction function) { this.system = function.isSystem(); this.content = function.getContent(); this.description = function.getDescription(); this.parameterNames = new ArrayList<String>(function.getParameterNames()); this.name = function.getName(); if (function.isIdDefined()) { this.id = function.getId(); } } public Builder() { this.system = false; } public Builder(boolean system, @Nonnull String name, @Nonnull List<String> parameterNames, @Nonnull String content) { this.system = system; this.content = content; this.parameterNames = parameterNames; this.name = name; } @Nonnull private static String prepareContent(@Nonnull String content) { final StringBuilder result = new StringBuilder(content.length()); final char groupingSeparator = JsclMathEngine.getInstance().getGroupingSeparator(); for (int i = 0; i < content.length(); i++) { final char ch = content.charAt(i); switch (ch) { case ' ': case '\'': case '\n': case '\r': // do nothing break; default: // remove grouping separator if (ch != groupingSeparator) { result.append(ch); } } } return result.toString(); } @Nonnull public Builder setDescription(@Nullable String description) { this.description = description; return this; } @Nonnull public Builder setId(@Nonnull Integer id) { this.id = id; return this; } @Nonnull public Builder setContent(@Nonnull String content) { this.content = content; return this; } @Nonnull public Builder setParameterNames(@Nonnull List<String> parameterNames) { this.parameterNames = parameterNames; return this; } @Nonnull public Builder setName(@Nonnull String name) { this.name = name; return this; } public CustomFunction create() throws CustomFunctionCalculationException { final CustomFunction customFunction = new CustomFunction(name, parameterNames, prepareContent(content), description); customFunction.setSystem(system); if (id != null) { customFunction.setId(id); } return customFunction; } } private final class ConstantData { @Nonnull final Constant global; @Nonnull final Constant local; @Nonnull final Generic globalExpression; @Nonnull final Generic localExpression; public ConstantData(@Nonnull String name) { global = new Constant(name + "#" + id); globalExpression = Expression.valueOf(global); local = new Constant(name); localExpression = Expression.valueOf(local); } } }