GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/IsotopicDataLibraryHandler.cpp
Date: 2025-11-20 01:41:33
Lines:
74/79
93.7%
Functions:
6/6
100.0%
Branches:
37/66
56.1%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2020 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the MsXpertSuite project.
10 *
11 * The MsXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Qt includes
35 #include <QDebug>
36 #include <QFile>
37
38 /////////////////////// IsoSpec
39 #include <IsoSpec++/isoSpec++.h>
40 #include <IsoSpec++/element_tables.h>
41
42
43 /////////////////////// Local includes
44 #include "MsXpS/libXpertMassCore/globals.hpp"
45 #include "MsXpS/libXpertMassCore/IsotopicDataLibraryHandler.hpp"
46
47 namespace MsXpS
48 {
49
50 namespace libXpertMassCore
51 {
52
53
54 /*!
55 \class MsXpS::libXpertMassCore::IsotopicDataLibraryHandler
56 \inmodule libXpertMassCore
57 \ingroup PolChemDefBuildingdBlocks
58 \inheaderfile IsotopicDataLibraryHandler.hpp
59
60 \brief The IsotopicDataLibraryHandler class handles \l{IsotopicData} from the
61 IsoSpec element data tables directly from the library's data. These are the
62 reference, pristine, \e{unmodified}, isotopic data.
63
64 The IsoSpec element data tables that are used in libXpertMassCore are the
65 following:
66
67 \list
68
69 \li elem_table_element
70 \li elem_table_symbol
71 \li elem_table_mass
72 \li elem_table_probability
73
74 \endlist
75
76 The data tables are all of the same length and the data in each row of a
77 given table matches the contents of that same row in all the other tables. For
78 example, the first two rows of table elem_table_ID are:
79
80 1
81
82 1
83
84 These two rows match the same rows in elem_table_mass:
85
86 1.00782503227
87
88 2.01410177819
89
90 and the the same rows in elem_table_element:
91
92 "hydrogen"
93
94 "hydrogen"
95
96 By reading, row-by-row, the data from the same row number in each one of the
97 tables, one constructs a fully qualified \l{Isotope}.
98
99 \sa IsotopicDataUserConfigHandler, IsotopicDataManualConfigHandler
100 */
101
102 /*!
103 \typedef MsXpS::libXpertMassCore::IsotopicDataLibraryHandlerSPtr
104 \relates IsotopicDataLibraryHandler
105
106 Synonym for std::shared_ptr<IsotopicDataLibraryHandler>.
107 */
108
109 /*!
110 \typedef MsXpS::libXpertMassCore::IsotopicDataLibraryHandlerCstSPtr
111 \relates IsotopicDataLibraryHandler
112
113 Synonym for std::shared_ptr<const IsotopicDataLibraryHandler>.
114 */
115
116
117 /*!
118 \brief Constructs the \l{IsotopicDataLibraryHandler}.
119
120 The instance will have empty member data.
121 */
122 69 IsotopicDataLibraryHandler::IsotopicDataLibraryHandler()
123
1/2
✓ Branch 1 taken 69 times.
✗ Branch 2 not taken.
69 : IsotopicDataBaseHandler()
124 {
125 69 }
126
127 /*!
128 \brief Constructs the \l{IsotopicDataLibraryHandler}.
129
130 The instance will have its isotopic data member pointing to \a
131 isotopic_data_sp.
132 */
133 1 IsotopicDataLibraryHandler::IsotopicDataLibraryHandler(
134 1 IsotopicDataSPtr isotopic_data_sp)
135
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 : IsotopicDataBaseHandler(isotopic_data_sp)
136 {
137 1 }
138
139 /*!
140 \brief Destructs the \l{IsotopicDataLibraryHandler}.
141
142 Nothing is explicitely deleted in the destructor.
143 */
144 142 IsotopicDataLibraryHandler::~IsotopicDataLibraryHandler()
145 {
146 // qDebug();
147 142 }
148
149 // NOT documented on purpose.
150 // GCOV_EXCL_START
151 qsizetype
152 IsotopicDataLibraryHandler::loadData([[maybe_unused]] const QString &filename)
153 {
154 qsizetype count_non_isotope_skipped_items = 0;
155 qsizetype count = loadData(count_non_isotope_skipped_items);
156
157 if(count)
158 qCritical() << "Not a single Isotope could be loaded from file.";
159
160 if(count_non_isotope_skipped_items)
161 qInfo() << "There were " << count_non_isotope_skipped_items
162 << "non-Isotope items in the tables.";
163
164 return count;
165 }
166
167 // GCOV_EXCL_STOP
168
169 /*!
170 \brief Loads isotopic data directly from IsoSpec library' element data tables.
171
172 The member isotopic data are cleared before setting new data read from the
173 library's element data tables.
174
175 The code iterates, row-by-row, in the all the tables and extracts
176 the data to fill in the Isotope data:
177
178 \code
179 IsotopeQSPtr isotope_qsp =
180 std::make_shared<Isotope>(QString(IsoSpec::elem_table_element[iter]),
181 QString(IsoSpec::elem_table_symbol[iter]),
182 IsoSpec::elem_table_mass[iter],
183 IsoSpec::elem_table_probability[iter]);
184 \endcode
185
186 Note that some rows in the table are populated with non-isotope data, like
187 electron, missing_electron or protonation (twice). When these rows are
188 encountered, the \a count_non_isotope_skipped_items is incremented.
189
190 Returns the count of \l{Isotope}s that were allocated and stored in the
191 msp_isotopicData member.
192 */
193 qsizetype
194 67 IsotopicDataLibraryHandler::loadData(qsizetype &count_non_isotope_skipped_items)
195 {
196
197 // We need to allocate one Isotope instance for each element
198 // in the various arrays in the IsoSpec++ source code header file.
199
200 // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
201 // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
202 // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
203 // extern const double
204 // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
205
206 // Big sanity check, all the arrays must be the same length!
207 67 std::size_t array_length = checkConsistency();
208
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 if(array_length < 1)
209 return false;
210
211 // qDebug() << "The size of the tables:" << array_length;
212
213 // Clear all the data, since this function might be called multiple times.
214 67 msp_isotopicData->clear();
215
216 67 std::size_t loaded_isotopes = 0;
217
2/2
✓ Branch 1 taken 19564 times.
✓ Branch 2 taken 67 times.
19631 for(std::size_t iter = 0; iter < array_length; ++iter)
218 {
219 19564 QString elem_element = QString(IsoSpec::elem_table_element[iter]);
220
221 // These are the last items in the various tables. We do not handle them
222 // at the moment.
223
4/4
✓ Branch 1 taken 19497 times.
✓ Branch 2 taken 67 times.
✓ Branch 4 taken 19430 times.
✓ Branch 5 taken 67 times.
19564 if(elem_element == "electron" || elem_element == "missing electron" ||
224
2/2
✓ Branch 1 taken 134 times.
✓ Branch 2 taken 19296 times.
19430 elem_element == "protonation" /* occurs twice */)
225 {
226 268 ++count_non_isotope_skipped_items;
227 268 continue;
228 }
229
230 19296 IsotopeQSPtr isotope_qsp = QSharedPointer<Isotope>::create(
231
1/2
✓ Branch 1 taken 19296 times.
✗ Branch 2 not taken.
38592 QString(IsoSpec::elem_table_element[iter]),
232 QString(IsoSpec::elem_table_symbol[iter]),
233 19296 IsoSpec::elem_table_mass[iter],
234
2/4
✓ Branch 1 taken 19296 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19296 times.
✗ Branch 5 not taken.
38592 IsoSpec::elem_table_probability[iter]);
235
236 // We do not want to update the mono/avg maps each time we load an
237 // isotope. We'll call the relevant function later.
238
2/4
✓ Branch 0 taken 19296 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 19296 times.
✗ Branch 4 not taken.
38592 msp_isotopicData->appendNewIsotope(isotope_qsp, /* update maps*/ false);
239 19296 ++loaded_isotopes;
240 19564 }
241
242 // Sanity check
243 67 if((loaded_isotopes + count_non_isotope_skipped_items) !=
244
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 IsoSpec::isospec_number_of_isotopic_entries)
245 qFatal(
246 "Programming error. Error loading the isotopic data from IsoSpec++'s "
247 "tables.");
248
249 // Now ask that the mono/avg mass maps be updated.
250
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
67 if(!msp_isotopicData->updateMassMaps())
251 qFatal("Programming error. Failed to update the mass maps.");
252
253 // qDebug() << "Done loading data with :" << msp_isotopicData->size()
254 // << "isotopes in the isotopic data.";
255
256 67 return msp_isotopicData->size();
257 }
258
259 /*!
260 \brief Write all the IsotopicData to \a file_name.
261
262 If \a file_name is empty, m_fileName is tried. If both are empty, the
263 function returns 0. If any one of the file names are correct (file_name takes
264 precedence over m_fileName), then m_fileName is set to that file name.
265
266 The format of the file consists in a single line of data per \l{Isotope} as
267 created using the Isotope::toString() function and
268
269 Isotope::Isotope(const QString &text, QObject *parent). Each isotope is output
270 to its own line.
271
272 Returns the count of \l{Isotope::Isotope}s written to file or 0 if the file does
273 not exist or is not readable.
274
275 \sa Isotope::Isotope and overloads
276 */
277 qsizetype
278 1 IsotopicDataLibraryHandler::writeData(const QString &file_name)
279 {
280 // Although the isotopic data were loaded from the IsoSpec library tables, we
281 // might be willing to store these data to a file.
282
283
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if(file_name.isEmpty() && m_fileName.isEmpty())
284 return 0;
285
286 1 QString temp_file_name;
287
288 // The passed filename takes precedence over the member datum. So copy
289 // that file name to the member datum.
290
291
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(!file_name.isEmpty())
292 1 temp_file_name = file_name;
293 else
294 temp_file_name = m_fileName;
295
296
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QFile file(temp_file_name);
297
298 // qDebug() << "File name to write to:" << temp_file_name;
299
300
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
301 {
302 qDebug("Failed to open file for writing.");
303 return 0;
304 }
305
306
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QTextStream out(&file);
307
308 1 out
309
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "# This file contains isotopic data in a format that can accommodate\n";
310 1 out
311
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "# comments in the form of lines beginning with the '#' character.\n\n";
312
313 1 std::size_t isotope_count = 0;
314
315
5/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 288 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 288 times.
✓ Branch 9 taken 1 times.
289 for(auto isotope_qsp : msp_isotopicData->m_isotopes)
316 {
317
2/4
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 288 times.
✗ Branch 5 not taken.
288 out << isotope_qsp->toString();
318 // We need to add it because toString() does not terminate the line with
319 // a new line character.
320
1/2
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
288 out << "\n";
321
322 288 ++isotope_count;
323 288 }
324
325
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out.flush();
326
327
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 file.close();
328
329 // Now we know that temp_file_name is fine. Store into m_fileName.
330 1 m_fileName = temp_file_name;
331
332 1 return isotope_count;
333 1 }
334
335 /*!
336 \brief Checks the consistency in all the IsoSpec library's different isotopic
337 data tables.
338
339 This function essentially verifies that each table has the same row count as
340 all the other ones.
341
342 Returns the count of isotopes in the isotopic data.
343 */
344 qsizetype
345 67 IsotopicDataLibraryHandler::checkConsistency()
346 {
347 67 qsizetype array_length = sizeof(IsoSpec::elem_table_atomicNo) /
348 sizeof(IsoSpec::elem_table_atomicNo[0]);
349
350 // qDebug() << "The array length is:" << array_length;
351
352 // All the tables in the header file of the IsoSpec library must
353 // have exactly the same size.
354
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 if(static_cast<qsizetype>(IsoSpec::isospec_number_of_isotopic_entries) != array_length)
356 {
357 qFatal(
358 "Found corruption: the size of IsoSpec arrays is not like expected.");
359 }
360
361 // Now test each table one by one.
362 67 qsizetype tested_length = sizeof(IsoSpec::elem_table_probability) /
363 sizeof(IsoSpec::elem_table_probability[0]);
364 67 if(tested_length != array_length)
365 {
366 qDebug()
367 << "Found corruption: at least two arrays are not of the same length."
368 << "tested_length:" << tested_length;
369
370 return 0;
371 }
372
373 67 tested_length =
374 sizeof(IsoSpec::elem_table_mass) / sizeof(IsoSpec::elem_table_mass[0]);
375 67 if(tested_length != array_length)
376 {
377 qDebug()
378 << "Found corruption: at least two arrays are not of the same length."
379 << "tested_length:" << tested_length;
380
381 return 0;
382 }
383
384 67 tested_length =
385 sizeof(IsoSpec::elem_table_massNo) / sizeof(IsoSpec::elem_table_massNo[0]);
386 67 if(tested_length != array_length)
387 {
388 qDebug()
389 << "Found corruption: at least two arrays are not of the same length."
390 << "tested_length:" << tested_length;
391
392 return 0;
393 }
394
395 67 tested_length = sizeof(IsoSpec::elem_table_extraNeutrons) /
396 sizeof(IsoSpec::elem_table_extraNeutrons[0]);
397 67 if(tested_length != array_length)
398 {
399 qDebug()
400 << "Found corruption: at least two arrays are not of the same length."
401 << "tested_length:" << tested_length;
402
403 return 0;
404 }
405
406 67 tested_length = sizeof(IsoSpec::elem_table_element) /
407 sizeof(IsoSpec::elem_table_element[0]);
408 67 if(tested_length != array_length)
409 {
410 qDebug()
411 << "Found corruption: at least two arrays are not of the same length."
412 << "tested_length:" << tested_length;
413
414 return 0;
415 }
416
417 67 tested_length =
418 sizeof(IsoSpec::elem_table_symbol) / sizeof(IsoSpec::elem_table_symbol[0]);
419 67 if(tested_length != array_length)
420 {
421 qDebug()
422 << "Found corruption: at least two arrays are not of the same length."
423 << "tested_length:" << tested_length;
424
425 return 0;
426 }
427
428 67 tested_length = sizeof(IsoSpec::elem_table_Radioactive) /
429 sizeof(IsoSpec::elem_table_Radioactive[0]);
430 67 if(tested_length != array_length)
431 {
432 qDebug()
433 << "Found corruption: at least two arrays are not of the same length."
434 << "tested_length:" << tested_length;
435
436 return 0;
437 }
438
439 67 tested_length = sizeof(IsoSpec::elem_table_log_probability) /
440 sizeof(IsoSpec::elem_table_log_probability[0]);
441 67 if(tested_length != array_length)
442 {
443 qDebug()
444 << "Found corruption: at least two arrays are not of the same length."
445 << "tested_length:" << tested_length;
446
447 return 0;
448 }
449
450 67 return tested_length;
451 }
452
453
454 } // namespace libXpertMassCore
455
456 } // namespace MsXpS
457
458
459 #if 0
460
461 Example from IsoSpec.
462
463 const int elementNumber = 2;
464 const int isotopeNumbers[2] = {2,3};
465
466 const int atomCounts[2] = {2,1};
467
468
469 const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
470 const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};
471
472 const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};
473
474 const double hydrogen_probs[2] = {0.5, 0.5};
475 const double oxygen_probs[3] = {0.5, 0.3, 0.2};
476
477 const double* probs[2] = {hydrogen_probs, oxygen_probs};
478
479 IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts,
480 isotope_masses, probs), 0.99);
481
482 #endif
483