Conventions
- the examples below assume one specifies the directive:
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:
- 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.
#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.
#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
|