libpappsomspp
Library for mass spectrometry
Loading...
Searching...
No Matches
cubicsplinemodel.cpp
Go to the documentation of this file.
1// Copyright 2026 Filippo Rusconi
2// Inspired by work by Lars Nilse in OpenMS
3
4
5/////////////////////// stdlib includes
6#include <limits>
7#include <algorithm>
8
9
10/////////////////////// Qt includes
11#include <QList>
12#include <QDebug>
13
14
15/////////////////////// pappsomspp includes
16
17
18/////////////////////// Local includes
20
21namespace pappso
22{
23
25{
26 // qDebug() << "Allocating:" << this;
27}
28
29CubicSplineModel::CubicSplineModel(const QList<double> &x_values,
30 const QList<double> &y_values)
31{
32 // qDebug() << "Allocating:" << this;
33
34 Q_ASSERT(x_values.size() == y_values.size());
35 Q_ASSERT(x_values.size() >= 2);
36
37 double eps = std::numeric_limits<double>::epsilon();
38
39 // Ensure the x_values are sorted increasingly.
40
41 Q_ASSERT(std::adjacent_find(
42 x_values.cbegin(), x_values.cend(), [=](double a, double b) {
43 return !(b > a + eps);
44 }) == x_values.cend());
45
46 setup(x_values, y_values);
47
48 // qDebug() << "Number of knots:" << m_knots.size();
49}
50
51// Use delegating constructor.
52CubicSplineModel::CubicSplineModel(const QMap<double, double> &x_y_values_map)
53 : CubicSplineModel(x_y_values_map.keys(), x_y_values_map.values())
54{
55}
56
58{
59 // qDebug() << "Allocating:" << this;
64 m_knots = other.m_knots;
65}
66
69{
70 // qDebug() << "Cloning.";
71 CubicSplineModel *copy_p = new CubicSplineModel();
72
73 copy_p->m_name = other.m_name;
74 copy_p->m_constCoeffs = other.m_constCoeffs;
75 copy_p->m_linearCoeffs = other.m_linearCoeffs;
77 copy_p->m_cubicCoeffs = other.m_cubicCoeffs;
78 copy_p->m_knots = other.m_knots;
79
80 return copy_p;
81}
82
84{
85 // qDebug() << "Destructing the cubic spline model";
86}
87
88void
89CubicSplineModel::setup(const QList<double> &x_values,
90 const QList<double> &y_values)
91{
92 // qDebug() << "Setting up with x_values size:" << x_values.size();
93
94 Q_ASSERT(x_values.size() == y_values.size());
95
96 const size_t n = x_values.size() - 1;
97
98 std::vector<double> h;
99 h.reserve(n);
100 m_constCoeffs.reserve(n);
101 m_knots.reserve(n + 1);
102 // do the 0'th element manually, since the loop below only starts at 1
103 h.push_back(x_values[1] - x_values[0]);
104 m_knots.push_back(x_values[0]);
105 m_constCoeffs.push_back(y_values[0]);
106
107 std::vector<double> mu(n, 0.0);
108 std::vector<double> z(n, 0.0);
109 for(unsigned i = 1; i < n; ++i)
110 {
111 h.push_back(x_values[i + 1] - x_values[i]);
112 const double l =
113 2 * (x_values[i + 1] - x_values[i - 1]) - h[i - 1] * mu[i - 1];
114 mu[i] = h[i] / l;
115 z[i] = (3 *
116 (y_values[i + 1] * h[i - 1] -
117 y_values[i] * (x_values[i + 1] - x_values[i - 1]) +
118 y_values[i - 1] * h[i]) /
119 (h[i - 1] * h[i]) -
120 h[i - 1] * z[i - 1]) /
121 l;
122 // store x,y -- required for evaluation later on
123 m_knots.push_back(x_values[i]);
124 m_constCoeffs.push_back(y_values[i]);
125 }
126 // 'm_knots' needs to be full length (all other member vectors (except
127 // m_quadraticCoeffs) are one element shorter)
128 m_knots.push_back(x_values[n]);
129
130 m_linearCoeffs.resize(n);
131 m_cubicCoeffs.resize(n);
132 m_quadraticCoeffs.resize(n + 1);
133 m_quadraticCoeffs.back() = 0;
134 for(int j = static_cast<int>(n) - 1; j >= 0; --j)
135 {
136 m_quadraticCoeffs[j] = z[j] - mu[j] * m_quadraticCoeffs[j + 1];
137 m_linearCoeffs[j] =
138 (y_values[j + 1] - y_values[j]) / h[j] -
139 h[j] * (m_quadraticCoeffs[j + 1] + 2 * m_quadraticCoeffs[j]) / 3;
140 m_cubicCoeffs[j] =
141 (m_quadraticCoeffs[j + 1] - m_quadraticCoeffs[j]) / (3 * h[j]);
142 }
143 // qDebug() << "m_knots size:" << m_knots.size();
144}
145
148{
149 if(&other == this)
150 return *this;
151
152 m_name = other.m_name;
157 m_knots = other.m_knots;
158
159 return *this;
160}
161
162const QList<double> &
164{
165 return m_knots;
166}
167
168double
170{
171 Q_ASSERT(x_value >= m_knots.front() || x_value <= m_knots.back());
172
173 // What is the index of the closest knot left of x_value (or the same).
174 unsigned i = static_cast<unsigned>(
175 std::lower_bound(m_knots.begin(), m_knots.end(), x_value) -
176 m_knots.begin());
177
178 if(m_knots[i] > x_value || m_knots.back() == x_value)
179 {
180 --i;
181 }
182
183 const double delta_x = x_value - m_knots[i];
184
185 double eval_value =
186 ((m_cubicCoeffs[i] * delta_x + m_quadraticCoeffs[i]) * delta_x +
187 m_linearCoeffs[i]) *
188 delta_x +
189 m_constCoeffs[i];
190
191 // qDebug() << "Now returning evaluation value:" << eval_value;
192
193 return eval_value;
194}
195
196double
197CubicSplineModel::derivative(const double x_value) const
198{
199 return derivatives(x_value, 1);
200}
201
202double
203CubicSplineModel::derivatives(const double x_value, unsigned order) const
204{
205 // qDebug() << "Size of m_knots:" << m_knots.size();
206
207 Q_ASSERT(x_value >= m_knots.front() && x_value <= m_knots.back());
208
209 Q_ASSERT(order >= 1 && order <= 3);
210
211 // determine index of closest node left of (or exactly at) x_value
212 unsigned i = static_cast<unsigned>(
213 std::lower_bound(m_knots.begin(), m_knots.end(), x_value) -
214 m_knots.begin());
215
216 if(m_knots[i] > x_value ||
217 m_knots.back() ==
218 x_value) // also, i must not point to last index in 'm_knots',
219 // since all other vectors are one element shorter
220 {
221 --i;
222 }
223
224 const double delta_x = x_value - m_knots[i];
225 if(order == 1)
226 {
227 return m_linearCoeffs[i] + 2 * m_quadraticCoeffs[i] * delta_x +
228 3 * m_cubicCoeffs[i] * delta_x * delta_x;
229 }
230 else if(order == 2)
231 {
232 return 2 * m_quadraticCoeffs[i] + 6 * m_cubicCoeffs[i] * delta_x;
233 }
234 else
235 {
236 return 6 * m_cubicCoeffs[i];
237 }
238}
239
240void
242 double const mz_at_left,
243 double const mz_at_right,
244 double &center_peak_mz,
245 double &center_peak_intensity,
246 double const threshold)
247{
248 // qDebug() << "spline model has" << spline_model.getKnots().size();
249
250 // calculate maximum by evaluating the spline's 1st derivative
251 // (bisection method)
252 double lefthand = mz_at_left;
253 double righthand = mz_at_right;
254
255 bool lefthand_sign = true;
256 double eps = std::numeric_limits<double>::epsilon();
257
258 // bisection
259 do
260 {
261 double mid = (lefthand + righthand) / 2.0;
262 double midpoint_deriv_val = spline_model.derivative(mid);
263
264 // if deriv nearly zero then maximum already found
265 if(!(std::fabs(midpoint_deriv_val) > eps))
266 {
267 break;
268 }
269
270 bool midpoint_sign = (midpoint_deriv_val < 0.0) ? false : true;
271
272 if(lefthand_sign ^ midpoint_sign)
273 {
274 righthand = mid;
275 }
276 else
277 {
278 lefthand = mid;
279 }
280 }
281 while(righthand - lefthand > threshold);
282
283 center_peak_mz = (lefthand + righthand) / 2;
284
285 center_peak_intensity = spline_model.evalSplineAt(center_peak_mz);
286
287 // qDebug() << "center_peak_intensity:" << center_peak_intensity;
288}
289
290} // namespace pappso
QList< double > m_quadraticCoeffs
void setup(const QList< double > &x_values, const QList< double > &y_values)
CubicSplineModel & operator=(const CubicSplineModel &other)
QList< double > m_linearCoeffs
double derivatives(const double x_value, unsigned order) const
double derivative(const double x_value) const
const QList< double > & getKnots() const
QList< double > m_cubicCoeffs
QList< double > m_constCoeffs
CubicSplineModel * clone(const CubicSplineModel &other)
double evalSplineAt(double x_value) const
tries to keep as much as possible monoisotopes, removing any possible C13 peaks and changes multichar...
Definition aa.cpp:39
void spline_bisection(const CubicSplineModel &spline_model, double const mz_at_left, double const mz_at_right, double &center_peak_mz, double &center_peak_intensity, double const threshold)