Fix bug where pow(JetA,JetB) returned wrong result for JetA==0. Change-Id: Ife7f6f3aca06322fa414ef747008d2b4dc468a57
diff --git a/include/ceres/jet.h b/include/ceres/jet.h index b476d80..87197ad 100644 --- a/include/ceres/jet.h +++ b/include/ceres/jet.h
@@ -573,22 +573,57 @@ } // pow -- base is a constant, exponent is a differentiable function. -// (a)^(p+dp) ~= a^p + a^p log(a) dp +// For a > 0 we have: (a)^(p + dp) ~= a^p + a^p log(a) dp +// For a == 0 and p > 0 we have: (a)^(p + dp) ~= 0 template <typename T, int N> inline Jet<T, N> pow(double f, const Jet<T, N>& g) { + if (f == 0 && g.a > 0) { + return Jet<T, N>(T(0.0)); + } T const tmp = pow(f, g.a); return Jet<T, N>(tmp, log(f) * tmp * g.v); } +// pow -- both base and exponent are differentiable functions. This has a +// variety of special cases that require careful handling. +// +// * For a > 0: (a + da)^(b + db) ~= a^b + a^(b - 1) * (b*da + a*log(a)*db) +// The numerical evaluation of a*log(a) for a > 0 is well behaved, even for +// extremely small values (e.g. 1e-99). +// +// * For a == 0 and b > 1: (a + da)^(b + db) ~= 0 +// This cases is needed because log(0) can not be evaluated in the a > 0 +// expression. However the function a*log(a) is well behaved around a == 0 +// and its limit as a-->0 is zero. +// +// * For a == 0 and b == 1: (a + da)^(b + db) ~= 0 + da +// +// * For a == 0 and 0 < b < 1: The value is finite but the derivatives are not. +// +// * For a == 0 and b < 0: The value and derivatives of a^b are not finite. +// +// * For a == 0 and b == 0: The C standard incorrectly defines 0^0 to be 1 +// "because there are applications that can exploit this definition". We +// (arbitrarily) decree that derivatives here will be nonfinite, since that +// is consistent with the behavior for b < 0 and 0 < b < 1. Practically any +// definition could have been justified because mathematical consistency has +// been lost at this point. -// pow -- both base and exponent are differentiable functions. -// (a+da)^(b+db) ~= a^b + b * a^(b-1) da + a^b log(a) * db template <typename T, int N> inline Jet<T, N> pow(const Jet<T, N>& f, const Jet<T, N>& g) { + if (f.a == 0 && g.a >= 1) { + // Handle the special cases when f == 0. + if (g.a > 1) { + return Jet<T, N>(T(0.0)); + } + return Jet<T, N>(T(0.0), f.v); + } + // Handle the generic case for f != 0. We also handle f == 0, g < 1 here and + // allow the log() function to generate -HUGE_VAL, since this results in a + // nonfinite derivative. T const tmp1 = pow(f.a, g.a); T const tmp2 = g.a * pow(f.a, g.a - T(1.0)); T const tmp3 = tmp1 * log(f.a); - return Jet<T, N>(tmp1, tmp2 * f.v + tmp3 * g.v); }
diff --git a/internal/ceres/jet_test.cc b/internal/ceres/jet_test.cc index 4598099..394dbb8 100644 --- a/internal/ceres/jet_test.cc +++ b/internal/ceres/jet_test.cc
@@ -244,6 +244,82 @@ ExpectJetsClose(v, u); } + { // Check that pow(0,y) == 1 for y>1, with both arguments Jets. + // This tests special case handling inside pow(). + J a = MakeJet(0, 1, 2); + J b = MakeJet(2, 3, 4); + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + ExpectJetsClose(c, MakeJet(0, 0, 0)); + } + + { // Check that pow(0,y) == 1 for y==1, with both arguments Jets. + // This tests special case handling inside pow(). + J a = MakeJet(0, 1, 2); + J b = MakeJet(1, 3, 4); + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + ExpectJetsClose(c, MakeJet(0, 1, 2)); + } + + { // Check that pow(0,<1) is not finite, with both arguments Jets. + for (int i = 1; i < 10; i++) { + J a = MakeJet(0, 1, 2); + J b = MakeJet(i*0.1, 3, 4); // b=0.1 ... 0.9 + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + EXPECT_EQ(c.a, 0.0); + EXPECT_FALSE(IsFinite(c.v[0])); + EXPECT_FALSE(IsFinite(c.v[1])); + } + for (int i = -10; i < 0; i++) { + J a = MakeJet(0, 1, 2); + J b = MakeJet(i*0.1, 3, 4); // b=-1,-0.9 ... 0 + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + EXPECT_FALSE(IsFinite(c.a)); + EXPECT_FALSE(IsFinite(c.v[0])); + EXPECT_FALSE(IsFinite(c.v[1])); + } + { + // The special case of 0^0=1 defined by the C standard. + J a = MakeJet(0, 1, 2); + J b = MakeJet(0, 3, 4); + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + EXPECT_EQ(c.a, 1.0); + EXPECT_FALSE(IsFinite(c.v[0])); + EXPECT_FALSE(IsFinite(c.v[1])); + } + } + + { // Check that pow(0,y) == 1 for y==2, with the second argument a Jets. + // This tests special case handling inside pow(). + double a = 0; + J b = MakeJet(2, 3, 4); + VL << "a = " << a; + VL << "b = " << b; + + J c = pow(a, b); + VL << "a^b = " << c; + ExpectJetsClose(c, MakeJet(0, 0, 0)); + } + { // Check that 1 + x == x + 1. J a = x + 1.0; J b = 1.0 + x;