let A be:
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
...
}
for the sake of simplicity consider the function:
double foo(input1, input2, ..., const A &a, const B &b, inputK, ...) {
/* do complex stuff */
return ...;
}
we want to differentiate foo() with respect to its arguments. Therefore the first order sensitivity d foo/d a is a std::vector<double> with size equal to a.size(). Same reasoning goes for d foo/d b.
A naive implementation would go as follows:
// compute d foo/d a
std::vector<double> computeDfDa(input1, input2, ..., const A &a, const B &b, inputK, ..., 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, input2, ..., aUp, b, inputK, ...);
auto down = foo(input1, input2, ..., aDown, b, inputK, ...);
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, input2, ..., const A &a, const B &b, inputK, ..., 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, input2, ..., a, bUp, inputK, ...);
auto down = foo(input1, input2, ..., a, bDown, inputK, ...);
auto derivative = (up - down) / 2.0 / db;
dfdb.pushback(derivative);
// revert bumps
bUp.bumpAt(i, -db);
bDown.bumpAt(i, db);
}
return dfdb;
}
This works well however we have basically the same code for computeDfDa() and for computeDfDb().
Is there any design pattern that would allow to have a unique (maybe templated) function that would understand automatically which input to bump?
Please note the position of a and b in the inputs is not commutative.
If the complexity and the number of inputs of foo() are much greater the naive solution would generate a lot of useless code as we'd have to write a computeDfDx() function for every input x of foo().
Since
computeorder is the same but iteration loop through different containers, you may refactor this function.Actual call.
Let
vpass by reference but it might cause maintenance problem. Becausevmight be the same reference ascomputeArg1orcomputeArg2, however incomputeLoopthis is not obvious. Someone might unconsciously break the code in the future.