Skip to content

Commit 10c5eb8

Browse files
committed
Reimplement AutoDiff from the ground up
1 parent 311bf86 commit 10c5eb8

26 files changed

Lines changed: 2347 additions & 446 deletions

common/BUILD.bazel

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,12 @@ drake_cc_library(
134134
hdrs = [
135135
"autodiff.h",
136136
"autodiff_overloads.h",
137-
"autodiffxd.h",
138-
"eigen_autodiff_types.h",
139137
],
140138
deps = [
141139
":cond",
142140
":dummy_value",
143141
":essential",
142+
"//common/autodiff:auto_diff",
144143
],
145144
)
146145

@@ -655,6 +654,7 @@ drake_cc_googletest(
655654
"test/autodiffxd_tanh_test.cc",
656655
"test/autodiffxd_test.h",
657656
],
657+
tags = ["manual"],
658658
deps = [
659659
":autodiff",
660660
"//common/test_utilities:eigen_matrix_compare",
@@ -663,6 +663,7 @@ drake_cc_googletest(
663663

664664
drake_cc_googletest(
665665
name = "autodiffxd_heap_test",
666+
tags = ["manual"],
666667
deps = [
667668
":autodiff",
668669
"//common/test_utilities:limit_malloc",
@@ -671,6 +672,7 @@ drake_cc_googletest(
671672

672673
drake_cc_googletest(
673674
name = "autodiff_overloads_test",
675+
tags = ["manual"],
674676
deps = [
675677
":autodiff",
676678
":essential",
@@ -1283,6 +1285,7 @@ drake_cc_googletest(
12831285

12841286
drake_cc_googletest(
12851287
name = "eigen_autodiff_types_test",
1288+
tags = ["manual"],
12861289
deps = [":autodiff"],
12871290
)
12881291

common/autodiff.h

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
11
#pragma once
2-
/// @file This header provides a single inclusion point for autodiff-related
3-
/// header files in the `drake/common` directory. Users should include this
4-
/// file. Including other individual headers such as `drake/common/autodiffxd.h`
5-
/// will generate a compile-time warning.
6-
7-
// In each header included below, it asserts that this macro
8-
// `DRAKE_COMMON_AUTODIFF_HEADER` is defined. If the macro is not defined, it
9-
// generates diagnostic warning messages.
10-
#define DRAKE_COMMON_AUTODIFF_HEADER
112

123
#include <Eigen/Core>
13-
#include <unsupported/Eigen/AutoDiff>
144

15-
// Do not alpha-sort the following block of hard-coded #includes, which is
16-
// protected by `clang-format on/off`.
17-
//
18-
// Rationale: We want to maximize the use of this header, `autodiff.h`, even
19-
// inside of the autodiff-related files to avoid any mistakes which might not be
20-
// detected. By centralizing the list here, we make sure that everyone will see
21-
// the correct order which respects the inter-dependencies of the autodiff
22-
// headers. This shields us from triggering undefined behaviors due to
23-
// order-of-specialization-includes-changed mistakes.
24-
//
25-
// clang-format off
26-
#include "drake/common/eigen_autodiff_types.h"
27-
#include "drake/common/autodiffxd.h"
5+
#include "drake/common/autodiff/auto_diff.h"
6+
7+
namespace drake {
8+
9+
/// An autodiff variable with a dynamic number of partials.
10+
using AutoDiffXd = autodiff::AutoDiff;
11+
12+
/// A dynamic-sized vector of autodiff variables, each with a dynamic-sized
13+
/// vector of partials.
14+
using AutoDiffVecXd = Eigen::Matrix<AutoDiffXd, Eigen::Dynamic, 1>;
15+
16+
} // namespace drake
17+
18+
#define DRAKE_COMMON_AUTODIFF_HEADER
2819
#include "drake/common/autodiff_overloads.h"
29-
// clang-format on
3020
#undef DRAKE_COMMON_AUTODIFF_HEADER

common/autodiff/BUILD.bazel

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- python -*-
2+
3+
load(
4+
"@drake//tools/skylark:drake_cc.bzl",
5+
"drake_cc_googletest",
6+
"drake_cc_library",
7+
"drake_cc_package_library",
8+
)
9+
load("//tools/lint:lint.bzl", "add_lint_tests")
10+
11+
package(default_visibility = ["//visibility:public"])
12+
13+
drake_cc_library(
14+
name = "auto_diff",
15+
srcs = [
16+
"auto_diff_derivatives_xpr.cc",
17+
"internal/partials.cc",
18+
"internal/standard_operations.cc",
19+
],
20+
hdrs = [
21+
"auto_diff.h",
22+
"auto_diff_derivatives_xpr.h",
23+
"internal/eigen_specializations.h",
24+
"internal/partials.h",
25+
"internal/standard_operations.h",
26+
],
27+
deps = [
28+
"//common:essential",
29+
"//common:reset_after_move",
30+
],
31+
)
32+
33+
drake_cc_googletest(
34+
name = "matrix_test",
35+
deps = [
36+
"//common:autodiff",
37+
],
38+
)
39+
40+
drake_cc_googletest(
41+
name = "partials_test",
42+
deps = [
43+
"//common:autodiff",
44+
"//common:random",
45+
"//common/test_utilities:eigen_matrix_compare",
46+
"//common/test_utilities:expect_throws_message",
47+
],
48+
)
49+
50+
drake_cc_googletest(
51+
name = "partials_heap_test",
52+
deps = [
53+
"//common:autodiff",
54+
"//common/test_utilities:limit_malloc",
55+
],
56+
)
57+
58+
drake_cc_googletest(
59+
name = "standard_operations_heap_test",
60+
deps = [
61+
"//common:autodiff",
62+
"//common/test_utilities:limit_malloc",
63+
],
64+
)
65+
66+
add_lint_tests()

common/autodiff/_zzz_notes.txt

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
2+
#if 0
3+
namespace internal {
4+
5+
/* ... See Eigen/src/Core/DenseStorage.h. */
6+
class DenseStorage {
7+
public:
8+
DenseStorage() = default;
9+
explicit DenseStorage(Eigen::internal::constructor_without_unaligned_array_assert) {}
10+
DenseStorage(Eigen::Index size, Eigen::Index rows, Eigen::Index cols) {
11+
resize(size, rows, cols);
12+
}
13+
14+
DenseStorage(const DenseStorage& other);
15+
DenseStorage& operator=(const DenseStorage& other);
16+
DenseStorage(DenseStorage&& other) noexcept;
17+
DenseStorage& operator=(DenseStorage&& other);
18+
19+
~DenseStorage() = default;
20+
21+
Eigen::Index rows() const { return rows_; }
22+
Eigen::Index cols() const { return cols_; }
23+
const AutoDiffScalar* data() const { return data_.data(); }
24+
AutoDiffScalar* data() { return data_.data(); }
25+
26+
void conservativeResize(Eigen::Index size, Eigen::Index rows, Eigen::Index cols) {
27+
DRAKE_ASSERT(size == rows * cols);
28+
data_.resize(size);
29+
rows_ = rows;
30+
cols_ = cols;
31+
}
32+
void resize(Eigen::Index size, Eigen::Index rows, Eigen::Index cols) {
33+
conservativeResize(size, rows, cols);
34+
}
35+
void swap(DenseStorage& other);
36+
37+
private:
38+
std::vector<AutoDiffScalar> data_;
39+
Eigen::Index rows_{0};
40+
Eigen::Index cols_{0};
41+
};
42+
} // namespace internal
43+
#endif
44+
45+
#if 0
46+
namespace Eigen {
47+
48+
/** A specialization of DenseStorage for use by Drake's AutoDiffScalar.
49+
This is for fully-dynamic matrices. */
50+
template <>
51+
class DenseStorage<
52+
/* T = */ drake::autodiff::AutoDiffScalar,
53+
/* Size = */ Dynamic,
54+
/* _Rows = */ Dynamic,
55+
/* _Cols = */ Dynamic,
56+
/* _Options = */ 0>
57+
: public drake::autodiff::internal::DenseStorage {};
58+
59+
} // namespace Eigen
60+
#endif
61+
62+
63+
64+
#if 0
65+
/// Overloads copysign from <cmath>.
66+
template <typename DerType, typename T>
67+
Eigen::AutoDiffScalar<DerType> copysign(const Eigen::AutoDiffScalar<DerType>& x,
68+
const T& y) {
69+
using std::isnan;
70+
if (isnan(x)) return (y >= 0) ? NAN : -NAN;
71+
if ((x < 0 && y >= 0) || (x >= 0 && y < 0))
72+
return -x;
73+
else
74+
return x;
75+
}
76+
77+
/// Overloads copysign from <cmath>.
78+
template <typename DerType>
79+
double copysign(double x, const Eigen::AutoDiffScalar<DerType>& y) {
80+
using std::isnan;
81+
if (isnan(x)) return (y >= 0) ? NAN : -NAN;
82+
if ((x < 0 && y >= 0) || (x >= 0 && y < 0))
83+
return -x;
84+
else
85+
return x;
86+
}
87+
88+
/// Overloads pow for an AutoDiffScalar base and exponent, implementing the
89+
/// chain rule.
90+
template <typename DerTypeA, typename DerTypeB>
91+
Eigen::AutoDiffScalar<
92+
typename internal::remove_all<DerTypeA>::type::PlainObject>
93+
pow(const Eigen::AutoDiffScalar<DerTypeA>& base,
94+
const Eigen::AutoDiffScalar<DerTypeB>& exponent) {
95+
// The two AutoDiffScalars being exponentiated must have the same matrix
96+
// type. This includes, but is not limited to, the same scalar type and
97+
// the same dimension.
98+
static_assert(
99+
std::is_same_v<
100+
typename internal::remove_all<DerTypeA>::type::PlainObject,
101+
typename internal::remove_all<DerTypeB>::type::PlainObject>,
102+
"The derivative types must match.");
103+
104+
internal::make_coherent(base.derivatives(), exponent.derivatives());
105+
106+
const auto& x = base.value();
107+
const auto& xgrad = base.derivatives();
108+
const auto& y = exponent.value();
109+
const auto& ygrad = exponent.derivatives();
110+
111+
using std::pow;
112+
using std::log;
113+
const auto x_to_the_y = pow(x, y);
114+
if (ygrad.isZero(std::numeric_limits<double>::epsilon()) ||
115+
ygrad.size() == 0) {
116+
// The derivative only depends on ∂(x^y)/∂x -- this prevents undefined
117+
// behavior in the corner case where ∂(x^y)/∂y is infinite when x = 0,
118+
// despite ∂y/∂v being 0.
119+
return Eigen::MakeAutoDiffScalar(x_to_the_y, y * pow(x, y - 1) * xgrad);
120+
}
121+
return Eigen::MakeAutoDiffScalar(
122+
// The value is x ^ y.
123+
x_to_the_y,
124+
// The multivariable chain rule states:
125+
// df/dv_i = (∂f/∂x * dx/dv_i) + (∂f/∂y * dy/dv_i)
126+
// ∂f/∂x is y*x^(y-1)
127+
y * pow(x, y - 1) * xgrad +
128+
// ∂f/∂y is (x^y)*ln(x)
129+
x_to_the_y * log(x) * ygrad);
130+
}
131+
132+
#endif

common/autodiff/auto_diff.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#pragma once
2+
3+
#include "drake/common/autodiff/auto_diff_derivatives_xpr.h"
4+
#include "drake/common/autodiff/internal/partials.h"
5+
#include "drake/common/drake_copyable.h"
6+
#include "drake/common/eigen_types.h"
7+
8+
namespace drake {
9+
namespace autodiff {
10+
11+
/** A scalar type that performs automatic differentiation, similar to
12+
Eigen::AutoDiffScalar<Eigen::VectorXd>. Unlike Eigen::AutoDiffScalar,
13+
Drake's AutoDiff is not templated; it only supports dynamically-sized
14+
derivatives using floating-point doubles.
15+
16+
However, using modern C++ implementation tricks (reference-counted,
17+
copy-on-write derivatives storage) and a more careful representation
18+
(maintaining the derivatives scale separately, and using inline storage
19+
in case only one partial is non-zero) it runs much faster than
20+
Eigen::AutoDiffScalar<Eigen::VectorXd>. */
21+
class AutoDiff {
22+
public:
23+
DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(AutoDiff);
24+
25+
/** Compatibility alias to mimic Eigen::AutoDiffScalar. */
26+
using DerType = Eigen::VectorXd;
27+
28+
/** Compatibility alias to mimic Eigen::AutoDiffScalar. */
29+
using Scalar = double;
30+
31+
/** Constructs zero. */
32+
AutoDiff() = default;
33+
34+
/** Constructs a value with empty derivatives. */
35+
// NOLINTNEXTLINE(runtime/explicit): This conversion is desirable.
36+
AutoDiff(double value) : value_{value} {}
37+
38+
/** Constructs a value with a single partial derivative of 1.0 at the given
39+
`offset` in a vector of `size` otherwise-zero derivatives. */
40+
AutoDiff(double value, Eigen::Index size, Eigen::Index offset)
41+
: value_{value},
42+
partials_{size, offset} {}
43+
44+
/** Constructs a value with the given derivatives. */
45+
AutoDiff(
46+
double value,
47+
const Eigen::Ref<const Eigen::VectorXd>& derivatives)
48+
: value_{value},
49+
partials_{derivatives} {}
50+
51+
/** Assigns a value and clears the derivatives. */
52+
AutoDiff& operator=(double value) {
53+
value_ = value;
54+
partials_.Clear();
55+
return *this;
56+
}
57+
58+
~AutoDiff() = default;
59+
60+
/** Returns the value part of this AutoDiff (readonly). */
61+
double value() const { return value_; }
62+
63+
/** (Advanced) Returns the value part of this AutoDiff (mutable).
64+
Operations on this value will NOT alter the dervatives. */
65+
double& value() { return value_; }
66+
67+
/** Returns a view of the dervatives part of this AutoDiff (readonly). */
68+
AutoDiffDerivativesConstXpr derivatives() const {
69+
return partials_.make_const_xpr();
70+
}
71+
72+
/** (Advanced) Returns a mutable view of the dervatives part of this AutoDiff.
73+
74+
Instead of mutating the derivatives after construction, it's generally
75+
preferable to set them directly in the constructor if possible.
76+
77+
@note this function name is kept for compatibility with Eigen::AutoDiffScalar
78+
but it does NOT run in constant-time even though its name is lowercase.
79+
Calling this function often needs to copy the derivatives prior to returning
80+
the reference, so is O(N) in the size() of the derivatives. */
81+
AutoDiffDerivativesMutableXpr derivatives() {
82+
return partials_.MakeMutableXpr();
83+
}
84+
85+
/** (Internal use only)
86+
Users should call derivatives() instead. */
87+
const internal::Partials& partials() const { return partials_; }
88+
89+
/** (Internal use only)
90+
Users should call derivatives() instead. */
91+
internal::Partials& partials() { return partials_; }
92+
93+
private:
94+
double value_{0.0};
95+
internal::Partials partials_;
96+
};
97+
98+
} // namespace autodiff
99+
} // namespace drake
100+
101+
/* clang-format off to disable clang-format-includes */
102+
// These futher refine our AutoDiff type and must appear in exactly this order.
103+
#include "drake/common/autodiff/internal/standard_operations.h"
104+
#include "drake/common/autodiff/internal/eigen_specializations.h"

0 commit comments

Comments
 (0)