#ifndef SimTK_SimTKCOMMON_FUNCTION_H_ #define SimTK_SimTKCOMMON_FUNCTION_H_ /* -------------------------------------------------------------------------- * * Simbody(tm): SimTKcommon * * -------------------------------------------------------------------------- * * This is part of the SimTK biosimulation toolkit originating from * * Simbios, the NIH National Center for Physics-Based Simulation of * * Biological Structures at Stanford, funded under the NIH Roadmap for * * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. * * * * Portions copyright (c) 2008-12 Stanford University and the Authors. * * Authors: Peter Eastman * * Contributors: Michael Sherman * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may * * not use this file except in compliance with the License. You may obtain a * * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * -------------------------------------------------------------------------- */ // Note: this file was moved from Simmath to SimTKcommon 20100601; see the // Simmath repository for earlier history. //#include "SimTKcommon/basics.h" //#include "SimTKcommon/Simmatrix.h" /* Update: This is a port of the original code so that it will work with the multibody code RBDL written by Martin Felis. Author: Matthew Millard Date: Nov 2015 */ #include #include #include #include #include /** This abstract class represents a mathematical function that calculates a value of arbitrary type based on M real arguments. The output type is set as a template argument, while the number of input components may be determined at runtime. The name "Function" (with no trailing _) may be used as a synonym for Function_. Subclasses define particular mathematical functions. Predefined subclasses are provided for several common function types: Function_::Constant, Function_::Linear, Function_::Polynomial, and Function_::Step. You can define your own subclasses for other function types. The Spline_ class also provides a convenient way to create various types of Functions. */ namespace RigidBodyDynamics { namespace Addons { namespace Geometry{ template class Function_ { public: class Constant; class Linear; class Polynomial; class Sinusoid; class Step; virtual ~Function_() { } /** Calculate the value of this function at a particular point. @param x the RigidBodyDynamics::Math::VectorNd of input arguments. Its size must equal the value returned by getArgumentSize(). */ virtual T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const = 0; /** Calculate a partial derivative of this function at a particular point. Which derivative to take is specified by listing the input components with which to take it. For example, if derivComponents=={0}, that indicates a first derivative with respective to component 0. If derivComponents=={0, 0, 0}, that indicates a third derivative with respective to component 0. If derivComponents=={4, 7}, that indicates a partial second derivative with respect to components 4 and 7. @param derivComponents The input components with respect to which the derivative should be taken. Its size must be less than or equal to the value returned by getMaxDerivativeOrder(). @param x The RigidBodyDynamics::Math::VectorNd of input arguments. Its size must equal the value returned by getArgumentSize(). @return The value of the selected derivative, which is of type T. */ virtual T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const = 0; /** This provides compatibility with std::vector without requiring any copying. **/ /* T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(std::vector(derivComponents),x); } */ /** * Get the number of components expected in the input vector. */ virtual int getArgumentSize() const = 0; /** * Get the maximum derivative order this Function_ object can calculate. */ virtual int getMaxDerivativeOrder() const = 0; }; /** This typedef is used for the very common case that the return type of the Function object is double. **/ typedef Function_ Function; /** * This is a Function_ subclass which simply returns a fixed value, independent * of its arguments. */ template class Function_::Constant : public Function_ { public: /** * Create a Function_::Constant object. * * @param value the value which should be returned by calcValue(); * @param argumentSize the value which should be returned by * getArgumentSize(), with a default of 1. */ explicit Constant(T value, int argumentSize=1) : argumentSize(argumentSize), value(value) { } T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { assert(x.size() == argumentSize); return value; } T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return static_cast(0); } virtual int getArgumentSize() const { return argumentSize; } int getMaxDerivativeOrder() const { return std::numeric_limits::max(); } /** This provides compatibility with std::vector without requiring any copying. **/ /* T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(std::vector(derivComponents),x); } */ private: const int argumentSize; const T value; }; /** * This is a Function_ subclass whose output value is a linear function of its * arguments: f(x, y, ...) = ax+by+...+c. */ template class Function_::Linear : public Function_ { public: /** * Create a Function_::Linear object. * * @param coefficients * The coefficients of the linear function. The number of arguments * expected by the function is equal to coefficients.size()-1. * coefficients[0] is the coefficient for the first argument, * coefficients[1] is the coefficient for the second argument, etc. * The final element of coefficients contains the constant term. */ explicit Linear( const RigidBodyDynamics::Math::VectorNd& coefficients) : coefficients(coefficients) { } T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { assert(x.size() == coefficients.size()-1); T value = static_cast(0); for (int i = 0; i < x.size(); ++i) value += x[i]*coefficients[i]; value += coefficients[x.size()]; return value; } T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { assert(x.size() == coefficients.size()-1); assert(derivComponents.size() > 0); if (derivComponents.size() == 1) return coefficients(derivComponents[0]); return static_cast(0); } virtual int getArgumentSize() const { return coefficients.size()-1; } int getMaxDerivativeOrder() const { return std::numeric_limits::max(); } /** This provides compatibility with std::vector without requiring any copying. **/ /* T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(ArrayViewConst_(derivComponents),x); } */ private: const RigidBodyDynamics::Math::VectorNd coefficients; }; /** * This is a Function_ subclass whose output value is a polynomial of its * argument: f(x) = ax^n+bx^(n-1)+...+c. */ template class Function_::Polynomial : public Function_ { public: /** * Create a Function_::Polynomial object. * * @param coefficients the polynomial coefficients in order of decreasing * powers */ Polynomial(const RigidBodyDynamics::Math::VectorNd& coefficients) : coefficients(coefficients) { } T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { assert(x.size() == 1); double arg = x[0]; T value = static_cast(0); for (int i = 0; i < coefficients.size(); ++i) value = value*arg + coefficients[i]; return value; } T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { assert(x.size() == 1); assert(derivComponents.size() > 0); double arg = x[0]; T value = static_cast(0); const int derivOrder = (int)derivComponents.size(); const int polyOrder = coefficients.size()-1; for (int i = 0; i <= polyOrder-derivOrder; ++i) { T coeff = coefficients[i]; for (int j = 0; j < derivOrder; ++j) coeff *= polyOrder-i-j; value = value*arg + coeff; } return value; } virtual int getArgumentSize() const { return 1; } int getMaxDerivativeOrder() const { return std::numeric_limits::max(); } /** This provides compatibility with std::vector without requiring any copying. **/ /* T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(ArrayViewConst_(derivComponents),x); } */ private: const RigidBodyDynamics::Math::VectorNd coefficients; }; /** * This is a Function_ subclass whose output value is a sinusoid of its * argument: f(x) = a*sin(w*x + p) where a is amplitude, w is frequency * in radians per unit of x, p is phase in radians. * * This is only defined for a scalar (double) return value. */ template <> class Function_::Sinusoid : public Function_ { public: /** * Create a Function::Sinusoid object, returning a*sin(w*x+p). * * @param[in] amplitude 'a' in the above formula * @param[in] frequency 'w' in the above formula * @param[in] phase 'p' in the above formula */ Sinusoid(double amplitude, double frequency, double phase=0) : a(amplitude), w(frequency), p(phase) {} void setAmplitude(double amplitude) {a=amplitude;} void setFrequency(double frequency) {w=frequency;} void setPhase (double phase) {p=phase;} double getAmplitude() const {return a;} double getFrequency() const {return w;} double getPhase () const {return p;} // Implementation of Function_ virtuals. virtual double calcValue( const RigidBodyDynamics::Math::VectorNd& x) const { const double t = x[0]; // we expect just one argument return a*std::sin(w*t + p); } virtual double calcDerivative( const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { const double t = x[0]; // time is the only argument const int order = derivComponents.size(); // The n'th derivative is // sign * a * w^n * sc // where sign is -1 if floor(order/2) is odd, else 1 // and sc is cos(w*t+p) if order is odd, else sin(w*t+p) switch(order) { case 0: return a* std::sin(w*t + p); case 1: return a*w* std::cos(w*t + p); case 2: return -a*w*w* std::sin(w*t + p); case 3: return -a*w*w*w*std::cos(w*t + p); default: const double sign = double(((order/2) & 0x1) ? -1 : 1); const double sc = (order & 0x1) ? std::cos(w*t+p) : std::sin(w*t+p); const double wn = std::pow(w, order); return sign*a*wn*sc; } } virtual int getArgumentSize() const {return 1;} // just time virtual int getMaxDerivativeOrder() const { return std::numeric_limits::max(); } /** This provides compatibility with std::vector without requiring any copying. **/ /* double calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(ArrayViewConst_(derivComponents),x); } */ private: double a, w, p; }; /** * This is a Function_ subclass whose output value y=f(x) is smoothly stepped * from y=y0 to y1 as its input argument goes from x=x0 to x1. This is * an S-shaped function with first and second derivatives y'(x0)=y'(x1)=0 * and y''(x0)=y''(x1)==0. The third derivative y''' exists and is continuous * but we cannot guarantee anything about it at the end points. */ template class Function_::Step : public Function_ { public: /** * Create a Function_::Step object that smoothly interpolates its output * through a given range as its input moves through its range. * * @param y0 Output value when (x-x0)*sign(x1-x0) <= 0. * @param y1 Output value when (x-x1)*sign(x1-x0) >= 0. * @param x0 Start of switching interval. * @param x1 End of switching interval. * * @tparam T The template type is the type of y0 and y1. This must * be a type that supports subtraction and scalar * multiplication by a double so that we can compute * an expression like y=y0 + f*(y1-y0) for some double scalar f. * * Note that the numerical values of x0 and x1 can be in either order * x0 < x1 or x0 > x1. */ Step(const T& y0, const T& y1, double x0, double x1) : m_y0(y0), m_y1(y1), m_yr(y1-y0), m_zero(double(0)*y0), m_x0(x0), m_x1(x1), m_ooxr(1/(x1-x0)), m_sign(copysign(1,m_ooxr)) { /* SimTK_ERRCHK1_ALWAYS(x0 != x1, "Function_::Step::ctor()", "A zero-length switching interval is illegal; both ends were %g.", x0); */ assert(x0 != x1); std::printf( "Function_::Step::ctor():" "A zero-length switching interval " "is illegal; both ends were %g.", x0); } T calcValue(const RigidBodyDynamics::Math::VectorNd& xin) const { /* SimTK_ERRCHK1_ALWAYS(xin.size() == 1, "Function_::Step::calcValue()", "Expected just one input argument but got %d.", xin.size()); */ assert(xin.size() == 1); std::printf( "Function_::Step::calcValue() " "Expected just one input argument but got %d.", xin.size()); const double x = xin[0]; if ((x-m_x0)*m_sign <= 0) return m_y0; if ((x-m_x1)*m_sign >= 0) return m_y1; // f goes from 0 to 1 as x goes from x0 to x1. const double f = step01(m_x0,m_ooxr, x); return m_y0 + f*m_yr; } T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& xin) const { /* SimTK_ERRCHK1_ALWAYS(xin.size() == 1, "Function_::Step::calcDerivative()", "Expected just one input argument but got %d.", xin.size()); */ assert(xin.size() == 1); std::printf( "Function_::Step::calcDerivative() " "Expected just one input argument but got %d.", xin.size()); const int derivOrder = (int)derivComponents.size(); /* SimTK_ERRCHK1_ALWAYS(1 <= derivOrder && derivOrder <= 3, "Function_::Step::calcDerivative()", "Only 1st, 2nd, and 3rd derivatives of the step are available," " but derivative %d was requested.", derivOrder); */ assert(1 <= derivOrder && derivOrder <= 3); std::printf("Function_::Step::calcDerivative() " "Only 1st, 2nd, and 3rd derivatives of the step are available," " but derivative %d was requested.", derivOrder); const double x = xin[0]; if ((x-m_x0)*m_sign <= 0) return m_zero; if ((x-m_x1)*m_sign >= 0) return m_zero; switch(derivOrder) { case 1: return dstep01(m_x0,m_ooxr, x) * m_yr; case 2: return d2step01(m_x0,m_ooxr, x) * m_yr; case 3: return d3step01(m_x0,m_ooxr, x) * m_yr; default: assert(!"impossible derivOrder"); } return NAN*m_yr; /*NOTREACHED*/ } virtual int getArgumentSize() const {return 1;} int getMaxDerivativeOrder() const {return 3;} /** This provides compatibility with std::vector without requiring any copying. **/ /* T calcDerivative(const std::vector& derivComponents, const RigidBodyDynamics::Math::VectorNd& x) const { return calcDerivative(ArrayViewConst_(derivComponents),x); } */ private: const T m_y0, m_y1, m_yr; // precalculate yr=(y1-y0) const T m_zero; // precalculate T(0) const double m_x0, m_x1, m_ooxr; // precalculate ooxr=1/(x1-x0) const double m_sign; // sign(x1-x0) is 1 or -1 double step01(double x0, double x1, double x){ double u = (x-x0)/(x1-x0); double u2 = u*u; double u3 = u2*u; return (3*u2 - 2*u3); } double dstep01(double x0, double x1, double x){ double u = (x-x0)/(x1-x0); double du = (1)/(x1-x0); double du2 = 2*u*du; double du3 = 3*u*u*du; return (3*du2 - 2*du3); } double d2step01(double x0, double x1, double x){ double u = (x-x0)/(x1-x0); double du = (1)/(x1-x0); //double ddu = 0; double ddu2 = 2*du*du;// + 2*u*ddu since ddu=0; double ddu3 = 3*du*u*du + 3*u*du*du;// + 3*u*u*du; ditto return (3*ddu2 - 2*ddu3); } double d3step01(double x0, double x1, double x){ double u = (x-x0)/(x1-x0); double du = (1)/(x1-x0); //double ddu = 0; //double dddu = 0; double dddu2 = 0; //2*du*du;// since ddu=0; double dddu3 = 3*du*du*du + 3*du*du*du;// ditto return (3*dddu2 - 2*dddu3); } }; } } } #endif // SimTK_SimTKCOMMON_FUNCTION_H_