Skip to content

Commit 38246fb

Browse files
gda98reclaude
andcommitted
Interpolation: add derivative method to SpanInterpolators
Adds a `derivative(source_points, values, target_point)` method to the `intrp::SpanInterpolator` interface and its derived classes, returning the derivative of the interpolant at the requested point (the analogue of the existing `interpolate`). This is needed by downstream code that must time-differentiate an interpolated worldtube quantity. - `SpanInterpolator` declares the pure-virtual real overload and a virtual complex overload whose base implementation splits the values into real/imaginary parts; derived classes override it when a specialized complex implementation is more efficient. - `LinearSpanInterpolator` returns the constant slope between the two points. - `CubicSpanInterpolator` adds `derivative_impl`, the analytic derivative of the cubic Lagrange interpolant. - `BarycentricRationalSpanInterpolator` uses boost's `barycentric_rational::prime`. Tested with an approximate-fidelity check against the analytic derivative of `A cos(w t)`, and -- since a degree-N interpolant reproduces a degree-N polynomial exactly -- with exact-polynomial derivative checks for the linear, cubic, and barycentric interpolators. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0fa37ba commit 38246fb

9 files changed

Lines changed: 378 additions & 17 deletions

src/NumericalAlgorithms/Interpolation/BarycentricRationalSpanInterpolator.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,23 @@ double BarycentricRationalSpanInterpolator::interpolate(
4141
return interpolant(target_point);
4242
}
4343

44+
double BarycentricRationalSpanInterpolator::derivative(
45+
const gsl::span<const double>& source_points,
46+
const gsl::span<const double>& values, const double target_point) const {
47+
if (UNLIKELY(source_points.size() < min_order_ + 1)) {
48+
ERROR("provided independent values for interpolation too small.");
49+
}
50+
// Boost moved barycentric_rational from boost::math to
51+
// boost::math::interpolators in version 1.77.
52+
// NOLINTNEXTLINE(google-build-using-namespace)
53+
using namespace boost::math;
54+
// NOLINTNEXTLINE(google-build-using-namespace)
55+
using namespace boost::math::interpolators;
56+
const barycentric_rational<double> interpolant(
57+
source_points.data(), values.data(), source_points.size(),
58+
std::min(source_points.size() - 1, max_order_));
59+
return interpolant.prime(target_point);
60+
}
61+
4462
PUP::able::PUP_ID intrp::BarycentricRationalSpanInterpolator::my_PUP_ID = 0;
4563
} // namespace intrp

src/NumericalAlgorithms/Interpolation/BarycentricRationalSpanInterpolator.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,18 @@ class BarycentricRationalSpanInterpolator : public SpanInterpolator {
6969
return std::make_unique<BarycentricRationalSpanInterpolator>(*this);
7070
}
7171

72-
// to get the generic overload:
72+
// to get the generic overloads:
73+
using SpanInterpolator::derivative;
7374
using SpanInterpolator::interpolate;
7475

7576
double interpolate(const gsl::span<const double>& source_points,
7677
const gsl::span<const double>& values,
7778
double target_point) const override;
7879

80+
double derivative(const gsl::span<const double>& source_points,
81+
const gsl::span<const double>& values,
82+
double target_point) const override;
83+
7984
size_t required_number_of_points_before_and_after() const override {
8085
return min_order_ / 2 + 1;
8186
}

