简体   繁体   中英

Writing a template for varying functions with shifted arguments

I hope this question (and possible answers to it) is general enough in scope such that it will also be useful for others.

I am trying to solve a numerical problem that involves a product of double functions , in the form

where is a predefined function, and i pass to an integrator.

The complication is that my functions are not static; the integration is done repeatedly, and at each integration the s have a different form, eg, in the first loop it could be

the second it becomes

and so on, where the denote members of a vector of functions. At each iteration of the integration i do not know a priori what the form of will be, only that it will be a linear combination of s with constant coefficients.

Since which i pass to the integrator involves with "shifted" arguments, at each iteration i need access to each of the since i need to call something of the form

double Y (double x, double y, double z){

  return (f(x+y) - f(x)) * (f(y+z) - f(y)) * A(z+y);

}

for the integrator (or maybe not?).

My question is, what would be the best way to represent my ? I have been reading up on the obvious candidates (templates, std::function), but i am not sure how i can "impose" the shifted arguments onto the function.

I am sure that in another incarnation this is a fairly typical programming design problem. Can anyone give me some suggestions on how to approach it?

Edit : Thanks to Rostislav and especially Gombat, who came up with the original solution (although Rostislav's is quite elegant). Before closing this question, maybe i'll append a further question here, ie, consider the (more general) case where one does not have such detailed information about the output f(x) (that it is a linear combination of f_i(x) with constant coefficients) at each iteration, and instead we only know that f(x) = F(x) , where F(x) is a double function which is again composed of the f_i(x) , but now in an arbitrary manner. All other conditions being the same (start with known f_0(x) ), how should i represent my F(x) in this case?

I was thinking that since we know the functional dependence of the solution F(x) (it depends only on one argument x , is a double , and is composed of known f_i(x) ), maybe it is possible to write a "template" (apologies for the abuse of nomenclature, it is very suggestive) function F(x) that i can pass to the integrator, which then "unwraps" the actual form of the function to be integrated, although this could be complicated.

Given that you are integrating Y , it is likely to be called numerous times during the integration, so it is better to avoid using std::function as the compilers basically cannot inline the calls to std::function due to type erasure that std::function uses internally.

Instead, you can use the lambdas directly. If you have control over the implementation of the integration procedure (or it already takes the type of function being integrated as a template parameter), then you can avoid all std::function usages. Otherwise, it would make sense to use std::function only for your communication with the integration procedure, but avoid them where possible. Below is the example code implementing it (live example here ):

#include <iostream>
#include <string>

#include <tuple>
#include <functional>

template<typename... Fn>
class LinearCombination {
public:
    template<typename... Un>
    LinearCombination(Un&&... fs)
        : functions(std::forward<Un>(fs)...)
    {
        coefs.fill(0.0);
    }

    double operator()(double x) const
    {
        return evaluateImpl(x, std::integral_constant<size_t, sizeof...(Fn)-1>());
    }

    void setCoef(size_t i, double c)
    {
        coefs[i] = c;
    }

private:
    template<size_t I>
    double evaluateImpl(double x, std::integral_constant<size_t, I> index) const
    {
        return evaluateOne(x, index) + evaluateImpl<I - 1>(x, std::integral_constant<size_t, I - 1>());
    }

    template<size_t I>
    double evaluateImpl(double x, std::integral_constant<size_t, 0> index) const
    {
        return evaluateOne(x, index);
    }

    template<size_t I>
    double evaluateOne(double x, std::integral_constant<size_t, I>) const
    {
        auto coef = coefs[I];
        return coef == 0.0 ? 0.0 : coef * std::get<I>(functions)(x);
    }


    std::tuple<Fn...> functions;
    std::array<double, sizeof...(Fn)> coefs;
};

// This helper function is there just to avoid writing something like...
// LinearCombination<decltype(f1), decltype(f2)> when you use lambdas f1 and f2
template<typename... Fn>
auto make_linear_combination(Fn&&... fn)
{
    return LinearCombination<Fn...>{std::forward<Fn>(fn)...};
}

double A(double)
{
    return 1.0;
}

/// Integration 1.0
double integrate3D(double(*f)(double, double, double))
{
    return f(1, 2, 3);
}

struct YHelper {
    static double Y(double x, double y, double z)
    {
        return (f(x + y) - f(x)) * (f(y + z) - f(y)) * A(z + y);
    }

    static std::function<double(double)> f;
};

std::function<double(double)> YHelper::f;


/// Integration 2.0
template<typename Integrable>
double integrate3D_2(Integrable&& f)
{
    return f(1, 2, 3);
}

int main()
{
    auto f1 = [](double x) { return x; };
    auto f2 = [](double x) { return 2 * x; };

    auto lc = make_linear_combination(std::move(f1), std::move(f2));
    lc.setCoef(0, 1.0);
    lc.setCoef(1, -1.0);

    std::cout << lc(2.0) << "\n";

    YHelper::f = std::ref(lc);
    std::cout << integrate3D(&YHelper::Y) << "\n";

    auto Y = [&lc](double x, double y, double z) { return (lc(x + y) - lc(x)) * (lc(y + z) - lc(y)) * A(z + y); };
    std::cout << integrate3D_2(Y) << "\n";
}

Note, that under Integration 1.0 is an example implementation for the case where you have no control over the signature of the integration procedure. The code under Integration 2.0 is not only cleaner, but will perform better as well.

PS: Of course, be careful when comparing coef to 0.0 in evaluateOne - assume it will only work when you set the coefficient directly to literal 0.0 and not a result of any computation. Otherwise, use abs(coef) < epsilon with an epsilon value that fits your application.

Based on the comments, I hope the following will help:

class MyF
{
public:
  // typedef as abbreviation
  typedef std::function<double(const double)> func;

  MyF( const std::vector<func>& functions )
   : m_functions(functions)
#ifdef _DEBUG
   , m_lcUpdated(false)
#endif
  { }

  // f combines the inner functions depending on m_linearCombination
  double operator()(const double x)
  {
    assert( m_lcUpdated );
    assert( m_linearCombination.size() > 0 );
    assert( m_linearCombination.size() <= m_functions.size() );

    double ret(0.0);
    // if m_linearCombination has a value only for the first function
    // no need to run the loop for all functions, so we iterate only
    // over the linearCombination entries.
    for ( size_t i = 0; i < m_linearCombination.size(); i++ )
    {
      // if the factor is very small, no need for m_function call
      if ( std::abs(m_linearCombination) < DBL_EPSILON ) continue;

      ret += m_linearCombination[i] * m_functions[i](x);
    }

    m_lcUpdated = false;
    return ret;
  }

  void setLinearCombination(const std::vector<double>& lc)
  { 
      m_linearCombination = lc; 
#ifdef _DEBUG
      m_lcUpdated = true; 
#endif
  }


private:
  std::vector<double> m_linearCombination;
  std::vector<func> m_functions;
#ifdef _DEBUG
  bool m_lcUpdated;
#endif
};

The constructor of MyF gets the functions f_i(x) in the first iteration (or before? You could also construct MyF before without arguments and write a set methods to set the vector of std::function s). The private member m_lcUpdated checks, whether you've updated the linear combination before calling the function with operator() .

std::vector<MyF::func> functions;

// ...fill the functions

// construct your function class
MyF F(functions);
// and the variable to fill in each iteration on how to linear combine the inner functions
std::vector<double> linearCombination;

// Provide the first linear combination (fill it before of course)
F.setLinearCombination(linearCombination);

// In each iteration, call the method combining inner functions
F(x); // it works like this, since it is the operator() which is overloaded for const double as input
// And provide the linear combination for the next iteration
F.setLinearCombination( linearCombination );

It would also be possible to have the linearCombinations as a std::map<size_t,double> where first is the index, so you don't have to iterate over elements whose value is possibly zero, if it is possible that a linear combination is eg f_0(x) + f_2(x) .

Edit : I implemented @Gombat's solution, as it was more intuitive, but definitely also see Rostislav's answer below.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM