/*
 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
 * Copyright (C) 2006 - JScience (http://jscience.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package org.jscience.mathematics.function;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import org.jscience.mathematics.structure.GroupAdditive;
import org.jscience.mathematics.structure.GroupMultiplicative;

import javolution.context.LocalContext;
import javolution.context.ObjectFactory;
import javolution.util.FastList;
import javolution.lang.Realtime;
import javolution.text.Text;
import javolution.text.TextBuilder;

/**
 * 

This abstract class represents a mapping between two sets such that * there is a unique element in the second set assigned to each element * in the first set.

* *

Functions can be discrete or continuous and multivariate functions * (functions with multiple variables) are also supported as illustrated * below:[code] * // Defines local variables. * Variable.Local varX = new Variable.Local("x"); * Variable.Local varY = new Variable.Local("y"); * * // f(x, y) = x² + x·y + 1; * Polynomial x = Polynomial.valueOf(Rational.ONE, varX); * Polynomial y = Polynomial.valueOf(Rational.ONE, varY); * Polynomial fx_y = x.pow(2).plus(x.times(y)).plus(Rational.ONE); * System.out.println("f(x,y) = " + fx_y); * * // Evaluates f(1,0) * System.out.println("f(1,0) = " + fx_y.evaluate(Rational.ONE, Rational.ZERO)); * * // Calculates df(x,y)/dx * System.out.println("df(x,y)/dx = " + fx_y.differentiate(varX)); * * > f(x,y) = [1/1]x^2 + [1/1]xy + [1/1] * > f(1,0) = 2/1 * > df(x,y)/dx = [2/1]x + [1/1]y * [/code]

* *

Functions are often given by formula (e.g. f(x) = x²-x+1, * f(x,y)= x·y) but the general function instance might tabulate * the values, solve an equation, etc.

* * @author Jean-Marie Dautelle * @version 3.1, April 1, 2006 * @see * Wikipedia: Functions (mathematics) */ public abstract class Function implements Serializable, Realtime { // TODO: Implements XMLSerializable. /** * Default constructor. */ protected Function() { } /** * Returns a lexically ordered list of the variables (or arguments) * for this function (empty list for constant functions). * * @return this function current unset variables (sorted). */ public abstract List> getVariables(); /** * Evaluates this function using its {@link Variable variables} current * values. * * @return the evaluation of this function. * @throws FunctionException if any of this function's variable is not set. */ public abstract Y evaluate(); /** * Indicates if this function is equals to the specified object. * * @param obj the object to be compared with. * @return true if this function and the specified argument * represent the same function; false otherwise. */ public boolean equals(Object obj) { return super.equals(obj); } /** * Returns the hash code for this function (consistent with * {@link #equals(Object)}. * * @return this function hash code. */ public int hashCode() { return super.hashCode(); } /** * Retrieves the variable from this function having the specified * symbol (convenience method). * * @return the variable having the specified symbol or null * if none. */ public final Variable getVariable(String symbol) { for (Variable v : this.getVariables()) { if (symbol.equals(v.getSymbol())) return v; } return null; } /** * Evaluates this function for the specified argument value * (convenience method). The evaluation is performed * in a {@link javolution.context.LocalContext LocalContext} and * can safely be called upon functions with {@link Variable.Global global * variables}. * * @param arg the single variable value used for the evaluation. * @return the evaluation of this function. * @throws FunctionException if getVariables().size() != 1 */ public final Y evaluate(X arg) { List> vars = getVariables(); if (vars.size() != 1) throw new FunctionException("This function is not monovariate"); Variable x = vars.get(0); X prev = x.get(); LocalContext.enter(); try { x.set(arg); return evaluate(); } finally { x.set(prev); LocalContext.exit(); } } /** * Evaluates this function for the specified arguments values * (convenience method). The evaluation is performed * in a {@link javolution.context.LocalContext LocalContext} and * can safely be called upon functions with {@link Variable.Global global * variables}. * * @param args the variables values used for the evaluation. * @return the evaluation of this function. * @throws IllegalArgumentException * if args.length != getVariables().size()) */ public final Y evaluate(X... args) { List> vars = getVariables(); if (vars.size() != args.length) throw new IllegalArgumentException("Found " + args.length + " arguments, but " + vars.size() + "required"); LocalContext.enter(); try { return evaluate(args, vars, 0); } finally { LocalContext.exit(); } } private final Y evaluate(X[] args, List> vars, int i) { if (i < args.length) { Variable var = vars.get(i); X prev = var.get(); var.set(args[i]); try { return evaluate(args, vars, i + 1); } finally { var.set(prev); // Restores previous variable value. } } else { return evaluate(); } } /** * Returns the composition of this function with the one specified. * * @param that the function for which the return value is passed as * argument to this function. * @return the function (this o that) * @throws FunctionException if this function is not monovariate. */ public Function compose(Function that) { if (getVariables().size() != 1) throw new FunctionException("This function is not monovariate"); return Compose.newInstance(this, that); } /** * Returns the first derivative of this function with respect to * the specified variable. * * @param v the variable for which the derivative is calculated. * @return d[this]/dv * @see * Derivative -- from MathWorld * @throws FunctionException if the derivative is undefined. */ public Function differentiate(Variable v) { return Derivative.newInstance(this, v); } /** * Returns an integral of this function with respect to * the specified variable. * * @param v the variable for which the integral is calculated. * @return S[this·dv] * @see * Integral -- from MathWorld */ public Function integrate(Variable v) { return Integral.newInstance(this, v); } /** * Returns the sum of this function with the one specified. * * @param that the function to be added. * @return this + that. */ public Function plus(Function that) { return Plus.newInstance(this, that); } /** * Returns the difference of this function with the one specified. * * @param that the function to be subtracted. * @return this - that. */ @SuppressWarnings("unchecked") public Function minus(Function that) { if (that instanceof GroupAdditive) { Function thatOpposite = (Function) ((GroupAdditive) that) .opposite(); return this.plus(thatOpposite); } return Minus.newInstance(this, that); } /** * Returns the product of this function with the one specified. * * @param that the function multiplier. * @return this · that. */ public Function times(Function that) { return Times.newInstance(this, that); } /** * Returns the quotient of this function with the one specified. * Evaluation of this function may raise an exception if the * function result is not a { * * @param that the function divisor. * @return this / that. */ @SuppressWarnings("unchecked") public Function divide(Function that) { if (that instanceof GroupMultiplicative) { Function thatInverse = (Function) ((GroupMultiplicative) that) .inverse(); return this.times(thatInverse); } return Divide.newInstance(this, that); } /** * Returns this function raised at the specified exponent. * * @param n the exponent. * @return thisn * @throws IllegalArgumentException if n <= 0 */ public Function pow(int n) { if (n <= 0) throw new IllegalArgumentException("n: " + n + " zero or negative values not allowed"); Function pow2 = this; Function result = null; while (n >= 1) { // Iteration. if ((n & 1) == 1) { result = (result == null) ? pow2 : result.times(pow2); } pow2 = pow2.times(pow2); n >>>= 1; } return result; } /** * Returns the textual representation of this real-time object * (equivalent to toString except that the returned value * can be allocated from the local context space). * * @return this object's textual representation. */ public abstract Text toText(); /** * Returns the text representation of this function as a * java.lang.String. * * @return toText().toString() */ public final String toString() { return toText().toString(); } // Merges the variable from the specified function into a single table. @SuppressWarnings("unchecked") static final List merge(List left, List right) { if (left.containsAll(right)) return left; if (right.containsAll(left)) return right; FastList result = FastList.newInstance(); Iterator iLeft = left.iterator(); Iterator iRight = right.iterator(); Variable l = null; Variable r = null; while (true) { if (!iLeft.hasNext()) { while (iRight.hasNext()) { result.add(iRight.next()); } return result; } if (!iRight.hasNext()) { while (iLeft.hasNext()) { result.add(iLeft.next()); } return result; } l = (l == null) ? (Variable) iLeft.next() : l; r = (r == null) ? (Variable) iRight.next() : r; if (l == r) { result.add(l); l = null; r = null; continue; } int comp = l.getSymbol().compareTo(r.getSymbol()); if (comp < 0) { result.add(l); l = null; continue; } if (comp > 0) { result.add(r); r = null; continue; } throw new FunctionException("Duplicate symbol " + l.getSymbol()); } } // Function composition (default implementation). private static final class Compose extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Compose create() { return new Compose(); } protected void cleanup(Compose compose) { compose._f = null; compose._g = null; } }; private Function _f; private Function _g; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Function g) { Compose compose = FACTORY.object(); compose._f = f; compose._g = g; return compose; } @Override public List getVariables() { return _g.getVariables(); } @Override @SuppressWarnings("unchecked") public Object evaluate() { return evaluate(_g.evaluate()); } @SuppressWarnings("unchecked") public Function differentiate(Variable v) { // Chain rule: http://en.wikipedia.org/wiki/Chain_rule Function fd = _f.differentiate(v); Function gd = _g.differentiate(v); return fd.compose(_g).times(gd); } public Text toText() { return TextBuilder.newInstance().append('(').append(_f).append(')') .append('o').append('(').append(_g).append(')').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Compose)) return false; Compose that = (Compose) obj; return this._f.equals(that._f) && this._g.equals(that._g); } @Override public int hashCode() { return _f.hashCode() + _g.hashCode(); } private static final long serialVersionUID = 1L; } // Function derivative (default implementation). private static final class Derivative extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Derivative create() { return new Derivative(); } protected void cleanup(Derivative derivative) { derivative._f = null; derivative._v = null; } }; private Function _f; private Variable _v; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Variable v) { Derivative derivative = FACTORY.object(); derivative._f = f; derivative._v = v; return derivative; } @Override public List getVariables() { return _f.getVariables(); } @Override public Object evaluate() { throw new FunctionException("Derivative of " + _f + " undefined"); } public Text toText() { return TextBuilder.newInstance().append("d[").append(_f).append("]/d") .append(_v.getSymbol()).toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Derivative)) return false; Derivative that = (Derivative) obj; return this._f.equals(that._f) && this._v.equals(that._v); } @Override public int hashCode() { return _f.hashCode() + _v.hashCode(); } private static final long serialVersionUID = 1L; } // Function integral (default implementation). private static final class Integral extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Integral create() { return new Integral(); } protected void cleanup(Integral integral) { integral._f = null; integral._v = null; } }; private Function _f; private Variable _v; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Variable v) { Integral integral = FACTORY.object(); integral._f = f; integral._v = v; return integral; } @Override public List getVariables() { return _f.getVariables(); } @Override public Object evaluate() { throw new FunctionException("Integral of " + _f + " undefined"); } public Text toText() { return TextBuilder.newInstance().append("S[").append(_f).append("·d") .append(_v.getSymbol()).append(']').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Integral)) return false; Integral that = (Integral) obj; return this._f.equals(that._f) && this._v.equals(that._v); } @Override public int hashCode() { return _f.hashCode() + _v.hashCode(); } private static final long serialVersionUID = 1L; } // Function addition (default implementation). private static final class Plus extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Plus create() { return new Plus(); } protected void cleanup(Plus plus) { plus._f = null; plus._g = null; } }; private Function _f, _g; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Function g) { Plus plus = FACTORY.object(); plus._f = f; plus._g = g; return plus; } @Override public List getVariables() { return merge(_f.getVariables(), _g.getVariables()); } @SuppressWarnings("unchecked") @Override public Object evaluate() { Object y2 = _g.evaluate(); Object y1 = _f.evaluate(); if (!(y1 instanceof GroupAdditive)) throw new FunctionException(y1.getClass() + " is not an additive group"); return ((GroupAdditive) y1).plus(y2); } @SuppressWarnings("unchecked") @Override public Function differentiate(Variable v) { return _f.differentiate(v).plus(_g.differentiate(v)); } @SuppressWarnings("unchecked") @Override public Function integrate(Variable v) { return _f.integrate(v).plus(_g.integrate(v)); } public Text toText() { return TextBuilder.newInstance().append('(').append(_f).append(")") .append('+').append('(').append(_g).append(')').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Plus)) return false; Plus that = (Plus) obj; return this._f.equals(that._f) && this._g.equals(that._g); } @Override public int hashCode() { return _f.hashCode() + _g.hashCode(); } private static final long serialVersionUID = 1L; } // Function addition (default implementation). private static final class Minus extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Minus create() { return new Minus(); } protected void cleanup(Minus minus) { minus._f = null; minus._g = null; } }; private Function _f, _g; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Function g) { Minus minus = FACTORY.object(); minus._f = f; minus._g = g; return minus; } @Override public List getVariables() { return merge(_f.getVariables(), _g.getVariables()); } @SuppressWarnings("unchecked") @Override public Object evaluate() { Object y2 = _g.evaluate(); if (!(y2 instanceof GroupAdditive)) throw new FunctionException(y2.getClass() + " is not an additive group"); y2 = ((GroupAdditive) y2).opposite(); Object y1 = _f.evaluate(); if (!(y1 instanceof GroupAdditive)) throw new FunctionException(y1.getClass() + " is not an additive group"); return ((GroupAdditive) y1).plus(y2); } @SuppressWarnings("unchecked") @Override public Function differentiate(Variable v) { return _f.differentiate(v).minus(_g.differentiate(v)); } @SuppressWarnings("unchecked") @Override public Function integrate(Variable v) { return _f.integrate(v).minus(_g.integrate(v)); } public Text toText() { return TextBuilder.newInstance().append('(').append(_f).append(")") .append('-').append('(').append(_g).append(')').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Minus)) return false; Minus that = (Minus) obj; return this._f.equals(that._f) && this._g.equals(that._g); } @Override public int hashCode() { return _f.hashCode() + _g.hashCode(); } private static final long serialVersionUID = 1L; } // Function multiplication (default implementation). private static final class Times extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Times create() { return new Times(); } protected void cleanup(Times times) { times._f = null; times._g = null; } }; private Function _f, _g; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Function g) { Times times = FACTORY.object(); times._f = f; times._g = g; return times; } @Override public List getVariables() { return merge(_f.getVariables(), _g.getVariables()); } @SuppressWarnings("unchecked") @Override public Object evaluate() { Object y2 = _g.evaluate(); Object y1 = _f.evaluate(); if (!(y1 instanceof GroupMultiplicative)) throw new FunctionException(y1.getClass() + " is not a multiplicative group"); return ((GroupMultiplicative) y1).times(y2); } @SuppressWarnings("unchecked") @Override public Function differentiate(Variable v) { // Product rule: http://en.wikipedia.org/wiki/Product_rule // (support for non-commutative multiplications). // r' = d(f·g) = f'g + fg' Function fd = _f.differentiate(v); Function gd = _g.differentiate(v); return fd.times(_g).plus(_f.times(gd)); } public Text toText() { return TextBuilder.newInstance().append('(').append(_f).append(")") .append('·').append('(').append(_g).append(')').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Times)) return false; Times that = (Times) obj; return this._f.equals(that._f) && this._g.equals(that._g); } @Override public int hashCode() { return _f.hashCode() + _g.hashCode(); } private static final long serialVersionUID = 1L; } // Function multiplication (default implementation). private static final class Divide extends Function { private static final ObjectFactory FACTORY = new ObjectFactory() { protected Divide create() { return new Divide(); } protected void cleanup(Divide divide) { divide._f = null; divide._g = null; } }; private Function _f, _g; @SuppressWarnings("unchecked") public static Function newInstance(Function f, Function g) { Divide divide = FACTORY.object(); divide._f = f; divide._g = g; return divide; } @Override public List getVariables() { return merge(_f.getVariables(), _g.getVariables()); } @SuppressWarnings("unchecked") @Override public Object evaluate() { Object y2 = _g.evaluate(); if (!(y2 instanceof GroupMultiplicative)) throw new FunctionException(y2.getClass() + " is not a multiplicative group"); y2 = ((GroupMultiplicative) y2).inverse(); Object y1 = _f.evaluate(); if (!(y1 instanceof GroupMultiplicative)) throw new FunctionException(y1.getClass() + " is not a multiplicative group"); return ((GroupMultiplicative) y1).times(y2); } @SuppressWarnings("unchecked") @Override public Function differentiate(Variable v) { // Quotient rule: http://en.wikipedia.org/wiki/Quotient_rule // with support for non-commutative multiplications. // r = f/g, rg = f, r'g + rg' = f' (produt rule) // r' = (f' - rg')/g, r' = (f' - (f/g)g')/g Function fd = _f.differentiate(v); Function gd = _g.differentiate(v); return fd.minus(_f.divide(_g).times(gd)).divide(_g); } public Text toText() { return TextBuilder.newInstance().append('(').append(_f).append(")") .append('/').append('(').append(_g).append(')').toText(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Divide)) return false; Divide that = (Divide) obj; return this._f.equals(that._f) && this._g.equals(that._g); } @Override public int hashCode() { return _f.hashCode() + _g.hashCode(); } private static final long serialVersionUID = 1L; } }

Ads help maintain this website.