ALFI
Advanced Library for Function Interpolation
Loading...
Searching...
No Matches
quadratic.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <iostream>
5#include <cmath>
6#include <variant>
7
8#include "../config.h"
9#include "../util/arrays.h"
10#include "../util/misc.h"
11#include "../util/spline.h"
12
13namespace alfi::spline {
14 template <typename Number = DefaultNumber, template <typename, typename...> class Container = DefaultContainer>
16 public:
17 struct Types final {
23 struct NotAKnotStart final {};
29 struct NotAKnotEnd final {};
33 struct SemiNotAKnot final {};
37 struct NaturalStart final {};
41 struct NaturalEnd final {};
45 struct SemiNatural final {};
49 struct SemiSemi final {};
53 struct ClampedStart final {
54 explicit ClampedStart(Number df) : df(std::move(df)) {}
55 Number df;
56 };
57
60 struct ClampedEnd final {
61 explicit ClampedEnd(Number df) : df(std::move(df)) {}
62 Number df;
63 };
64
67 struct SemiClamped final {
68 SemiClamped(Number df_1, Number df_n) : df_1(std::move(df_1)), df_n(std::move(df_n)) {}
69 Number df_1, df_n;
70 };
71
74 struct FixedSecondStart final {
75 explicit FixedSecondStart(Number d2f) : d2f(std::move(d2f)) {}
76 Number d2f;
77 };
78
81 struct FixedSecondEnd final {
82 explicit FixedSecondEnd(Number d2f) : d2f(std::move(d2f)) {}
83 Number d2f;
84 };
85
88 struct SemiFixedSecond final {
89 SemiFixedSecond(Number d2f_1, Number d2f_n) : d2f_1(std::move(d2f_1)), d2f_n(std::move(d2f_n)) {}
90 Number d2f_1, d2f_n;
91 };
92
95 struct Clamped final {
96 Clamped(SizeT point_idx, Number df) : point_idx(std::move(point_idx)), df(std::move(df)) {}
98 Number df;
99 };
100
103 struct FixedSecond final {
104 FixedSecond(SizeT segment_idx, Number d2f) : segment_idx(std::move(segment_idx)), d2f(std::move(d2f)) {}
106 Number d2f;
107 };
108
113 struct NotAKnot final {
114 explicit NotAKnot(SizeT point_idx) : point_idx(std::move(point_idx)) {}
116 };
118 };
119
120 using Type = std::variant<typename Types::NotAKnotStart,
121 typename Types::NotAKnotEnd,
122 typename Types::SemiNotAKnot,
123 typename Types::NaturalStart,
124 typename Types::NaturalEnd,
125 typename Types::SemiNatural,
126 typename Types::SemiSemi,
127 typename Types::ClampedStart,
128 typename Types::ClampedEnd,
129 typename Types::SemiClamped,
131 typename Types::FixedSecondEnd,
132 typename Types::SemiFixedSecond,
133 typename Types::Clamped,
134 typename Types::FixedSecond,
135 typename Types::NotAKnot>;
136
137 static Container<Number> compute_coeffs(const Container<Number>& X, const Container<Number>& Y, Type type = typename Types::Default{}) {
138 if (X.size() != Y.size()) {
139 std::cerr << "Error in function " << __FUNCTION__
140 << ": Vectors X (of size " << X.size()
141 << ") and Y (of size " << Y.size()
142 << ") are not the same size. Returning an empty array..." << std::endl;
143 return {};
144 }
145
146 const SizeT n = X.size();
147
148 if (n <= 1) {
150 }
151
152 if (std::holds_alternative<typename Types::NotAKnotStart>(type)) {
153 return compute_coeffs(X, Y, typename Types::NotAKnot(1));
154 } else if (std::holds_alternative<typename Types::NotAKnotEnd>(type)) {
155 return compute_coeffs(X, Y, typename Types::NotAKnot(n - 2));
156 } else if (std::holds_alternative<typename Types::SemiNotAKnot>(type)) {
157 return util::arrays::mean(compute_coeffs(X, Y, typename Types::NotAKnot{1}), compute_coeffs(X, Y, typename Types::NotAKnot{n - 2}));
158 } else if (std::holds_alternative<typename Types::NaturalStart>(type)) {
159 return compute_coeffs(X, Y, typename Types::FixedSecond(0, 0));
160 } else if (std::holds_alternative<typename Types::NaturalEnd>(type)) {
161 return compute_coeffs(X, Y, typename Types::FixedSecond(n - 2, 0));
162 } else if (std::holds_alternative<typename Types::SemiNatural>(type)) {
164 } else if (std::holds_alternative<typename Types::SemiSemi>(type)) {
166 } else if (const auto* cs = std::get_if<typename Types::ClampedStart>(&type)) {
167 return compute_coeffs(X, Y, typename Types::Clamped(0, cs->df));
168 } else if (const auto* ce = std::get_if<typename Types::ClampedEnd>(&type)) {
169 return compute_coeffs(X, Y, typename Types::Clamped(n - 1, ce->df));
170 } else if (const auto* sc = std::get_if<typename Types::SemiClamped>(&type)) {
171 return util::arrays::mean(compute_coeffs(X, Y, typename Types::Clamped(0, sc->df_1)), compute_coeffs(X, Y, typename Types::Clamped(n - 1, sc->df_n)));
172 } else if (const auto* fss = std::get_if<typename Types::FixedSecondStart>(&type)) {
173 return compute_coeffs(X, Y, typename Types::FixedSecond(0, fss->d2f));
174 } else if (const auto* fse = std::get_if<typename Types::FixedSecondEnd>(&type)) {
175 return compute_coeffs(X, Y, typename Types::FixedSecond(n - 2, fse->d2f));
176 } else if (const auto* sfs = std::get_if<typename Types::SemiFixedSecond>(&type)) {
177 return util::arrays::mean(compute_coeffs(X, Y, typename Types::FixedSecond(0, sfs->d2f_1)), compute_coeffs(X, Y, typename Types::FixedSecond(n - 2, sfs->d2f_n)));
178 }
179
180 Container<Number> coeffs(3 * (n - 1));
181
182 // i1 - index of first constructed segment
183 // i2 - index of first after last constructed segment
184 SizeT i1 = 0, i2 = n - 1;
185
186 if (const auto* clamped = std::get_if<typename Types::Clamped>(&type)) {
187 const auto i = clamped->point_idx;
188 if (i < 0 || i >= n) {
189 std::cerr << "Error in function " << __FUNCTION__
190 << ": point index for type 'Clamped' is out of bounds: "
191 << "expected to be non-negative and less than " << n << ", but got " << i
192 << ". Returning an empty array..." << std::endl;
193 return {};
194 }
195 if (i > 0) {
196 const auto dx1 = X[i] - X[i-1], dy1 = Y[i] - Y[i-1];
197 coeffs[3*(i-1)+2] = Y[i-1];
198 coeffs[3*(i-1)+1] = 2*dy1/dx1 - clamped->df;
199 coeffs[3*(i-1)] = dy1/dx1/dx1 - coeffs[3*(i-1)+1]/dx1;
200 i1 = i - 1;
201 }
202 if (i < n - 1) {
203 const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
204 coeffs[3*i+2] = Y[i];
205 coeffs[3*i+1] = clamped->df;
206 coeffs[3*i] = dy/dx/dx - coeffs[3*i+1]/dx;
207 i2 = i + 1;
208 }
209 } else if (const auto* fixed_second = std::get_if<typename Types::FixedSecond>(&type)) {
210 const auto i = fixed_second->segment_idx;
211 if (i < 0 || i >= n - 1) {
212 std::cerr << "Error in function " << __FUNCTION__
213 << ": segment index for type 'FixedSecond' is out of bounds: "
214 << "expected to be non-negative and less than " << n - 1 << ", but got " << i
215 << ". Returning an empty array..." << std::endl;
216 return {};
217 }
218 coeffs[3*i] = fixed_second->d2f / 2;
219 coeffs[3*i+1] = (Y[i+1]-Y[i])/(X[i+1]-X[i]) - coeffs[3*i]*(X[i+1]-X[i]);
220 coeffs[3*i+2] = Y[i];
221 i1 = i;
222 i2 = i + 1;
223 } else if (const auto* not_a_knot = std::get_if<typename Types::NotAKnot>(&type)) {
224 if (n <= 2) {
226 }
227 const auto i = not_a_knot->point_idx;
228 if (i < 1 || i >= n - 1) {
229 std::cerr << "Error in function " << __FUNCTION__
230 << ": point index for type 'NotAKnot' is out of bounds: "
231 << "expected to be positive and less than " << n - 1 << ", but got " << i
232 << ". Returning an empty array..." << std::endl;
233 return {};
234 }
235 const auto dx1 = X[i] - X[i-1], dy1 = Y[i] - Y[i-1];
236 const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
237 coeffs[3*(i-1)+2] = Y[i-1];
238 coeffs[3*i+2] = Y[i];
239 coeffs[3*(i-1)] = coeffs[3*i] = (dy/dx - dy1/dx1) / (dx1 + dx);
240 coeffs[3*(i-1)+1] = dy1/dx1 - coeffs[3*(i-1)]*dx1;
241 coeffs[3*i+1] = dy/dx - coeffs[3*i]*dx;
242 i1 = i - 1;
243 i2 = i + 1;
244 } else {
245 std::cerr << "Error in function " << __FUNCTION__ << ": Unknown type. This should not have happened. "
246 << "Please report this to the developers. Returning an empty array..." << std::endl;
247 return {};
248 }
249
250 for (SizeT iter = 0; iter < i1; ++iter) {
251 const auto i = i1 - 1 - iter;
252 const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
253 coeffs[3*i] = coeffs[3*(i+1)+1]/dx - dy/dx/dx;
254 coeffs[3*i+1] = 2*dy/dx - coeffs[3*(i+1)+1];
255 coeffs[3*i+2] = Y[i];
256 }
257
258 for (SizeT i = i2; i < n - 1; ++i) {
259 const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
260 coeffs[3*i+2] = Y[i];
261 coeffs[3*i+1] = coeffs[3*(i-1)+1] + 2*coeffs[3*(i-1)]*(X[i]-X[i-1]);
262 coeffs[3*i] = dy/dx/dx - coeffs[3*i+1]/dx;
263 }
264
265 return coeffs;
266 }
267
268 QuadraticSpline() = default;
269
270 template <typename ContainerXType>
271 QuadraticSpline(ContainerXType&& X, const Container<Number>& Y, Type type = typename Types::Default{}) {
272 construct(std::forward<ContainerXType>(X), Y, type);
273 }
274
275 QuadraticSpline(const QuadraticSpline& other) = default;
276 QuadraticSpline(QuadraticSpline&& other) noexcept = default;
277
278 QuadraticSpline& operator=(const QuadraticSpline& other) = default;
279 QuadraticSpline& operator=(QuadraticSpline&& other) noexcept = default;
280
281 template <typename ContainerXType>
282 void construct(ContainerXType&& X, const Container<Number>& Y, Type type = typename Types::Default{}) {
283 if (X.size() != Y.size()) {
284 std::cerr << "Error in function " << __FUNCTION__
285 << ": Vectors X (of size " << X.size()
286 << ") and Y (of size " << Y.size()
287 << ") are not the same size. Doing nothing..." << std::endl;
288 return;
289 }
290 auto coeffs = compute_coeffs(X, Y, type);
291 if (coeffs.empty() && !X.empty()) {
292 std::cerr << "Error in function " << __FUNCTION__
293 << ": failed to construct coefficients. Not changing the object..." << std::endl;
294 return;
295 }
296 _type = type;
297 _X = std::forward<ContainerXType>(X);
298 _coeffs = std::move(coeffs);
299 }
300
301 Number eval(Number x) const {
302 return eval(x, std::distance(_X.begin(), util::misc::first_leq_or_begin(_X.begin(), _X.end(), x)));
303 }
304 Number eval(Number x, SizeT segment) const {
305 if (_coeffs.empty()) {
306 return NAN;
307 } else if (_coeffs.size() == 1) {
308 return _coeffs[0];
309 }
310 segment = std::clamp(segment, static_cast<SizeT>(0), static_cast<SizeT>(_X.size() - 2));
311 x = x - _X[segment];
312 return (_coeffs[3*segment] * x + _coeffs[3*segment+1]) * x + _coeffs[3*segment+2];
313 }
314
315 Container<Number> eval(const Container<Number>& xx, bool sorted = true) const {
316 Container<Number> result(xx.size());
317 if (sorted) {
318 for (SizeT i = 0, i_x = 0; i < xx.size(); ++i) {
319 const Number x = xx[i];
320 while (i_x + 1 < _X.size() && x >= _X[i_x+1])
321 ++i_x;
322 result[i] = eval(x, i_x);
323 }
324 } else {
325 for (SizeT i = 0; i < xx.size(); ++i) {
326 result[i] = eval(xx[i]);
327 }
328 }
329 return result;
330 }
331
332 Number operator()(Number x) const {
333 return eval(x);
334 }
335 Container<Number> operator()(const Container<Number>& xx) const {
336 return eval(xx);
337 }
338
339 Type type() const {
340 return _type;
341 }
342
343 const Container<Number>& X() const & {
344 return _X;
345 }
346 Container<Number>&& X() && {
347 return std::move(_X);
348 }
349
350 const Container<Number>& coeffs() const & {
351 return _coeffs;
352 }
353 Container<Number>&& coeffs() && {
354 return std::move(_coeffs);
355 }
356
357 static std::pair<SizeT, SizeT> segment(SizeT index) {
358 return {3*index, 3*(index+1)};
359 }
360
361 private:
362 Type _type = typename Types::Default{};
363 Container<Number> _X = {};
364 Container<Number> _coeffs = {};
365 };
366}
QuadraticSpline(ContainerXType &&X, const Container< Number > &Y, Type type=typename Types::Default{})
Definition quadratic.h:271
Number eval(Number x) const
Definition quadratic.h:301
Container< Number > && X() &&
Definition quadratic.h:346
static Container< Number > compute_coeffs(const Container< Number > &X, const Container< Number > &Y, Type type=typename Types::Default{})
Definition quadratic.h:137
Container< Number > operator()(const Container< Number > &xx) const
Definition quadratic.h:335
const Container< Number > & X() const &
Definition quadratic.h:343
Container< Number > && coeffs() &&
Definition quadratic.h:353
QuadraticSpline & operator=(const QuadraticSpline &other)=default
void construct(ContainerXType &&X, const Container< Number > &Y, Type type=typename Types::Default{})
Definition quadratic.h:282
std::variant< typename Types::NotAKnotStart, typename Types::NotAKnotEnd, typename Types::SemiNotAKnot, typename Types::NaturalStart, typename Types::NaturalEnd, typename Types::SemiNatural, typename Types::SemiSemi, typename Types::ClampedStart, typename Types::ClampedEnd, typename Types::SemiClamped, typename Types::FixedSecondStart, typename Types::FixedSecondEnd, typename Types::SemiFixedSecond, typename Types::Clamped, typename Types::FixedSecond, typename Types::NotAKnot > Type
Definition quadratic.h:120
QuadraticSpline & operator=(QuadraticSpline &&other) noexcept=default
Number operator()(Number x) const
Definition quadratic.h:332
Container< Number > eval(const Container< Number > &xx, bool sorted=true) const
Definition quadratic.h:315
QuadraticSpline(const QuadraticSpline &other)=default
static std::pair< SizeT, SizeT > segment(SizeT index)
Definition quadratic.h:357
const Container< Number > & coeffs() const &
Definition quadratic.h:350
QuadraticSpline(QuadraticSpline &&other) noexcept=default
Number eval(Number x, SizeT segment) const
Definition quadratic.h:304
Type type() const
Definition quadratic.h:339
Definition cubic.h:13
Container< Number > mean(const Container< Number > &container1, const Container< Number > &container2)
Definition arrays.h:55
Iterator first_leq_or_begin(Iterator begin, Iterator end, const T &value)
Definition misc.h:27
Container< Number > simple_spline(const Container< Number > &X, const Container< Number > &Y, SizeT degree)
Definition spline.h:10
ALFI_DEFAULT_NUMBER DefaultNumber
Definition config.h:17
ALFI_SIZE_TYPE SizeT
Definition config.h:22
ALFI_DEFAULT_CONTAINER< Number > DefaultContainer
Definition config.h:20
ClampedEnd(Number df)
Definition quadratic.h:61
ClampedStart(Number df)
Definition quadratic.h:54
Clamped(SizeT point_idx, Number df)
Definition quadratic.h:96
SizeT point_idx
Definition quadratic.h:97
Number df
Definition quadratic.h:98
FixedSecondEnd(Number d2f)
Definition quadratic.h:82
FixedSecondStart(Number d2f)
Definition quadratic.h:75
Number d2f
Definition quadratic.h:106
SizeT segment_idx
Definition quadratic.h:105
FixedSecond(SizeT segment_idx, Number d2f)
Definition quadratic.h:104
SizeT point_idx
Definition quadratic.h:115
NotAKnot(SizeT point_idx)
Definition quadratic.h:114
SemiClamped(Number df_1, Number df_n)
Definition quadratic.h:68
SemiFixedSecond(Number d2f_1, Number d2f_n)
Definition quadratic.h:89
Definition quadratic.h:17
SemiNotAKnot Default
Definition quadratic.h:117