Skip to content

Commit b913b68

Browse files
authored
Added new quadratic spline types and unified calculations (#30)
1. Added "ClampedStart", "ClampedEnd", "SemiClamped", "FixedSecondStart", "FixedSecondEnd", "SemiFixedSecond", "Clamped", "FixedSecond", and "NotAKnot" quadratic spline types. 2. Added minimal documentation for these types. 3. Unified calculations in `QuadraticSpline::compute_coeffs`.
1 parent 61a30e3 commit b913b68

File tree

3 files changed

+199
-94
lines changed

3 files changed

+199
-94
lines changed

ALFI/ALFI/spline/cubic.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ namespace alfi::spline {
7070
*/
7171
struct NotAKnotEnd final {};
7272
/**
73-
The arithmetic mean_array between NotAKnotStart and NotAKnotEnd.
73+
The arithmetic mean of NotAKnotStart and NotAKnotEnd.
7474
*/
7575
struct SemiNotAKnot final {};
7676
using Default = Natural;

ALFI/ALFI/spline/quadratic.h

Lines changed: 187 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace alfi::spline {
2828
*/
2929
struct NotAKnotEnd final {};
3030
/**
31-
The arithmetic mean_array between NotAKnotStart and NotAKnotEnd.
31+
The arithmetic mean of NotAKnotStart and NotAKnotEnd.
3232
*/
3333
struct SemiNotAKnot final {};
3434
/**
@@ -40,13 +40,80 @@ namespace alfi::spline {
4040
*/
4141
struct NaturalEnd final {};
4242
/**
43-
The arithmetic mean_array between NaturalStart and NaturalEnd.
43+
The arithmetic mean of NaturalStart and NaturalEnd.
4444
*/
4545
struct SemiNatural final {};
4646
/**
47-
The arithmetic mean_array between SemiNotAKnot and SemiNatural.
47+
The arithmetic mean of SemiNotAKnot and SemiNatural.
4848
*/
4949
struct SemiSemi final {};
50+
/**
51+
The first derivative equals `df` at the first point.
52+
*/
53+
struct ClampedStart final {
54+
explicit ClampedStart(Number df) : df(df) {}
55+
Number df;
56+
};
57+
/**
58+
The first derivative equals `df` at the last point.
59+
*/
60+
struct ClampedEnd final {
61+
explicit ClampedEnd(Number df) : df(df) {}
62+
Number df;
63+
};
64+
/**
65+
The arithmetic mean of ClampedStart and ClampedEnd.
66+
*/
67+
struct SemiClamped final {
68+
SemiClamped(Number df_1, Number df_n) : df_1(df_1), df_n(df_n) {}
69+
Number df_1, df_n;
70+
};
71+
/**
72+
The second derivative equals `d2f` on the first segment.
73+
*/
74+
struct FixedSecondStart final {
75+
explicit FixedSecondStart(Number d2f) : d2f(d2f) {}
76+
Number d2f;
77+
};
78+
/**
79+
The second derivative equals `d2f` on the last segment.
80+
*/
81+
struct FixedSecondEnd final {
82+
explicit FixedSecondEnd(Number d2f) : d2f(d2f) {}
83+
Number d2f;
84+
};
85+
/**
86+
The arithmetic mean of FixedSecondStart and FixedSecondEnd.
87+
*/
88+
struct SemiFixedSecond final {
89+
SemiFixedSecond(Number d2f_1, Number d2f_n) : d2f_1(d2f_1), d2f_n(d2f_n) {}
90+
Number d2f_1, d2f_n;
91+
};
92+
/**
93+
The first derivative equals `df` at the point with index `point_idx`.
94+
*/
95+
struct Clamped final {
96+
Clamped(SizeT point_idx, Number df) : point_idx(point_idx), df(df) {}
97+
SizeT point_idx;
98+
Number df;
99+
};
100+
/**
101+
The second derivative equals `d2f` on the segment with index `segment_idx`.
102+
*/
103+
struct FixedSecond final {
104+
FixedSecond(SizeT segment_idx, Number d2f) : segment_idx(segment_idx), d2f(d2f) {}
105+
SizeT segment_idx;
106+
Number d2f;
107+
};
108+
/**
109+
The second derivative is continuous at the point with index `point_idx`.\n
110+
This also means, that the second derivative is the same on the segments with indices `point_idx-1` and `point_idx`.\n
111+
If number of points is less than or equal to 2, then `point_idx` is ignored.
112+
*/
113+
struct NotAKnot final {
114+
explicit NotAKnot(SizeT point_idx) : point_idx(point_idx) {}
115+
SizeT point_idx;
116+
};
50117
using Default = SemiNotAKnot;
51118
};
52119

@@ -56,7 +123,16 @@ namespace alfi::spline {
56123
typename Types::NaturalStart,
57124
typename Types::NaturalEnd,
58125
typename Types::SemiNatural,
59-
typename Types::SemiSemi>;
126+
typename Types::SemiSemi,
127+
typename Types::ClampedStart,
128+
typename Types::ClampedEnd,
129+
typename Types::SemiClamped,
130+
typename Types::FixedSecondStart,
131+
typename Types::FixedSecondEnd,
132+
typename Types::SemiFixedSecond,
133+
typename Types::Clamped,
134+
typename Types::FixedSecond,
135+
typename Types::NotAKnot>;
60136

61137
static Container<Number> compute_coeffs(const auto& X, const auto& Y, Type type = typename Types::Default{}) {
62138
if (X.size() != Y.size()) {
@@ -73,97 +149,115 @@ namespace alfi::spline {
73149
return util::spline::simple_spline<Number,Container>(X, Y, 2);
74150
}
75151

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)) {
163+
return util::arrays::mean(compute_coeffs(X, Y, typename Types::NaturalStart{}), compute_coeffs(X, Y, typename Types::NaturalEnd{}));
164+
} else if (std::holds_alternative<typename Types::SemiSemi>(type)) {
165+
return util::arrays::mean(compute_coeffs(X, Y, typename Types::SemiNotAKnot{}), compute_coeffs(X, Y, typename Types::SemiNatural{}));
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+
76180
Container<Number> coeffs(3 * (n - 1));
77181

78-
std::visit(util::misc::overload{
79-
[&](const typename Types::NotAKnotStart&) {
80-
if (n <= 3) {
81-
coeffs = util::spline::simple_spline<Number,Container>(X, Y, 2);
82-
return;
83-
}
84-
{ // first three points
85-
const Number h1 = X[1] - X[0], h2 = X[2] - X[1];
86-
const Number d1 = (Y[1] - Y[0]) / h1, d2 = (Y[2] - Y[1]) / h2;
87-
coeffs[0] = (d2 - d1) / (h1 + h2); // a1
88-
coeffs[1] = d1 - h1 * coeffs[0]; // b1
89-
coeffs[2] = Y[0]; // c1
90-
coeffs[3] = coeffs[0]; // a2
91-
coeffs[4] = d1 + h1 * coeffs[0]; // b2
92-
coeffs[5] = Y[1]; // c2
93-
}
94-
for (SizeT i = 2; i < n - 1; ++i) {
95-
const Number hi = X[i+1] - X[i], hi_1 = X[i] - X[i-1];
96-
const Number d = 2 * coeffs[3*(i-1)] * hi_1 + coeffs[3*(i-1)+1];
97-
coeffs[3*i] = ((Y[i+1] - Y[i])/hi - d) / hi;
98-
coeffs[3*i+1] = d;
99-
coeffs[3*i+2] = Y[i];
100-
}
101-
},
102-
[&](const typename Types::NotAKnotEnd&) {
103-
if (n <= 3) {
104-
coeffs = util::spline::simple_spline<Number,Container>(X, Y, 2);
105-
return;
106-
}
107-
{ // last three points
108-
const Number hn_2 = X[n-2] - X[n-3], hn_1 = X[n-1] - X[n-2];
109-
const Number dn_2 = (Y[n-2] - Y[n-3]) / hn_2, dn_1 = (Y[n-1] - Y[n-2]) / hn_1;
110-
coeffs[3*(n-3)] = (dn_1 - dn_2) / (hn_2 + hn_1); // an-2
111-
coeffs[3*(n-3)+1] = dn_2 - hn_2 * coeffs[3*(n-3)]; // bn-2
112-
coeffs[3*(n-3)+2] = Y[n-3]; // cn-2
113-
coeffs[3*(n-2)] = coeffs[3*(n-3)]; // an-1
114-
coeffs[3*(n-2)+1] = dn_2 + hn_2 * coeffs[3*(n-3)]; // bn-1
115-
coeffs[3*(n-2)+2] = Y[n-2]; // cn-1
116-
}
117-
for (SizeT iter = 0; iter <= n - 4; ++iter) {
118-
const SizeT i = n - 4 - iter;
119-
const Number hi = X[i+1] - X[i];
120-
coeffs[3*i] = (coeffs[3*(i+1)+1] - (Y[i+1] - Y[i])/hi) / hi;
121-
coeffs[3*i+1] = coeffs[3*(i+1)+1] - 2*coeffs[3*i]*hi;
122-
// another way: coeffs[3*i+1] = (Y[i+1] - Y[i])/hi - coeffs[3*i]*hi;
123-
coeffs[3*i+2] = Y[i];
124-
}
125-
},
126-
[&](const typename Types::SemiNotAKnot&) {
127-
coeffs = util::arrays::mean(compute_coeffs(X, Y, typename Types::NotAKnotStart{}), compute_coeffs(X, Y, typename Types::NotAKnotEnd{}));
128-
},
129-
[&](const typename Types::NaturalStart&) {
130-
if (n <= 2) {
131-
coeffs = util::spline::simple_spline<Number,Container>(X, Y, 2);
132-
return;
133-
}
134-
coeffs[0] = 0; // a1 = 0
135-
coeffs[1] = (Y[1] - Y[0]) / (X[1] - X[0]); // b1
136-
coeffs[2] = Y[0]; // c1
137-
for (SizeT i = 1; i < n - 1; ++i) {
138-
coeffs[3*i+1] = 2 * (Y[i] - Y[i-1]) / (X[i] - X[i-1]) - coeffs[3*(i-1)+1];
139-
coeffs[3*i] = ((Y[i+1] - Y[i]) / (X[i+1] - X[i]) - coeffs[3*i+1]) / (X[i+1] - X[i]);
140-
// another way: coeffs[3*i] = (coeffs[3*(i+1)+1] - coeffs[3*i+1]) / 2 / (X[i+1] - X[i]);
141-
coeffs[3*i+2] = Y[i];
142-
}
143-
},
144-
[&](const typename Types::NaturalEnd&) {
145-
if (n <= 2) {
146-
coeffs = util::spline::simple_spline<Number,Container>(X, Y, 2);
147-
return;
148-
}
149-
coeffs[3*(n-2)] = 0; // an-1 = 0
150-
coeffs[3*(n-2)+1] = (Y[n-1] - Y[n-2]) / (X[n-1] - X[n-2]); // bn-1
151-
coeffs[3*(n-2)+2] = Y[n-2]; // cn-1
152-
for (SizeT iter = 0; iter <= n - 3; ++iter) {
153-
const SizeT i = n - 3 - iter;
154-
coeffs[3*i+1] = 2 * (Y[i+1] - Y[i]) / (X[i+1] - X[i]) - coeffs[3*(i+1)+1];
155-
// another way: coeffs[3*i] = ((Y[i+1] - Y[i]) / (X[i+1] - X[i]) - coeffs[3*i+1]) / (X[i+1] - X[i]);
156-
coeffs[3*i] = (coeffs[3*(i+1)+1] - coeffs[3*i+1]) / 2 / (X[i+1] - X[i]);
157-
coeffs[3*i+2] = Y[i];
158-
}
159-
},
160-
[&](const typename Types::SemiNatural&) {
161-
coeffs = util::arrays::mean(compute_coeffs(X, Y, typename Types::NaturalStart{}), compute_coeffs(X, Y, typename Types::NaturalEnd{}));
162-
},
163-
[&](const typename Types::SemiSemi&) {
164-
coeffs = util::arrays::mean(compute_coeffs(X, Y, typename Types::SemiNotAKnot{}), compute_coeffs(X, Y, typename Types::SemiNatural{}));
165-
},
166-
}, type);
182+
SizeT i1 = 0, i2 = n - 1;
183+
184+
if (const auto* clamped = std::get_if<typename Types::Clamped>(&type)) {
185+
const auto i = clamped->point_idx;
186+
if (i < 0 || i >= n) {
187+
std::cerr << "Error in function " << __FUNCTION__
188+
<< ": point index for type 'Clamped' is out of bounds:"
189+
<< "expected to be non-negative and less than " << n << ", but got " << i
190+
<< ". Returning an empty array..." << std::endl;
191+
return {};
192+
}
193+
if (i > 0) {
194+
const auto dx1 = X[i] - X[i-1], dy1 = Y[i] - Y[i-1];
195+
coeffs[3*(i-1)+2] = Y[i-1];
196+
coeffs[3*(i-1)+1] = 2*dy1/dx1 - clamped->df;
197+
coeffs[3*(i-1)] = dy1/dx1/dx1 - coeffs[3*(i-1)+1]/dx1;
198+
i1 = i - 1;
199+
}
200+
if (i < n - 1) {
201+
const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
202+
coeffs[3*i+2] = Y[i];
203+
coeffs[3*i+1] = clamped->df;
204+
coeffs[3*i] = dy/dx/dx - coeffs[3*i+1]/dx;
205+
i2 = i;
206+
}
207+
} else if (const auto* fixed_second = std::get_if<typename Types::FixedSecond>(&type)) {
208+
const auto i = fixed_second->segment_idx;
209+
if (i < 0 || i >= n - 1) {
210+
std::cerr << "Error in function " << __FUNCTION__
211+
<< ": point index for type 'FixedSecond' is out of bounds:"
212+
<< "expected to be non-negative and less than " << n - 1 << ", but got " << i
213+
<< ". Returning an empty array..." << std::endl;
214+
return {};
215+
}
216+
coeffs[3*i] = fixed_second->d2f / 2;
217+
coeffs[3*i+1] = (Y[i+1]-Y[i])/(X[i+1]-X[i]) - coeffs[3*i]*(X[i+1]-X[i]);
218+
coeffs[3*i+2] = Y[i];
219+
i1 = i2 = i;
220+
} else if (const auto* not_a_knot = std::get_if<typename Types::NotAKnot>(&type)) {
221+
if (n <= 2) {
222+
return util::spline::simple_spline<Number,Container>(X, Y, 2);
223+
}
224+
const auto i = not_a_knot->point_idx;
225+
if (i < 1 || i >= n - 1) {
226+
std::cerr << "Error in function " << __FUNCTION__
227+
<< ": point index for type 'NotAKnot' is out of bounds:"
228+
<< "expected to be positive and less than " << n - 1 << ", but got " << i
229+
<< ". Returning an empty array..." << std::endl;
230+
return {};
231+
}
232+
const auto dx1 = X[i] - X[i-1], dy1 = Y[i] - Y[i-1];
233+
const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
234+
coeffs[3*(i-1)+2] = Y[i-1];
235+
coeffs[3*i+2] = Y[i];
236+
coeffs[3*(i-1)] = coeffs[3*i] = (dy/dx - dy1/dx1) / (dx1 + dx);
237+
coeffs[3*(i-1)+1] = dy1/dx1 - coeffs[3*(i-1)]*dx1;
238+
coeffs[3*i+1] = dy/dx - coeffs[3*i]*dx;
239+
i1 = i - 1;
240+
i2 = i;
241+
} else {
242+
std::cerr << "Error in function " << __FUNCTION__ << ": Unknown type. This should not have happened."
243+
<< " Please report this to the developers. Returning an empty array..." << std::endl;
244+
return {};
245+
}
246+
247+
for (SizeT iter = 0; iter < i1; ++iter) {
248+
const auto i = i1 - 1 - iter;
249+
const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
250+
coeffs[3*i] = coeffs[3*(i+1)+1]/dx - dy/dx/dx;
251+
coeffs[3*i+1] = 2*dy/dx - coeffs[3*(i+1)+1];
252+
coeffs[3*i+2] = Y[i];
253+
}
254+
255+
for (SizeT i = i2 + 1; i < n - 1; ++i) {
256+
const auto dx = X[i+1] - X[i], dy = Y[i+1] - Y[i];
257+
coeffs[3*i+2] = Y[i];
258+
coeffs[3*i+1] = coeffs[3*(i-1)+1] + 2*coeffs[3*(i-1)]*(X[i]-X[i-1]);
259+
coeffs[3*i] = dy/dx/dx - coeffs[3*i+1]/dx;
260+
}
167261

168262
return coeffs;
169263
}

examples/interpolation/interpolation.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ class PlotWindow final : public QWidget {
5454
{"Natural end", alfi::spline::QuadraticSpline<>::Types::NaturalEnd{}},
5555
{"Semi-natural", alfi::spline::QuadraticSpline<>::Types::SemiNatural{}},
5656
{"Semi-semi", alfi::spline::QuadraticSpline<>::Types::SemiSemi{}},
57+
{"Clamped-start(10)", alfi::spline::QuadraticSpline<>::Types::ClampedStart{10}},
58+
{"Clamped-start(-10)", alfi::spline::QuadraticSpline<>::Types::ClampedStart{-10}},
59+
{"Clamped-end(10)", alfi::spline::QuadraticSpline<>::Types::ClampedEnd{10}},
60+
{"Clamped-end(-10)", alfi::spline::QuadraticSpline<>::Types::ClampedEnd{-10}},
61+
{"Semi-clamped(10, 10)", alfi::spline::QuadraticSpline<>::Types::SemiClamped{10, 10}},
62+
{"Fixed-second-start(10)", alfi::spline::QuadraticSpline<>::Types::FixedSecondStart{10}},
63+
{"Fixed-second-end(10)", alfi::spline::QuadraticSpline<>::Types::FixedSecondEnd{10}},
64+
{"Semi-fixed-second(10, 10)", alfi::spline::QuadraticSpline<>::Types::SemiFixedSecond{10, 10}},
65+
{"Clamped(10, 10)", alfi::spline::QuadraticSpline<>::Types::Clamped{10, 10}},
66+
{"Fixed-second(10, 10)", alfi::spline::QuadraticSpline<>::Types::FixedSecond{10, 10}},
67+
{"Not-a-knot(10)", alfi::spline::QuadraticSpline<>::Types::NotAKnot{10}},
5768
};
5869
static const inline size_t quadratic_spline_default_type_index = 2;
5970

0 commit comments

Comments
 (0)