src/NumericalAlgorithms/Interpolation/CubicSpanInterpolator.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,44 @@ SPECTRE_ALWAYS_INLINE ValueType interpolate_impl(
3535
((t0 - t1) * (t0 - t2) * (t1 - t2) * (t0 - t3) * (t1 - t3) *
3636
(t2 - t3));
3737
}
38+
39+
template <typename ValueType>
40+
SPECTRE_ALWAYS_INLINE ValueType derivative_impl(
41+
const gsl::span<const double>& source_points,
42+
const gsl::span<const ValueType>& values, const double target_point) {
43+
const double t0 = source_points[0];
44+
const double t1 = source_points[1];
45+
const double t2 = source_points[2];
46+
const double t3 = source_points[3];
47+
48+
const auto d0 = values[0];
49+
const auto d1 = values[1];
50+
const auto d2 = values[2];
51+
const auto d3 = values[3];
52+
53+
const double x_minus_t0 = target_point - t0;
54+
const double x_minus_t1 = target_point - t1;
55+
const double x_minus_t2 = target_point - t2;
56+
const double x_minus_t3 = target_point - t3;
57+
58+
// Sum-of-products derivative of each Lagrange basis numerator:
59+
// d/dx [(x - a)(x - b)(x - c)] = (x-b)(x-c) + (x-a)(x-c) + (x-a)(x-b)
60+
const double s0 = (x_minus_t2 * x_minus_t3) + (x_minus_t1 * x_minus_t3) +
61+
(x_minus_t1 * x_minus_t2);
62+
const double s1 = (x_minus_t2 * x_minus_t3) + (x_minus_t0 * x_minus_t3) +
63+
(x_minus_t0 * x_minus_t2);
64+
const double s2 = (x_minus_t1 * x_minus_t3) + (x_minus_t0 * x_minus_t3) +
65+
(x_minus_t0 * x_minus_t1);
66+
const double s3 = (x_minus_t1 * x_minus_t2) + (x_minus_t0 * x_minus_t2) +
67+
(x_minus_t0 * x_minus_t1);
68+
69+
return (d0 * s0 * (t1 - t2) * (t1 - t3) * (t2 - t3) -
70+
d1 * s1 * (t0 - t2) * (t0 - t3) * (t2 - t3) +
71+
d2 * s2 * (t0 - t1) * (t0 - t3) * (t1 - t3) -
72+
d3 * s3 * (t0 - t1) * (t0 - t2) * (t1 - t2)) /
73+
((t0 - t1) * (t0 - t2) * (t1 - t2) * (t0 - t3) * (t1 - t3) *
74+
(t2 - t3));
75+
}
3876
} // namespace
3977

4078
double CubicSpanInterpolator::interpolate(
@@ -50,5 +88,18 @@ std::complex<double> CubicSpanInterpolator::interpolate(
5088
return interpolate_impl(source_points, values, target_point);
5189
}
5290

91+
double CubicSpanInterpolator::derivative(
92+
const gsl::span<const double>& source_points,
93+
const gsl::span<const double>& values, double target_point) const {
94+
return derivative_impl(source_points, values, target_point);
95+
}
96+
97+
std::complex<double> CubicSpanInterpolator::derivative(
98+
const gsl::span<const double>& source_points,
99+
const gsl::span<const std::complex<double>>& values,
100+
double target_point) const {
101+
return derivative_impl(source_points, values, target_point);
102+
}
103+
53104
PUP::able::PUP_ID intrp::CubicSpanInterpolator::my_PUP_ID = 0;
54105
} // namespace intrp

src/NumericalAlgorithms/Interpolation/CubicSpanInterpolator.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ class CubicSpanInterpolator : public SpanInterpolator {
5454
const gsl::span<const std::complex<double>>& values,
5555
double target_point) const;
5656

57+
double derivative(const gsl::span<const double>& source_points,
58+
const gsl::span<const double>& values,
59+
double target_point) const override;
60+
61+
std::complex<double> derivative(
62+
const gsl::span<const double>& source_points,
63+
const gsl::span<const std::complex<double>>& values,
64+
double target_point) const override;
65+
5766
size_t required_number_of_points_before_and_after() const override {
5867
return 2;
5968
}

src/NumericalAlgorithms/Interpolation/LinearSpanInterpolator.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ SPECTRE_ALWAYS_INLINE ValueType interpolate_impl(
2020
(source_points[1] - source_points[0]) *
2121
(target_point - source_points[0]);
2222
}
23+
24+
template <typename ValueType>
25+
SPECTRE_ALWAYS_INLINE ValueType
26+
derivative_impl(const gsl::span<const double>& source_points,
27+
const gsl::span<const ValueType>& values) {
28+
return (values[1] - values[0]) / (source_points[1] - source_points[0]);
29+
}
2330
} // namespace
2431

