support copysign jet
libc++ (default on macOS) uses the copysign function for std::complex
multiplication which causes compilation errors such as these if
std::complex is combined with jets:
/Applications/Xcode_12.4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/complex:607:19: error: no matching function for call to 'copysign'
__a = copysign(__libcpp_isinf_or_builtin(__a) ? _Tp(1) : _Tp(0), __a);
^~~~~~~~
foo.hpp:218:39: note: in instantiation of function template specialization 'std::__1::operator*<ceres::Jet<double, 3> >' requested here
std::complex<Scalar> result = lhs * rhs;
copysign is also useful in other situations where the sign is required
allowing it to compute without branching.
Change-Id: I84b2d23374f1bf3bae32833016bb686d97d74054
diff --git a/include/ceres/jet.h b/include/ceres/jet.h
index be24617..c97e621 100644
--- a/include/ceres/jet.h
+++ b/include/ceres/jet.h
@@ -426,6 +426,42 @@
return Jet<T, N>(abs(f.a), copysign(T(1), f.a) * f.v);
}
+// copysign(a, b) composes a float with the magnitude of a and the sign of b.
+// Therefore, the function can be formally defined as
+//
+// copysign(a, b) = sgn(b)|a|
+//
+// where
+//
+// d/dx |x| = sgn(x)
+// d/dx sgn(x) = 2δ(x)
+//
+// sgn(x) being the signum function. Differentiating copysign(a, b) with respect
+// to a and b gives:
+//
+// d/da sgn(b)|a| = sgn(a) sgn(b)
+// d/db sgn(b)|a| = 2|a|δ(b)
+//
+// with the dual representation given by
+//
+// copysign(a + da, b + db) ~= sgn(b)|a| + (sgn(a)sgn(b) da + 2|a|δ(b) db)
+//
+// where δ(b) is the Dirac delta function.
+template <typename T, int N>
+inline Jet<T, N> copysign(const Jet<T, N>& f, const Jet<T, N> g) {
+ // The Dirac delta function δ(b) is undefined at b=0 (here it's
+ // infinite) and 0 everywhere else.
+ T d = g.a == T(0) ? std::numeric_limits<T>::infinity() : T(0);
+ T sa = copysign(T(1), f.a); // sgn(a)
+ T sb = copysign(T(1), g.a); // sgn(b)
+ // The second part of the infinitesimal is 2|a|δ(b) which is either infinity
+ // or 0 unless a or any of the values of the b infinitesimal are 0. In the
+ // latter case, the corresponding values become NaNs (multiplying 0 by
+ // infinity gives NaN). We drop the constant factor 2 since it does not change
+ // the result (its values will still be either 0, infinity or NaN).
+ return Jet<T, N>(copysign(f.a, g.a), sa * sb * f.v + abs(f.a) * d * g.v);
+}
+
// log(a + h) ~= log(a) + h / a
template <typename T, int N>
inline Jet<T, N> log(const Jet<T, N>& f) {
diff --git a/internal/ceres/jet_test.cc b/internal/ceres/jet_test.cc
index 4718a20..5631312 100644
--- a/internal/ceres/jet_test.cc
+++ b/internal/ceres/jet_test.cc
@@ -782,6 +782,103 @@
VL << "z = " << z;
ExpectJetsClose(J{y.a}, z);
}
+ { // copysign(x, +1)
+ J z = copysign(x, J{+1});
+ VL << "z = " << z;
+ ExpectJetsClose(x, z);
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(x, -1)
+ J z = copysign(x, J{-1});
+ VL << "z = " << z;
+ ExpectJetsClose(-x, z);
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(-x, +1)
+
+ J z = copysign(-x, J{+1});
+ VL << "z = " << z;
+ ExpectJetsClose(x, z);
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(-x, -1)
+ J z = copysign(-x, J{-1});
+ VL << "z = " << z;
+ ExpectJetsClose(-x, z);
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(-0, +1)
+ J z = copysign(MakeJet(-0, 1, 2), J{+1});
+ VL << "z = " << z;
+ ExpectJetsClose(MakeJet(+0, 1, 2), z);
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(-0, -1)
+ J z = copysign(MakeJet(-0, 1, 2), J{-1});
+ VL << "z = " << z;
+ ExpectJetsClose(MakeJet(-0, -1, -2), z);
+ EXPECT_TRUE(std::signbit(z.a));
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(+0, -1)
+ J z = copysign(MakeJet(+0, 1, 2), J{-1});
+ VL << "z = " << z;
+ ExpectJetsClose(MakeJet(-0, -1, -2), z);
+ EXPECT_TRUE(std::signbit(z.a));
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(+0, +1)
+ J z = copysign(MakeJet(+0, 1, 2), J{+1});
+ VL << "z = " << z;
+ ExpectJetsClose(MakeJet(+0, 1, 2), z);
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(IsFinite(z.v[0]));
+ EXPECT_TRUE(IsFinite(z.v[1]));
+ }
+ { // copysign(+0, +0)
+ J z = copysign(MakeJet(+0, 1, 2), J{+0});
+ VL << "z = " << z;
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(std::signbit(z.v[0]));
+ EXPECT_TRUE(std::signbit(z.v[1]));
+ EXPECT_TRUE(IsNaN(z.v[0]));
+ EXPECT_TRUE(IsNaN(z.v[1]));
+ }
+ { // copysign(+0, -0)
+ J z = copysign(MakeJet(+0, 1, 2), J{-0});
+ VL << "z = " << z;
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(std::signbit(z.v[0]));
+ EXPECT_TRUE(std::signbit(z.v[1]));
+ EXPECT_TRUE(IsNaN(z.v[0]));
+ EXPECT_TRUE(IsNaN(z.v[1]));
+ }
+ { // copysign(-0, +0)
+ J z = copysign(MakeJet(-0, 1, 2), J{+0});
+ VL << "z = " << z;
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(std::signbit(z.v[0]));
+ EXPECT_TRUE(std::signbit(z.v[1]));
+ EXPECT_TRUE(IsNaN(z.v[0]));
+ EXPECT_TRUE(IsNaN(z.v[1]));
+ }
+ { // copysign(-0, -0)
+ J z = copysign(MakeJet(-0, 1, 2), J{-0});
+ VL << "z = " << z;
+ EXPECT_FALSE(std::signbit(z.a));
+ EXPECT_TRUE(std::signbit(z.v[0]));
+ EXPECT_TRUE(std::signbit(z.v[1]));
+ EXPECT_TRUE(IsNaN(z.v[0]));
+ EXPECT_TRUE(IsNaN(z.v[1]));
+ }
}
TEST(Jet, JetsInEigenMatrices) {