Funct Package
Provides symbolic description of function and their manipulation capabilities. Symbolic calculation includes simplification, factorization, n-th order derivatives, primitive function. User can specify to adopt numerical integration for functions where a symbolic integration can't be performed.

Contents

Conventions
  • the examples below assume one specifies the directive:
    using namespace Funct;
    otherwise the namespace Funct should be specified where appropriate.
Basic concepts
  • Functions (or functors)
    • a function, or better functor, is a C++ class (or struct) that implements the operator "()", returning a double value on the basis of arguments passed by the user. The arguments represent the variable values the function is computed on.
    • Examples are:
    • struct DumpedOscillator {
      double operator()( double x )
      { return exp( - x ) * sin( x ); }
      };

      struct SinPower {
      double operator()( double x, int n )
      { return pow( sin( x ), n ); }
      };
    • Expressions
      • an expression implements the operator "()", returning a double, but with no argument. It is somewhat similar to a function, but the variables are not provided as arguments. 
      • A simpe example is:
    struct Period {
    Period( double omega ) : _period( 2 * M_PI / omega ) { }
    double operator()() { return _period; }
    private:
    double _period;
    };
    Constants, Parameters and Variables
    • Constants (defined in Funct/Constant.h)
      • a constant is a simple expression that returns a double value based on what has been passed to its constructor. The value can't be modified after a constant's instantiation.
      • Example:
      Constant pi( 3.1415926535898 );
      cout << pi() << endl;
    • Parameters (defined in Funct/Parameter.h)
      • a parameter is a simple expression that returns a double value based on the initializer passed at construction time. The value can be modified after a constant's instantiation. Copying a parameter results in the copied parameter always having the same value as the original parameter.
      • Example:
    Parameter a( 123.456 );
    Parameter a_copy( a );
    a = 10.01; // a_copy returns now 10.01 too
    assert( a() == a_copy() ); // a_copy returns 10.01 too
    • Variables (defined in Funct/Variables.h)
      • Variables are used in symbolic calculation of expressions. Different variables must correspond to different C++ types in order to let the symbolic calculation engine distinguish them.
      • Variables are classes that behave like expressions returning a double value. The value is common to all objects of the class. Three variable types are provided: X, Y and Z. Users can specify more variable types (see below).
      • Example:
    X x;
    x = 15, // or equivalently X::set( 15 );
    cout << x() << endl;
      • To define more variable types the macos DEFINE_VARIABLE, DEFINE_VARIABLE_INT and DEFINE_VARIABLE_T are provided for double, int and generic variables respectively. Implementation code should contain the corresponding IMPLEMENT_XXX macro:
    // in a user header file
    DEFINE_VARIABLE( X1, "x1" ) // define a new printed as x1

    // in a user implementation file
    IMPLEMENT_VARIABLE( X1 )
    Numerical and fractional expressions
    • Numerical expressions are expression that return always the same integer value. The value is specified as template parameter. A templated function returning a numerical expression of a given value is provided. The header are Funct/Numerical.h, Fraction.h.
    Numerical<1> one;
    cout << one() << endl; // prints "1"
    Numerical<1> one_1 = num<1>();
    Numerical<1> one_2 = num(1); // same as above, using a cpp macro
    • Fractional expressions are expression that return always the same value.
    Fraction<1, 2>::type half;
    cout << half<< " = " << half() << endl; // prints "1/2 = 0.5"
    Fraction<1, 2>::type = half_1 = fract<1, 2>();
    Fraction<1, 2>::type = half_2 = fract(1, 2); // same as above
    • Fractional expressions are simplified when possible. For instance, Fraction<4, 2>::type is evaluated the same type as Numerical<2> by the compiler, and Fraction<2, 6>::type will be  evaluated as Fraction<1, 6>::type.

    Symbolic manupulation of expressions
    • A symbolic expression is a C++ type obtained combining variables, constants and/or parameters with arithmetic operators like sum, product, etc. and analytic functions, like sin, exp, etc. Given that each expression produces a new C++ type, handling those types directly would be rather inconvenient, though feasible. For instance, the following type would represent sin2x + cos2x:
      typedef Sum< Square< Sin< X >::type >::type, 
      Square< Cos< X >::type >::type Exp;
    • The expression would be automatically reduce to the type Numerical<1> by the symbolic manipulation engine. Most of the symbolic computation and simplification is performed at compile time thanks to template metaprogramming, so there is no computational overhead when evaluating expressions.
    • The definition of symbolic manipulations is contained in the header Funct/FunctManip.h which also includes the header files mentioned in the sections above. The header Funct/FunctIO.h provides printout functionality. 
    • The supported operations and functions in the current version are sum (+), difference (-), product (*), ratio (/), power (pow function), sin, cos, tan, log, exp, abs and sgn (sign function).
    • An example of code using symbolic expression is the following:
    #include "FunctManip.h"
    #include "FunctIO.h"

    X x;
    Y y;
    Product< Sin< X >::type,
    Cos< Y >::type >::type p( sin( x ) + cos( y ) );
    x = M_PI / 2;
    y = 0;
    cout << p << " = " << p() << endl;
    The above example would display something like:
    sin( x ) + cos( y ) = 2
    • Derivatives
      • Symbolic (partial) derivatives and nth-order derivatives can be computed. The type returned is Derivative<X, E>::type, where E is the expression to be derived w.r.t. the variable type X.
      • Example:
    X x;
    cout << derivative<X>( sin(x) * cos(x) ) << endl;
    cout << nth_derivative<2, X>( sin(x) * cos(x) ) << endl;
    cout << nth_derivative<3, X>( sin(x) * cos(x) ) << endl;
    cout << nth_derivative<4, X>( sin(x) * cos(x) ) << endl;
    The above example would display something like:
    cos(x)^2 - sin(x)^2
    -4 sin(x) cos(x)
    -4 ( cos(x)^2 - sin(x)^2 )
    16 sin(x) cos(x)
    • Primitive functions
      • Primitive functions can be computed symbolically when possible. The type returned is Primitive<X, E>::type, where E is the expression to be integrated w.r.t. the variable type X.
      • Example: 
    X x;
    Y y;
    cout << primitive<X>( pow( x, num(2) ) * exp( x ) ) << endl;
    cout << primitive<Y>(
    primitive< X > (
    y / pow( sin( x ), num(2) ) ) )<< endl;
    The above example would display something like:
    exp(x) ( x^2 - 2 ( x - 1 ) )
    - y^2/( 2 tan(x) )
      • Notice in the example above that when combining numerical expression with function or operations, numerical constants must be used, not int constants. E.g.: ( sin x + cos x ) /2 should be written as( sin(x) + cos(x) )/num(2), not
        ( sin(x) + cos(x) )/2. The latter expression will not compile.
      • Primitive functions not always can be computed. If not, the type UndefinedIntegral will be returned. Trying to use such a result is likely to produce compile time errors because the above type has no functionality.
      • Primitive functions can be specified by the user with the cpp macro DECLARE_PRIMITIVE, as in the following example:
    template< typename X > struct MySquare {
      MyExpr( const X& x ) : _( x ) { }
      double operator()() const { return std::pow( _(), 2 ); }
      X _;
    };

    template< typename X > struct MySquarePrimitive {
      MyExprPrimitive( const X& x ) : _( x ) { }
      double operator()() const { return std::pow(_(), 3)/3; }
      X _;
    };

    DECLARE_PRIMITIVE( X, MySquare<X>, MySquarePrimitive<X> )
    Generic expressions and function adaptors
    • Generic expressions (defined in Funct/Expression.h)
      • With growing complexity of expressions handling the types explicitly would become unmanageable. For this reason the struct Expression has been provided. The disadvantage is that an object of the type Expression can't be further manupulated. The performance cost is one call of a virtual function, so shouldn't affect significantly run time performances.
      • Example:
    #include "FunctManip.h"
    #include "FunctIO.h"
    #include "Expression.h"

    X x;
    Y y;
    Expression p( sin(x) + cos(y) );
    x = M_PI / 2;
    y = 0;
    cout << p << " = " << p() << endl;
    • Function adaptors (defined in Funct/Function.h)
      • Expressions are not practical to be handled as functions, since the variable values must be set explicitly. For this reason, adaptors are provided to trasform expressions into functions (or funtors). Function arguments are of the same type as the corresponding variable value types. The current version of the toolkit support functions of up to three variables.
      • Example:
    #include "FunctManip.h"
    #include "FunctIO.h"
    #include "Function.h"

    X x;
    Y y;
    Function<X, Y> f( sin(x) + cos(y) );
    cout << p << " = " << f( M_PI/2, 0 ) << endl;
    Expression integration
    • integration of an expression in a specified range can be performet either analytically (when possible) or numerically (when specified by the user). The header Funct/Integral.h defines the Integral template, and the header Funct/GenericIntegral.h defines its generic implementation.
    • Example:
    template< typename X > struct MyExpr {
      MyExpr( const X& x ) : _( x ) { }
      double operator()() const { return std::pow( _(), 2 ); }
      X _;
    };

    // specify a numerical integration with sampling
    // of 1000 points.
    NUMERICAL_INTEGRAL( X, MyExpr< X >, 1000 )

    cout << "analitic integration: " << endl;
    GenericIntegral<X> i ( pow( x, num(2) ) );
    cout << i << " { 0, 1 } = " << i( 0, 1 ) << endl;

    cout << "numeric integration" << endl;
    MyExpr2<X> e2 ( x );
    GenericIntegral<X> j2 ( e2 );
    cout << j2 << " { 0, 1 } = " << j2( 0, 1 ) << endl;
    Examples