2532
double LinearSpanInterpolator::interpolate(
@@ -35,5 +42,19 @@ std::complex<double> LinearSpanInterpolator::interpolate(
3542
return interpolate_impl(source_points, values, target_point);
3643
}
3744

45+
double LinearSpanInterpolator::derivative(
46+
const gsl::span<const double>& source_points,
47+
const gsl::span<const double>& values,
48+
const double /*target_point*/) const {
49+
return derivative_impl(source_points, values);
50+
}
51+
52+
std::complex<double> LinearSpanInterpolator::derivative(
53+
const gsl::span<const double>& source_points,
54+
const gsl::span<const std::complex<double>>& values,
55+
const double /*target_point*/) const {
56+
return derivative_impl(source_points, values);
57+
}
58+
3859
PUP::able::PUP_ID intrp::LinearSpanInterpolator::my_PUP_ID = 0;
3960
} // namespace intrp

src/NumericalAlgorithms/Interpolation/LinearSpanInterpolator.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ class LinearSpanInterpolator : public SpanInterpolator {
5050
const gsl::span<const std::complex<double>>& values,
5151
double target_point) const;
5252

53+
double derivative(const gsl::span<const double>& source_points,
54+
const gsl::span<const double>& values,
55+
double target_point) const override;
56+
57+
std::complex<double> derivative(
58+
const gsl::span<const double>& source_points,
59+
const gsl::span<const std::complex<double>>& values,
60+
double target_point) const override;
61+
5362
size_t required_number_of_points_before_and_after() const override {
5463
return 1;
5564
}

src/NumericalAlgorithms/Interpolation/SpanInterpolator.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,24 @@ std::complex<double> SpanInterpolator::interpolate(
3131
gsl::span<const double>(imag_part.data(), imag_part.size()),
3232
target_point)};
3333
}
34+
35+
std::complex<double> SpanInterpolator::derivative(
36+
const gsl::span<const double>& source_points,
37+
const gsl::span<const std::complex<double>>& values,
38+
const double target_point) const {
39+
// the operation below to get the real and imag parts does not alter the
40+
// contents of the span, so the const-cast is safe.
41+
const ComplexDataVector view{
42+
const_cast<std::complex<double>*>(values.data()), // NOLINT
43+
values.size()};
44+
const DataVector real_part = real(view);
45+
const DataVector imag_part = imag(view);
46+
return std::complex<double>{
47+
derivative(source_points,
48+
gsl::span<const double>(real_part.data(), real_part.size()),
49+
target_point),
50+
derivative(source_points,
51+
gsl::span<const double>(imag_part.data(), imag_part.size()),
52+
target_point)};
53+
}
3454
} // namespace intrp

src/NumericalAlgorithms/Interpolation/SpanInterpolator.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,23 @@ class SpanInterpolator : public PUP::able {
5555
const gsl::span<const std::complex<double>>& values,
5656
double target_point) const;
5757

58+
/// Evaluate the derivative of the interpolant of the function represented by
59+
/// `values` at `source_points`, evaluated at the requested `target_point`.
60+
virtual double derivative(const gsl::span<const double>& source_points,
61+
const gsl::span<const double>& values,
62+
double target_point) const = 0;
63+
64+
/// Evaluate the derivative of the interpolant of the function represented by
65+
/// complex `values` at `source_points`, evaluated at the requested
66+
/// `target_point`. This generic implementation calls the real version on the
67+
/// real and imaginary parts separately; derived classes should override it
68+
/// when a specialized complex implementation that avoids the split is more
69+
/// efficient.
70+
virtual std::complex<double> derivative(
71+
const gsl::span<const double>& source_points,
72+
const gsl::span<const std::complex<double>>& values,
73+
double target_point) const;
74+
5875
/// The number of domain points that should be both before and after the
5976
/// requested target point for best interpolation. For instance, for a linear
6077
/// interpolator, this function would return `1` to request that the target is

0 commit comments

Comments
 (0)