16void thomas(std::vector<double>& sub, std::vector<double>& diag, std::vector<double>& sup, std::vector<double>& d) {
17 const std::size_t n = diag.size();
25 constexpr double kTinyDiag = 1e-300;
26 for (std::size_t i = 0; i < n; ++i) {
27 if (std::abs(diag[i]) < kTinyDiag) {
28 throw std::invalid_argument(
"thomas: zero (or sub-normal) diagonal entry; tridiagonal system is singular");
32 for (std::size_t i = 1; i < n; ++i) {
33 const double m = sub[i] / diag[i - 1];
34 diag[i] -= m * sup[i - 1];
35 if (std::abs(diag[i]) < kTinyDiag) {
36 throw std::invalid_argument(
"thomas: pivot vanished during forward sweep (system is singular at row " + std::to_string(i) +
")");
41 d[n - 1] /= diag[n - 1];
42 for (std::size_t i = n - 1; i-- > 0;) {
43 d[i] = (d[i] - sup[i] * d[i + 1]) / diag[i];
53double segment_eval(
double t,
double h,
double b_i,
double b_ip1,
double M_i,
double M_ip1)
noexcept {
54 const double s = 1.0 - t;
55 const double h2_6 = h * h / 6.0;
56 return s * b_i + t * b_ip1 + h2_6 * ((s * s * s - s) * M_i + (t * t * t - t) * M_ip1);
66double segment_eval_da(
double t,
double h,
double b_i,
double b_ip1,
double M_i,
double M_ip1)
noexcept {
67 const double s = 1.0 - t;
68 const double h_6 = h / 6.0;
69 return (b_ip1 - b_i) / h + h_6 * ((1.0 - 3.0 * s * s) * M_i + (3.0 * t * t - 1.0) * M_ip1);
78std::pair<double, double> compute_bounds(
const std::vector<double>& a,
const std::vector<double>& b,
const std::vector<double>& M) {
79 double lo = b.front();
80 double hi = b.front();
81 for (
const double v : b) {
89 const std::size_t n_seg = a.size() - 1;
90 for (std::size_t i = 0; i < n_seg; ++i) {
91 const double h = a[i + 1] - a[i];
107 const double D = b[i + 1] - b[i];
108 const double k = h * h / 6.0;
109 const double A = k * 3.0 * (M[i + 1] - M[i]);
110 const double B = k * 6.0 * M[i];
111 const double C = D + k * (-2.0 * M[i] - M[i + 1]);
112 const auto try_t = [&](
double t) {
113 if (t > 0.0 && t < 1.0) {
114 const double v = segment_eval(t, h, b[i], b[i + 1], M[i], M[i + 1]);
123 if (std::abs(A) < 1e-300) {
125 if (std::abs(B) > 1e-300) {
129 const double disc = B * B - 4.0 * A * C;
131 const double sq = std::sqrt(disc);
132 try_t((-B + sq) / (2.0 * A));
133 try_t((-B - sq) / (2.0 * A));
143 if (a.size() != b.size()) {
144 throw std::invalid_argument(
"CubicSplineCurve::build: a and b must have the same size");
146 const std::size_t n = a.size();
148 throw std::invalid_argument(
"CubicSplineCurve::build: need at least 2 knots");
150 for (std::size_t i = 1; i < n; ++i) {
151 if (!(a[i] > a[i - 1])) {
152 throw std::invalid_argument(
"CubicSplineCurve::build: a must be strictly increasing");
156 std::vector<double> M(n, 0.0);
160 const std::size_t m = n - 2;
161 std::vector<double> sub(m, 0.0);
162 std::vector<double> diag(m, 0.0);
163 std::vector<double> sup(m, 0.0);
164 std::vector<double> rhs(m, 0.0);
165 for (std::size_t i = 0; i < m; ++i) {
166 const std::size_t k = i + 1;
167 const double h_lo = a[k] - a[k - 1];
168 const double h_hi = a[k + 1] - a[k];
170 diag[i] = (h_lo + h_hi) / 3.0;
172 rhs[i] = (b[k + 1] - b[k]) / h_hi - (b[k] - b[k - 1]) / h_lo;
180 thomas(sub, diag, sup, rhs);
181 for (std::size_t i = 0; i < m; ++i) {
187 auto bnds = compute_bounds(a, b, M);
188 return std::unique_ptr<CubicSplineCurve>(
new CubicSplineCurve(std::move(a), std::move(b), std::move(M), bnds.first, bnds.second));
191CubicSplineCurve::CubicSplineCurve(std::vector<double> a, std::vector<double> b, std::vector<double> M,
double b_min,
double b_max)
192 : a_(std::move(a)), b_(std::move(b)), M_(std::move(M)), b_min_(b_min), b_max_(b_max) {
193 build_bucket_table_();
196void CubicSplineCurve::build_bucket_table_() noexcept {
197 const std::size_t n = a_.size();
198 const double a_lo = a_.front();
199 const double a_hi = a_.back();
200 const double span = a_hi - a_lo;
203 a_lo_inv_step_ = (span > 0.0) ? (
static_cast<double>(kBuckets) / span) : 0.0;
207 const auto max_seg_idx =
static_cast<std::uint16_t
>(n - 2);
208 for (std::size_t k = 0; k < kBuckets; ++k) {
209 const double a_bucket = a_lo +
static_cast<double>(k) * span /
static_cast<double>(kBuckets);
212 while (i + 1 < n - 1 && a_[i + 1] <= a_bucket) {
215 bucket_to_knot_[k] = (i > max_seg_idx) ? max_seg_idx :
static_cast<std::uint16_t
>(i);
220 return State{a_, b_, M_, b_min_, b_max_};
224 const std::size_t n = s.
a.size();
225 if (s.
b.size() != n || s.
M.size() != n) {
226 throw std::invalid_argument(
"CubicSplineCurve::from_state: a/b/M size mismatch");
229 throw std::invalid_argument(
"CubicSplineCurve::from_state: need at least 2 knots");
231 for (std::size_t i = 1; i < n; ++i) {
232 if (!(s.
a[i] > s.
a[i - 1])) {
233 throw std::invalid_argument(
"CubicSplineCurve::from_state: a must be strictly increasing");
243 const auto bnds = compute_bounds(s.
a, s.
b, s.
M);
244 return std::unique_ptr<CubicSplineCurve>(
new CubicSplineCurve(std::move(s.
a), std::move(s.
b), std::move(s.
M), bnds.first, bnds.second));
247std::size_t CubicSplineCurve::locate(
double a)
const noexcept {
251 const std::size_t n = a_.size();
252 const double a_lo = a_.front();
258 if (!std::isfinite(a)) {
264 if (a >= a_.back()) {
269 auto k =
static_cast<std::ptrdiff_t
>((a - a_lo) * a_lo_inv_step_);
272 }
else if (k >=
static_cast<std::ptrdiff_t
>(kBuckets)) {
273 k =
static_cast<std::ptrdiff_t
>(kBuckets) - 1;
275 std::size_t i = bucket_to_knot_[
static_cast<std::size_t
>(k)];
280 while (i + 1 < n - 1 && a_[i + 1] <= a) {
287 const std::size_t i = locate(a);
288 const double h = a_[i + 1] - a_[i];
289 const double t = (a - a_[i]) / h;
290 return segment_eval(t, h, b_[i], b_[i + 1], M_[i], M_[i + 1]);
294 const std::size_t i = locate(a);
295 const double h = a_[i + 1] - a_[i];
296 const double t = (a - a_[i]) / h;
297 return segment_eval_da(t, h, b_[i], b_[i + 1], M_[i], M_[i + 1]);
301 return {b_min_, b_max_};
305 return {a_.front(), a_.back()};