C ++有限差分微分-设计

问题描述

A为:

class A {
  std::vector<double> values_;
public:
  A(const std::vector<double> &values) : values_(values){};

  void bumpAt(std::size_t i,const double &df) {
    values_[i] += df;

  virtual method1();
  virtual method2();
  ...
}

class B : public A {
  overrides methods
  ...
}

为简单起见,请考虑以下功能

double foo(input1,input2,...,const A &a,const B &b,inputK,...) {
 /* do complex stuff */
 return ...;
}

我们要根据其参数来区分foo()。因此,一阶灵敏度d foo/d a是大小为std::vector<double>的{​​{1}}。 a.size()的推理也是如此。

一个简单的实现如下:

d foo/d b

这很好用,但是我们为 // compute d foo/d a std::vector<double> computeDfDa(input1,double da = 1.0){ std::vector<double> dfda = {}; auto aUp = a.copy(); auto aDown = a.copy(); for (auto i = 0; i < a.size(); ++i) { // bump up aUp.bumpAt(i,da); // bump down aDown.bumpAt(i,-da); auto up = foo(input1,aUp,b,...); auto down = foo(input1,aDown,...); auto derivative = (up - down) / 2.0 / da; dfda.pushback(derivative); // revert bumps aUp.bumpAt(i,-da); aDown.bumpAt(i,da); } return dfda; } // compute d foo/d b std::vector<double> computeDfdb(input1,double db = 0.01){ std::vector<double> dfdb = {}; auto bUp = b.copy(); auto bDown = b.copy(); for (auto i = 0; i < a.size(); ++i) { // bump up bUp.bumpAt(i,db); // bump down bDown.bumpAt(i,-db); auto up = foo(input1,a,bUp,bDown,...); auto derivative = (up - down) / 2.0 / db; dfdb.pushback(derivative); // revert bumps bUp.bumpAt(i,-db); bDown.bumpAt(i,db); } return dfdb; } computeDfDa()使用了基本相同的代码

是否有任何设计模式可以使它具有独特的(也许是模板化的)功能,可以自动理解要颠倒的输入?

请注意,输入中computeDfdb()a的位置是不是可交换的。

如果b的复杂度和输入数量大得多,那么天真的解决方案将生成很多无用的代码,因为我们必须为每个输入{{1 }},共foo()

解决方法

由于compute的顺序相同,但是迭代遍历了不同的容器,因此您可以重构此函数。

std::vector<double> computeLoop( std::vector<double> &v,std::vector<double> const &computeArg1,std::vector<double> const &computeArg2,double d = 1.0 )
{
  std::vector<double> dfd = {};
  for (auto i = 0; i < v.size(); ++i) {
      // bump up
      v[i] += d;
      auto up = compute(computeArg1,computeArg2);
      v[i] -= d;

      // bump down
      v[i] -= d;
      auto down = compute(computeArg1,computeArg2);
      v[i] += d;

      auto derivative = (up - down) / 2.0 / d;
      dfd.pushback(derivative);
  }
}

实际通话。

auto dfda = computeLoop( a,a,b );
auto dfdb = computeLoop( b,b );

v通过引用传递,但这可能会导致维护问题。因为v可能与computeArg1computeArg2相同,但是在computeLoop中这并不明显。将来可能有人不知不觉地破坏了代码。

,

恕我直言,问题在于,抽象水平发生了变化。

那些A,B类是...

  1. 伪装成正义的载体
  2. 不是向量,而是完全其他的东西。

在情况(1)中,应该可以重写为类似于以下形式:

#include <... the usual suspects ... >

using VecF64 = std::vector<double>;
using VecVecF64 = std::vector<VecF64>;

// foo,dropping allusions of encapsulations. It KNOWS those A,B are vectors.
// Hence we can write it without knowledge of A,B types.
double foo(const VecVecF64& args) {
  return <result-of-complicated-computations>;
}

// With that,it is also easier to write the differentiation function
VecVecF64 fooGradients( const VecVecF64& args,double delta = 0.01) {
  VecVecF64 result;
  result.reserve(args.size());
  // for each arg vector in args,do your thing.
  // .. 
  return result;
}

在情况(2)中,如果您都被封装并且对A,B的性质保密, 您如何知道foo可以用于计算的双精度数?这就引出了A的梯度矢量大小的问题,并使这种颠簸的想法无法实现。

我的猜测是,您遇到一种情况1的问题。