GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/IsotopicData.cpp
Date: 2025-11-20 01:41:33
Lines:
276/358
77.1%
Functions:
36/41
87.8%
Branches:
127/268
47.4%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2024 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 /////////////////////// Std lib includes
35 #include <limits>
36 #include <set>
37
38 /////////////////////// Qt includes
39 #include <QDebug>
40
41
42 /////////////////////// IsoSpec
43 #include <IsoSpec++/isoSpec++.h>
44 #include <IsoSpec++/element_tables.h>
45
46
47 // extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
48 // extern const double
49 // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
50 // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
51 // extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
52 // extern const int
53 // elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
54 // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
55 // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
56 // extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
57 // extern const double
58 // elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
59
60
61 /////////////////////// Local includes
62 #include "MsXpS/libXpertMassCore/Utils.hpp"
63 #include "MsXpS/libXpertMassCore/Isotope.hpp"
64 #include "MsXpS/libXpertMassCore/IsotopicData.hpp"
65
66 namespace MsXpS
67 {
68
69 namespace libXpertMassCore
70 {
71
72 /*!
73 \class MsXpS::libXpertMassCore::IsotopicData
74 \inmodule libXpertMassCore
75 \ingroup PolChemDefBuildingdBlocks
76 \inheaderfile IsotopicData.hpp
77
78 \brief The IsotopicData class provides a collection of \l{Isotope}s and
79 associated methods to access them in various ways.
80
81 The IsotopicData class provides a collection of \l{Isotope}s and
82 provides methods to access them in various ways. Methods are available to
83 return the monoisotopic mass of an isotope or the average mass calculated from
84 the data of all the isotopes listed for a given chemical element.
85 */
86
87 /*!
88 \variable MsXpS::libXpertMassCore::IsotopicData::m_isotopes
89
90 \brief The vector of \l{MsXpS::libXpertMassCore::IsotopeQSPtr}.
91
92 The vector should never be sorted as we want to keep the order of the
93 isotopes in the way the vector has been populated, either by looking into
94 the IsoSpec library tables or by reading data from a user-configured file.
95 */
96
97 /*!
98 \variable MsXpS::libXpertMassCore::IsotopicData::m_symbolMonoMassMap
99
100 \brief The map relating the Isotope::m_symbol to the monoisotopic mass.
101 */
102
103 /*!
104 \variable MsXpS::libXpertMassCore::IsotopicData::m_symbolAvgMassMap
105
106 \brief The map relating the Isotope::m_symbol to the average mass.
107 */
108
109 /*!
110 \variable MsXpS::libXpertMassCore::IsotopicData::m_isValid
111
112 \brief The validity status of the IsotopicData instance.
113
114 \sa isValid(), validate()
115 */
116
117
118 /*!
119 \typedef MsXpS::libXpertMassCore::IsotopicDataSPtr
120 \relates IsotopicData
121
122 Synonym for std::shared_ptr<IsotopicData>.
123 */
124
125 /*!
126 \typedef MsXpS::libXpertMassCore::IsotopicDataCstSPtr
127 \relates IsotopicData
128
129 Synonym for std::shared_ptr<const IsotopicData>.
130 */
131
132 using IsotopeListCstIterator = QList<IsotopeQSPtr>::const_iterator;
133 using IsotopeListIterator = QList<IsotopeQSPtr>::iterator;
134 using IsotopeListCstIteratorPair =
135 std::pair<IsotopeListCstIterator, IsotopeListCstIterator>;
136
137 /*!
138 \typealias MsXpS::libXpertMassCore::IsotopeListCstIterator
139
140 Alias for QList<IsotopeQSPtr>::const_iterator.
141 */
142
143 /*!
144 \typealias MsXpS::libXpertMassCore::IsotopeListIterator
145
146 Alias for QList<IsotopeQSPtr>::iterator.
147 */
148
149 /*!
150 \typealias MsXpS::libXpertMassCore::IsotopeListCstIteratorPair
151
152 Alias for std::pair<IsotopeListCstIterator, IsotopeListCstIterator>.
153 */
154
155 /*!
156 \brief Constructs the \l{IsotopicData}.
157
158 The instance will have empty member data.
159 */
160 198 IsotopicData::IsotopicData(QObject *parent): QObject(parent)
161 {
162 198 }
163
164 /*!
165 \brief Constructs the \l{IsotopicData} as a copy of \a other.
166
167 This is a deep copy with all the data in the containers copied from \a other
168 to this IsotopicData.
169 */
170 2 IsotopicData::IsotopicData(const IsotopicData &other, QObject *parent)
171 : QObject(parent),
172 4 m_isotopes(other.m_isotopes),
173
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 m_symbolMonoMassMap(other.m_symbolMonoMassMap),
174
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
4 m_symbolAvgMassMap(other.m_symbolAvgMassMap)
175 {
176 2 }
177
178 /*!
179 \brief Destructs the \l{IsotopicData}.
180
181 Nothing is explicitely deleted in the destructor.
182 */
183 264 IsotopicData::~IsotopicData()
184 {
185 // qDebug();
186 264 }
187
188 /*!
189 \brief Appends a new \l{IsotopeQSPtr} to this \l{IsotopicData}.
190
191 \a isotope_qsp The new isotope to be added to this collection. The isotope is
192 added to the end of the collection using
193
194 \code
195 m_isotopes.push_back(isotope_qsp);
196 \endcode
197
198 Each time a new isotope is added to this collection, the chemical
199 signification of the corresponding chemical element changes at heart. It
200 might thus be required that the data in the two m_symbolMonoMassMap and
201 m_symbolAvgMassMap maps be recalculated.
202
203 \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
204 to be updated with the new collection of isotopes.
205
206 \sa updateMassMaps(), appendNewIsotopes()
207 */
208 void
209 40623 IsotopicData::appendNewIsotope(IsotopeQSPtr isotope_qsp, bool update_maps)
210 {
211 // We may append an unvalid isotope, but then that changes the status
212 // of these isotopic data.
213
214 40623 QVector<QString> error_list;
215
1/2
✓ Branch 1 taken 40623 times.
✗ Branch 2 not taken.
40623 m_isValid = isotope_qsp->validate(&error_list);
216
217
1/2
✓ Branch 1 taken 40623 times.
✗ Branch 2 not taken.
40623 m_isotopes.push_back(isotope_qsp);
218
219 // We have modified the fundamental data, we may need to recompute some data.
220 // update_maps might be false when loading data from a file, in which case it
221 // is the responsibility of the user to call updateMassMaps() at the end of
222 // the file loading.
223
224
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 40619 times.
40623 if(update_maps)
225
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 updateMassMaps();
226 40623 }
227
228 /*!
229 \brief Appends a collection of new \l{IsotopeQSPtr} to this \l{IsotopicData}.
230
231 \a isotopes The collection (<vector>) of new isotopes to be added to this
232 collection. The isotope is added to the end of the collection using
233
234 \code
235 m_isotopes.insert(m_isotopes.end(), isotopes.begin(), isotopes.end());
236 \endcode
237
238 Each time new isotopes are added to this collection, the chemical
239 signification of all the corresponding chemical elements changes at heart. It
240 might thus be required that the data in the two m_symbolMonoMassMap and
241 m_symbolAvgMassMap maps be recalculated.
242
243 Internally, this function calls <vector>.insert() to append all the isotopes in
244 \a isotopes to the end of m_isotopes.
245
246 \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
247 to be updated with the new collection of isotopes.
248
249 \sa updateMassMaps(), appendNewIsotope()
250 */
251 void
252 44 IsotopicData::appendNewIsotopes(const QList<IsotopeQSPtr> &isotopes,
253 bool update_maps)
254 {
255 44 qsizetype count_before = m_isotopes.size();
256
257 44 m_isotopes.append(isotopes.begin(), isotopes.end());
258
259
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 qsizetype count_after = m_isotopes.size();
260
261
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if(count_after - count_before != isotopes.size())
262 qFatal("Programming error.");
263
264
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 24 times.
44 if(update_maps)
265 20 updateMassMaps();
266 44 }
267
268 /*!
269 \brief Inserts a new \l{IsotopeQSPtr} to this \l{IsotopicData} at index
270 \a index.
271
272 \a isotope_qsp The new isotope to be inserted in this collection.
273
274 If \a index is out of bounds or this collection is empty, the isotope is
275 appended to this collection. Otherwise, the isotope is inserted at the
276 requested index, which means that the new isotope displaces to the bottom
277 (aka back) the isotope currently at \a index.
278
279 Each time a new isotope is added to this collection, the chemical
280 signification of the corresponding chemical element changes at heart. It
281 might thus be required that the data in the two m_symbolMonoMassMap and
282 m_symbolAvgMassMap maps be recalculated.
283
284 \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
285 to be updated with the new collection of isotopes.
286
287 Returns true if the iterator at the inserted position is not m_isotopes.end().
288
289 \sa updateMassMaps(), appendNewIsotope(), appendNewIsotopes()
290 */
291 bool
292 2 IsotopicData::insertNewIsotope(IsotopeQSPtr isotope_qsp,
293 qsizetype index,
294 bool update_maps)
295 {
296 // qDebug() << "the size of the data:" << size() << "and" << m_isotopes.size()
297 //<< "requested index:" << index;
298
299 // We define that we insert the new isotope before the one at index, as is the
300 // convention in the STL and in Qt code.
301
302
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if(!m_isotopes.size() || index > m_isotopes.size() - 1)
303 {
304 appendNewIsotope(isotope_qsp, update_maps);
305 return true;
306 }
307
308 // Convert the index to an iterator.
309
310 2 QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.begin() + index;
311
312 // Finally, do the insertion.
313
314
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 iter = m_isotopes.insert(iter, isotope_qsp);
315
316 // qDebug() << "Inserted isotope:" << (*iter)->getSymbol();
317
318 // If inserting an empty isotope in relation to a row insertion in the table
319 // view, then update_maps needs to be false because update_maps needs valid
320 // symbols for isotopes!
321
322
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(update_maps)
323 2 updateMassMaps();
324
325 // iter points to the inserted isotope.
326 2 return iter != m_isotopes.end();
327 }
328
329 /*!
330 \brief Removes the isotopes located between \a begin_index and \a end_index.
331
332 The removed isotopes are contained inclusively between the two indices passed
333 as parameters.
334
335 Each time isotopes are removed from this collection, the chemical
336 signification of the corresponding chemical elements changes at heart. It
337 might thus be required that the data in the two m_symbolMonoMassMap and
338 m_symbolAvgMassMap maps be recalculated.
339
340 \a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
341 to be updated with the new collection of isotopes.
342
343 Returns an iterator to the end of this collection if either \a begin_index is
344 out of bounds or this collection is empty. Otherwise, returns an iterator to
345 the collection at the position below the last removed item.
346 */
347 QList<IsotopeQSPtr>::const_iterator
348 3 IsotopicData::eraseIsotopes(qsizetype begin_index,
349 qsizetype end_index,
350 bool update_maps)
351 {
352
353 // qDebug() << "Erasing isotopes in inclusive index range: [" << begin_index
354 //<< "-" << end_index << "] - range is fully inclusive.";
355
356
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
3 if(!m_isotopes.size() || begin_index > m_isotopes.size() - 1)
357 2 return m_isotopes.cend();
358
359 1 QList<IsotopeQSPtr>::const_iterator iter_begin =
360
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_isotopes.cbegin() + begin_index;
361
362 // Let's say that by default, we remove until the last inclusively:
363
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();
364
365 // But, if end_index is less than the last index, then end() has to be the
366 // next item after the one at that index.
367
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(end_index < m_isotopes.size() - 1)
368 1 iter_end = std::next(m_isotopes.begin() + end_index);
369
370 // At this point we are confident we can assign the proper end() iterator
371 // value for the erase function below.
372
373 1 auto iter = m_isotopes.erase(iter_begin, iter_end);
374
375
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(update_maps)
376 {
377 // qDebug() << "Now updating masses";
378 1 updateMassMaps();
379 }
380 // else
381 // qDebug() << "Not updating masses";
382
383 #if 0
384 if(m_isotopes.size())
385 {
386 qDebug() << "The avg mass of the first isotope symbol in the vector:"
387 << computeAvgMass(
388 getIsotopesBySymbol(m_isotopes.front()->getSymbol()));
389 }
390 #endif
391
392 1 return iter;
393 }
394
395 /*!
396 \brief Redefines the monoisotopic mass of the chemical element specified by \a
397 symbol.
398
399 For the set of isotopes corresponding to \a symbol, set the most
400 abundant isotope's mass as the value for key \a symbol in m_symbolMonoMassMap.
401
402 Returns true if the map pair was actually inserted in m_symbolMonoMassMap or
403 false if the monoisotopic mass value was set to an existing key.
404 */
405 bool
406 11925 IsotopicData::updateMonoMassMap(const QString &symbol)
407 {
408 // We do only work with a single symbol here.
409
410 // GCOVR_EXCL_START
411 if(symbol.isEmpty())
412 qFatal("Programming error. The symbol cannot be empty.");
413 // GCOVR_EXCL_STOP
414
415 11925 double greatest_abundance = std::numeric_limits<double>::min();
416 11925 double mass = 0.0;
417
418 11925 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
419
420
2/2
✓ Branch 0 taken 40774 times.
✓ Branch 1 taken 11925 times.
52699 while(iter_pair.first != iter_pair.second)
421 {
422
2/2
✓ Branch 1 taken 25608 times.
✓ Branch 2 taken 15166 times.
40774 if((*iter_pair.first)->getProbability() > greatest_abundance)
423 {
424 25608 greatest_abundance = (*iter_pair.first)->getProbability();
425 25608 mass = (*iter_pair.first)->getMass();
426 }
427
428 40774 ++iter_pair.first;
429 }
430
431 // At this point we have the mono mass of the currently iterated symbol.
432
2/2
✓ Branch 0 taken 11755 times.
✓ Branch 1 taken 170 times.
11925 bool key_found = m_symbolMonoMassMap.contains(symbol);
433 11925 m_symbolMonoMassMap.insert(symbol, mass);
434
435 11925 return key_found;
436 }
437
438 /*!
439 \brief Redefines the monoisotopic mass of all the chemical elements in this
440 collection of isotopes.
441
442 This function is generally called by default by all the functions that add
443 new isotopes to this collection [via updateMassMaps()].
444
445 First, a list of all the unique element symbols in this collection is
446 crafted. Then for each symbol in that list, updateMonoMassMap(symbol) is called.
447
448 Returns the number of updated symbols, that is, the unique symbol count in
449 this collection.
450
451 \sa updateMonoMassMap(const QString &symbol), updateMassMaps()
452 */
453 std::size_t
454 183 IsotopicData::updateMonoMassMap()
455 {
456 // For all the common chemical elements found in organic substances, the
457 // monoisotopic mass is the mass of the most abundant isotope which
458 // happens to be also the lightest isotope. However that is not true for
459 // *all* the chemical elements. We thus need to iterate in the isotopes
460 // of each symbol in the vector of isotopes and record the mass of the
461 // isotope that is most abundant.
462
463 183 m_symbolMonoMassMap.clear();
464
465 // Get the list of all the isotope symbols.
466
467 183 std::size_t count = 0;
468
469 183 std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();
470
471
3/4
✓ Branch 0 taken 11923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11923 times.
✓ Branch 3 taken 183 times.
12106 for(auto symbol : all_symbols)
472 {
473
1/2
✓ Branch 1 taken 11923 times.
✗ Branch 2 not taken.
11923 updateMonoMassMap(symbol);
474 11923 ++count;
475 11923 }
476
477 183 return count;
478 183 }
479
480 /*!
481 \brief Recalculates the average mass of the chemical element specified by \a
482 symbol.
483
484 For the set of isotopes corresponding to \a symbol, compute the average mass
485 and set it in m_symbolAvgMassMap as the value for key \a symbol.
486
487 Returns true if the map pair was actually inserted in m_symbolAvgMassMap or
488 false if the average mass value was set to an already existing key.
489
490 \sa updateMonoMassMap(const QString &symbol), updateMonoMassMap(),
491 updateAvgMassMap(const QString &symbol), updateMassMaps()
492 */
493 bool
494 11925 IsotopicData::updateAvgMassMap(const QString &symbol)
495 {
496 // For each chemical element (that is either name or symbol), we need to
497 // compute the sum of the probabilities of all the corresponding
498 // isotopes. Once that sum (which should be 1) is computed, it is
499 // possible to compute the averag mass of "that symbol", so to say.
500
501 // We do only work with a single symbol here.
502
503 // GCOVR_EXCL_START
504 if(symbol.isEmpty())
505 qFatal("Programming error. The symbol cannot be empty.");
506 // GCOVR_EXCL_STOP
507
508 #if 0
509
510 // Now this is in a function per se:
511 // computeAvgMass(IsotopeListCstIteratorPair iter_pair, bool *ok)
512
513 double cumulated_probabilities = 0.0;
514 double avg_mass = 0.0;
515
516 IsotopeListCstIteratorPair pair = getIsotopesBySymbol(symbol);
517
518 // We need to use that iterator twice, so we do make a copy.
519
520 IsotopeCstIterator local_iter = pair.first;
521
522 while(local_iter != pair.second)
523 {
524 cumulated_probabilities += (*pair.first)->getProbability();
525
526 ++local_iter;
527 }
528
529 // Sanity check
530 if(!cumulated_probabilities)
531 qFatal("Programming error. The cumulated probabilities cannot be naught.");
532
533 // And at this point we can compute the average mass.
534
535 local_iter = pair.first;
536
537 while(local_iter != pair.second)
538 {
539 avg_mass += (*local_iter)->getMass() *
540 ((*local_iter)->getProbability() / cumulated_probabilities);
541
542 ++local_iter;
543 }
544 #endif
545
546 11925 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
547
548 // qDebug() << "For symbol" << symbol << "the iter range was found to
549 // be of distance:"
550 //<< std::distance(iter_pair.first, iter_pair.second)
551 //<< "with symbol: " << (*iter_pair.first)->getSymbol();
552
553 11925 ErrorList error_list;
554
555
1/2
✓ Branch 1 taken 11925 times.
✗ Branch 2 not taken.
11925 double avg_mass = computeAvgMass(iter_pair, &error_list);
556 // GCOVR_EXCL_START
557 if(error_list.size())
558 {
559 qFatal(
560 "The calculation of the average mass for a given "
561 "symbol failed.");
562 }
563 // GCOVR_EXCL_STOP
564
565
2/2
✓ Branch 0 taken 11755 times.
✓ Branch 1 taken 170 times.
11925 bool key_found = m_symbolAvgMassMap.contains(symbol);
566
1/2
✓ Branch 1 taken 11925 times.
✗ Branch 2 not taken.
11925 m_symbolAvgMassMap.insert(symbol, avg_mass);
567
568 11925 return key_found;
569 11925 }
570
571 /*!
572 \brief Recalculates the average mass of all the chemical elements in this
573 collection of isotopes.
574
575 This function is generally called by default by all the functions that add
576 new isotopes to this collection [via updateMassMaps()].
577
578 First, a list of all the unique element symbols in this collection is
579 crafted. Then for each symbol in that list, updateAvgMassMap(symbol) is called.
580
581 Returns the number of updated symbols, that is, the unique symbol count in
582 this collection.
583
584 \sa updateMonoMassMap(const QString &symbol), updateMassMaps()
585 */
586 std::size_t
587 183 IsotopicData::updateAvgMassMap()
588 {
589 // For each chemical element (that is either name or symbol), we need to
590 // compute the sum of the probabilities of all the corresponding
591 // isotopes. Once that sum (which should be 1) is computed, it is
592 // possible to compute the averag mass of "that symbol", so to say.
593
594 183 m_symbolAvgMassMap.clear();
595
596 // Get the list of all the isotope symbols.
597
598 183 std::size_t count = 0;
599
600 183 std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();
601
602
3/4
✓ Branch 0 taken 11923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11923 times.
✓ Branch 3 taken 183 times.
12106 for(auto symbol : all_symbols)
603 {
604
1/2
✓ Branch 1 taken 11923 times.
✗ Branch 2 not taken.
11923 updateAvgMassMap(symbol);
605 11923 ++count;
606 11923 }
607
608 183 return count;
609 183 }
610
611 /*!
612 \brief Compute the average mass for isotopes contained in the \a
613 iter_pair iterator range.
614
615 \a iter_pair pair of [begin -- end[ iterators to the isotopes in this
616 collection
617
618 \a error_list_p vector of strings in which to store error messages
619
620 There are no sanity checks performed. The iterator pair should hold two
621 iterator values that frame isotopes of the same chemical element.
622
623 The average mass is computed on the basis of the isotopes contained in the
624 [\a iter_pair .first -- \a iter_pair .second[ range.
625
626 Returns 0 if the first member of \a iter_pair is the collection's end
627 iterator, the average mass otherwise.
628
629 */
630 double
631 11930 IsotopicData::computeAvgMass(IsotopeListCstIteratorPair iter_pair,
632 ErrorList *error_list_p) const
633 {
634 // We get an iterator range for which we need to compute the average
635 // mass. No check whatsoever, we do what we are asked to do. This
636 // function is used to check or document user's actions in some places.
637
638 11930 double avg_mass = 0.0;
639
640 // qDebug() << "Computing avg mass for iter range of distance:"
641 //<< std::distance(iter_pair.first, iter_pair.second)
642 //<< "with symbol: " << (*iter_pair.first)->getSymbol();
643
644
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 11930 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11930 times.
23860 if(iter_pair.first == m_isotopes.cend())
645 {
646 qDebug() << "First iterator is actually end of m_isotopes.";
647 error_list_p->push_back(
648 QString("First iterator is actually end of m_isotopes."));
649
650 return avg_mass;
651 }
652
653 11930 qsizetype previous_error_count = error_list_p->size();
654
655 11930 double cumulated_probabilities =
656 11930 getCumulatedProbabilities(iter_pair, error_list_p);
657
658
2/4
✓ Branch 0 taken 11930 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 11930 times.
11930 if(error_list_p->size() > previous_error_count || !cumulated_probabilities)
659 {
660 // There was an error. We want to report it.
661
662 error_list_p->push_back(
663 QString("Failed to compute the cumulated probabilities needed to "
664 "compute the average mass."));
665
666 return avg_mass;
667 }
668
669 // At this point, compute the average mass.
670
671
2/2
✓ Branch 0 taken 40784 times.
✓ Branch 1 taken 11930 times.
52714 while(iter_pair.first != iter_pair.second)
672 {
673 81568 avg_mass +=
674 40784 (*iter_pair.first)->getMass() *
675 40784 ((*iter_pair.first)->getProbability() / cumulated_probabilities);
676
677 // qDebug() << "avg_mass:" << avg_mass;
678
679 40784 ++iter_pair.first;
680 }
681
682 return avg_mass;
683 }
684
685 /*!
686 \brief Update the monoisotopic and average symbol-mass maps only for \a
687 symbol.
688
689 \sa updateMonoMassMap(const QString &symbol), updateAvgMassMap(const QString
690 &symbol)
691 */
692 void
693 2 IsotopicData::updateMassMaps(const QString &symbol)
694 {
695 2 updateMonoMassMap(symbol);
696 2 updateAvgMassMap(symbol);
697 2 }
698
699 /*!
700 \brief Update the monoisotopic and average symbol-mass maps for all the
701 symbols in the collection.
702
703 This function is typically called each time new isotopes are added to this
704 collection.
705
706 Returns the count of updated symbols, that is, the unique symbol count in this
707 collection.
708
709 \sa updateMonoMassMap(), updateAvgMassMap()
710 */
711 std::size_t
712 183 IsotopicData::updateMassMaps()
713 {
714 183 std::size_t count_mono = updateMonoMassMap();
715
716 183 if(!count_mono)
717 183 qDebug("There are no isotopes. Cleared the mono mass map.");
718
719 183 std::size_t count_avg = updateAvgMassMap();
720
721 183 if(!count_avg)
722 183 qDebug("There are no isotopes. Cleared the avg mass map.");
723
724
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 183 times.
183 if(count_mono != count_avg)
725 qFatal("Programming error.");
726
727 // Number of symbols for which the mass was updated.
728 183 return count_mono;
729 }
730
731 /*!
732 \brief Returns the monoisotopic mass for element of \a symbol.
733
734 Returns 0 if \a symbol was not found in this Isotope collection and sets \a
735 ok to false if \a ok is not nullptr; returns the monoisotopic mass for element
736 \a symbol otherwise and sets \a ok to true if \a ok is not nullptr.
737 */
738 double
739 249881 IsotopicData::getMonoMassBySymbol(const QString &symbol, bool &ok) const
740 {
741
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249881 times.
249881 if(symbol.isEmpty())
742 qFatal("Programming error. The symbol cannot be empty.");
743
744 // qDebug() << "The symbol/mono mass map has size:"
745 //<< m_symbolMonoMassMap.size();
746
747 249881 QMap<QString, double>::const_iterator found_iter =
748
2/2
✓ Branch 0 taken 249879 times.
✓ Branch 1 taken 2 times.
249881 m_symbolMonoMassMap.find(symbol);
749
750
4/4
✓ Branch 0 taken 249879 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 249879 times.
499760 if(found_iter == m_symbolMonoMassMap.cend())
751 {
752 2 qDebug() << "Failed to find the symbol in the map.";
753
754 2 ok = false;
755 2 return 0.0;
756 }
757
758 249879 ok = true;
759
760 // qDebug() << "The mono mass is found to be" << found_iter->second;
761
762 249879 return found_iter.value();
763 }
764
765 /*!
766 \brief Returns the mass of the most abundant isotope in a range of isotopes.
767
768 The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair
769 .first -- \a iter_pair .second [.
770
771 If errors are encountered, these are appended to \a error_list_p.
772
773 For all the common chemical elements found in organic substances, the
774 monoisotopic mass is the mass of the most abundant isotope which
775 happens to be also the lightest isotope. However that is not true for
776 *all* the chemical elements. We thus need to iterate in the isotopes
777 of each symbol in the vector of isotopes and record the mass of the
778 isotope that is most abundant.
779 */
780 double
781 5 IsotopicData::getMonoMass(IsotopeListCstIteratorPair iter_pair,
782 ErrorList *error_list_p) const
783 {
784 // The mono mass of a set of isotopes is the mass of the most abundant
785 // isotope (not the lightest!).
786
787 5 double mass = 0.0;
788 5 double greatest_abundance = 0.0;
789
790
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if(iter_pair.first == m_isotopes.cend())
791 {
792 qDebug() << "First iterator is actually end of m_isotopes.";
793
794 error_list_p->push_back(
795 QString("First iterator is actually end of m_isotopes."));
796
797 return mass;
798 }
799
800
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
15 while(iter_pair.first != iter_pair.second)
801 {
802
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
10 if((*iter_pair.first)->getProbability() > greatest_abundance)
803 {
804 5 greatest_abundance = (*iter_pair.first)->getProbability();
805 5 mass = (*iter_pair.first)->getMass();
806 }
807
808 10 ++iter_pair.first;
809 }
810
811 // qDebug() << "The mono mass is found to be" << mass;
812
813 return mass;
814 }
815
816 /*!
817 \brief Returns the average mass of \a symbol.
818
819 The returned mass is found as the value for key \a symbol in
820 m_symbolAvgMassMap. If \a ok is not nullptr, it is set to true.
821
822 If the symbol is not found, 0 is returned and \a ok is set to false if \a ok
823 is not nullptr.
824 */
825 double
826 249884 IsotopicData::getAvgMassBySymbol(const QString &symbol, bool &ok) const
827 {
828
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249884 times.
249884 if(symbol.isEmpty())
829 qFatal("Programming error. The symbol cannot be empty.");
830
831 // qDebug() << "The symbol/avg mass map has size:" <<
832 // m_symbolAvgMassMap.size();
833
834 249884 QMap<QString, double>::const_iterator found_iter =
835
2/2
✓ Branch 0 taken 249882 times.
✓ Branch 1 taken 2 times.
249884 m_symbolAvgMassMap.find(symbol);
836
837
4/4
✓ Branch 0 taken 249882 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 249882 times.
499766 if(found_iter == m_symbolAvgMassMap.cend())
838 {
839 2 qDebug() << "Failed to find the symbol in the map.";
840
841 2 ok = false;
842 2 return 0.0;
843 }
844
845 249882 ok = true;
846
847 // qDebug() << "The avg mass is found to be" << found_iter->second;
848
849 249882 return found_iter.value();
850 }
851
852 /*!
853 \brief Returns the sum of the probabilities of all the isotopes of \a
854 symbol.
855
856 If errors occur, they will be described as strings appended in \a error_list_p.
857 */
858 double
859 11 IsotopicData::getCumulatedProbabilitiesBySymbol(const QString &symbol,
860 ErrorList *error_list_p) const
861 {
862 // qDebug() << "symbol: " << symbol;
863
864 11 double cumulated_probabilities = 0.0;
865
866 // We'll need this to calculate the indices of the isotopes in the
867 // m_isotopes vector.
868 11 IsotopeListCstIterator iter_begin = m_isotopes.cbegin();
869
870 // Get the isotopes iter range for symbol.
871 11 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
872
873
874
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 11 times.
31 while(iter_pair.first != iter_pair.second)
875 {
876 20 QString error_text;
877 20 ErrorList iter_error_list;
878
879
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 bool result = (*iter_pair.first)->validate(&iter_error_list);
880
881
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if(!result)
882 {
883 error_text = QString("Isotope symbol %1 at index %2:\n%3")
884 .arg(symbol)
885 .arg(std::distance(iter_begin, iter_pair.first))
886 .arg(Utils::joinErrorList(iter_error_list, "\n"));
887 error_list_p->push_back(error_text);
888 cumulated_probabilities = 0.0;
889 }
890 else
891
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 cumulated_probabilities += (*iter_pair.first)->getProbability();
892
893 20 ++iter_pair.first;
894 20 }
895
896 11 return cumulated_probabilities;
897 }
898
899 /*!
900 \brief Returns the sum of the probabilities of all the isotopes in the \a
901 iter_pair range of iterators.
902
903 The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair
904 .first -- \a iter_pair .second [.
905
906 If errors are encountered, these are appended to \a error_list_p.
907 */
908 double
909 11935 IsotopicData::getCumulatedProbabilities(IsotopeListCstIteratorPair iter_pair,
910 ErrorList *error_list_p) const
911 {
912 11935 double cumulated_probabilities = 0.0;
913
914
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11935 times.
11935 if(iter_pair.first == m_isotopes.cend())
915 {
916 qWarning() << "First iterator is actually end of m_isotopes.";
917
918 error_list_p->push_back(
919 QString("First iterator is actually end of m_isotopes."));
920
921 return cumulated_probabilities;
922 }
923
924
2/2
✓ Branch 0 taken 40794 times.
✓ Branch 1 taken 11935 times.
52729 while(iter_pair.first != iter_pair.second)
925 {
926 40794 cumulated_probabilities += (*iter_pair.first)->getProbability();
927
928 40794 ++iter_pair.first;
929 }
930
931 // qDebug() << "cumulated_probabilities:" << cumulated_probabilities;
932
933 return cumulated_probabilities;
934 }
935
936 /*!
937 \brief Returns a range of iterators framing the isotopes of \a symbol.
938
939 \note The order of the isotopes in the collection is not alphabetical (it
940 is the order of the atomic number. This function works on the assumption that
941 all the isotopes of a given symbol are clustered together in the isotopes
942 vector with *no* gap in between.
943
944 If \a symbol is empty, the iterators are set to be end() of the
945 Isotopes collection. The returned pair of iterators frame the isotopes of
946 \a symbol as a [begin,end[ pair of iterators.
947 */
948 IsotopeListCstIteratorPair
949 545933 IsotopicData::getIsotopesBySymbol(const QString &symbol) const
950 {
951
952
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 545933 times.
545933 if(symbol.isEmpty())
953 return IsotopeListCstIteratorPair(m_isotopes.cend(), m_isotopes.cend());
954
955 // qDebug() << "The symbol by which isotopes are being searched for: " <<
956 // symbol;
957
958 // We want to "extract" from the vector of isotopes, the ones that share
959 // the same symbol under the form of a [begin,end[ pair of iterators.
960
961 //////////////////////////// ASSUMPTION /////////////////////////
962 //////////////////////////// ASSUMPTION /////////////////////////
963 // The order of the isotopes is not alphabetical (it is the order of the
964 // atomic number. This function works on the assumption that all the
965 // isotopes of a given symbol are clustered together in the isotopes
966 // vector with *no* gap in between.
967 //////////////////////////// ASSUMPTION /////////////////////////
968 //////////////////////////// ASSUMPTION /////////////////////////
969
970 // We will start iterating in the vector of isotopes at the very
971 // beginning.
972 545933 QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.cbegin();
973
974 // Never reach the end of the isotopes vector.
975 545933 QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();
976
977 // This iterator will be the end iterator of the range that comprises
978 // the isotopes all sharing the same symbol. We initialize it to
979 // iter_end in case we do not find the symbol at all. Otherwise it will
980 // be set to the right value.
981 545933 QList<IsotopeQSPtr>::const_iterator symbol_iter_end = iter_end;
982
983
2/2
✓ Branch 0 taken 8063461 times.
✓ Branch 1 taken 6 times.
8063467 while(iter != iter_end)
984 {
985 8063461 QString current_symbol = (*iter)->getSymbol();
986
987 // qDebug() << "First loop iteration in isotope with symbol:"
988 // << current_symbol;
989
990
2/2
✓ Branch 0 taken 7517534 times.
✓ Branch 1 taken 545927 times.
8063461 if(current_symbol != symbol)
991 {
992 // qDebug() << "Current isotope has symbol" << current_symbol
993 //<< "and we are looking for" << symbol
994 //<< "incrementing to next position.";
995 7517534 ++iter;
996 }
997 else
998 {
999 // qDebug() << "Current isotope has symbol" << current_symbol
1000 //<< "and we are looking for" << symbol
1001 //<< "with mass:" << (*iter)->getMass()
1002 //<< "Now starting inner iteration loop.";
1003
1004 // At this point we encountered one isotope that has the right
1005 // symbol. The iterator "iter" will not change anymore because
1006 // of the inner loop below that will go on iterating in vector
1007 // using another set of iterators. "iter" will thus point
1008 // correctly to the first isotope in the vector having the right
1009 // symbol.
1010
1011 // Now in this inner loop, continue iterating in the vector,
1012 // starting at the present position and go on as long as the
1013 // encountered isotopes have the same symbol.
1014
1015 // Set then end iterator to the current position and increment
1016 // to the next one, since current position has been iterated
1017 // into already (position is stored in "iter") and go on. This
1018 // way, if there was a single isotope by given symbol,
1019 // "symbol_iter_end" rightly positions at the next isotope. If
1020 // that is not the case, its value updates and is automatically
1021 // set to the first isotope that has not the right symbol (or
1022 // will be set to iter_end if that was the last set of isotopes
1023 // in the vector).
1024
1025 545927 symbol_iter_end = iter;
1026 545927 ++symbol_iter_end;
1027
1028
2/2
✓ Branch 0 taken 1272658 times.
✓ Branch 1 taken 399 times.
1273057 while(symbol_iter_end != iter_end)
1029 {
1030 // qDebug() << "Second loop iteration in: "
1031 //<< (*symbol_iter_end)->getSymbol()
1032 //<< "while we search for" << symbol
1033 //<< "Iterated isotope has mass:"
1034 //<< (*symbol_iter_end)->getMass();
1035
1036
3/4
✓ Branch 1 taken 1272658 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 727130 times.
✓ Branch 5 taken 545528 times.
1272658 if((*symbol_iter_end)->getSymbol() == symbol)
1037 {
1038 // We can iterate further in the isotopes vector because
1039 // the current iterator pointed to an isotope that still
1040 // had the right symbol. qDebug()
1041 //<< "Good symbol, going to next inner iteration
1042 // position.";
1043 727130 ++symbol_iter_end;
1044 }
1045 else
1046 {
1047 // We currently iterate in an isotope that has a symbol
1048 // different from the searched one: the symbol_iter_end
1049 // thus effectively plays the role of the
1050 // iterator::end() of the isotopes range having the
1051 // proper symbol.
1052
1053 // qDebug() << "The symbols do not match, breaking the inner
1054 // loop.";
1055 break;
1056 }
1057 }
1058
1059 // We can break the outer loop because we have necessarily gone
1060 // through the isotopes of the requested symbol at this point.
1061 // See at the top of this outer loop that when an isotope has
1062 // not the right symbol, the iter is incremented.
1063 545927 break;
1064 }
1065 // End of block
1066 // else of if(current_symbol != symbol)
1067 8063461 }
1068 // End of outer
1069 // while(iter != iter_end)
1070
1071 // qDebug() << "For symbol" << symbol << "the iter range was found to be:"
1072 //<< std::distance(iter, symbol_iter_end);
1073
1074 545933 return IsotopeListCstIteratorPair(iter, symbol_iter_end);
1075 }
1076
1077 /*!
1078 \brief Returns the count of isotopes of \a symbol.
1079 */
1080 qsizetype
1081 32 IsotopicData::getIsotopeCountBySymbol(const QString &symbol) const
1082 {
1083 32 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
1084
1085 32 return std::distance(iter_pair.first, iter_pair.second);
1086 }
1087
1088 /*!
1089 \brief Returns a range of iterators framing the isotopes of element \a name.
1090
1091 \note The order of the isotopes in the collection is not alphabetical (it
1092 is the order of the atomic number. This function works on the assumption that
1093 all the isotopes of a given symbol are clustered together in the isotopes
1094 vector with *no* gap in between.
1095
1096 If \a name is empty, the iterators are set to be end() of the
1097 Isotopes collection. The returned pair of iterators frame the isotopes of
1098 \a name as a [begin,end[ pair of iterators.
1099 */
1100 IsotopeListCstIteratorPair
1101 3 IsotopicData::getIsotopesByName(const QString &name) const
1102 {
1103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(name.isEmpty())
1104 return IsotopeListCstIteratorPair(m_isotopes.cend(), m_isotopes.cend());
1105
1106 // qDebug() << "The name by which isotopes are being searched for: " << name;
1107
1108 // We want to "extract" from the vector of isotopes, the ones that share
1109 // the same name under the form of a [begin,end[ pair of iterators.
1110
1111 //////////////////////////// ASSUMPTION /////////////////////////
1112 //////////////////////////// ASSUMPTION /////////////////////////
1113 // The order of the isotopes is not alphabetical (it is the order of the
1114 // atomic number. This function works on the assumption that all the
1115 // isotopes of a given symbol are clustered together in the isotopes
1116 // vector with *no* gap in between.
1117 //////////////////////////// ASSUMPTION /////////////////////////
1118 //////////////////////////// ASSUMPTION /////////////////////////
1119
1120 // We will start iterating in the vector of isotopes at the very
1121 // beginning.
1122 3 QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.cbegin();
1123
1124 // Never reach the end of the isotopes vector.
1125 3 QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();
1126
1127 // This iterator will be the end iterator of the range that comprises
1128 // the isotopes all sharing the same name. We initialize it to iter_end
1129 // in case we do not find the name at all. Otherwise it will be set to
1130 // the right value.
1131 3 QList<IsotopeQSPtr>::const_iterator name_iter_end = iter_end;
1132
1133
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 while(iter != iter_end)
1134 {
1135 5 QString current_name = (*iter)->getName();
1136
1137 // qDebug() << "First loop iteration in isotope with name:" <<
1138 // current_name;
1139
1140
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if(current_name != name)
1141 {
1142 // qDebug() << "Current isotope has name" << current_name
1143 //<< "and we are looking for" << name
1144 //<< "incrementing to next position.";
1145 2 ++iter;
1146 }
1147 else
1148 {
1149 // qDebug() << "Current isotope has name" << current_name
1150 //<< "and we are looking for" << name
1151 //<< "with mass:" << (*iter)->getMass()
1152 //<< "Now starting inner iteration loop.";
1153
1154 // At this point we encountered one isotope that has the right name.
1155 // The iterator "iter" will not change anymore because of the inner
1156 // loop below that will go on iterating in vector using another set
1157 // of iterators. "iter" will thus point correctly to the first
1158 // isotope in the vector having the right name.
1159
1160 // Now in this inner loop, continue iterating in the vector,
1161 // starting at the present position and go on as long as the
1162 // encountered isotopes have the same name.
1163
1164 // Set then end iterator to the current position and increment to
1165 // the next one, since current position has been iterated into
1166 // already (position is stored in "iter") and go on. This way, if
1167 // there was a single isotope by given name, "name_iter_end" rightly
1168 // positions at the next isotope. If that is not the case, its value
1169 // updates and is automatically set to the first isotope that has
1170 // not the right name (or will be set to iter_end if that was the
1171 // last set of isotopes in the vector).
1172
1173 3 name_iter_end = iter;
1174 3 ++name_iter_end;
1175
1176
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 while(name_iter_end != iter_end)
1177 {
1178 // qDebug() << "Second loop iteration in: "
1179 //<< (*name_iter_end)->getName()
1180 //<< "while we search for" << name
1181 //<< "Iterated isotope has mass:"
1182 //<< (*name_iter_end)->getMass();
1183
1184
3/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
5 if((*name_iter_end)->getName() == name)
1185 {
1186 // We can iterate further in the isotopes vector because the
1187 // current iterator pointed to an isotope that still had the
1188 // right name.
1189 // qDebug() << "Going to next iterator position.";
1190 3 ++name_iter_end;
1191 }
1192 else
1193 {
1194 // We currently iterate in an isotope that has a name
1195 // different from the searched one: the name_iter_end thus
1196 // effectively plays the role of the iterator::end() of the
1197 // isotopes range having the proper name.
1198
1199 // qDebug() << "The names do not match, breaking the inner
1200 // loop.";
1201 break;
1202 }
1203 }
1204
1205 // We can break the outer loop because we have necessarily gone
1206 // through the isotopes of the requested name at this point.
1207 // See at the top of this outer loop that when an isotope has
1208 // not the right name, the iter is incremented.
1209 3 break;
1210 }
1211 // End of block
1212 // else of if(current_name != name)
1213 5 }
1214 // End of outer
1215 // while(iter != iter_end)
1216
1217 // qDebug() << "For name" << name << "the iter range was found to be:"
1218 //<< std::distance(iter, name_iter_end);
1219
1220 3 return IsotopeListCstIteratorPair(iter, name_iter_end);
1221 }
1222
1223 /*!
1224 \brief Returns all the unique symbols from the collection as they are stored.
1225 */
1226 std::vector<QString>
1227 376 IsotopicData::getUniqueSymbolsInOriginalOrder() const
1228 {
1229 // The way IsoSpec works and the way we configure the
1230 // symbol/count/isotopes data depend in some situations on the order in
1231 // which the Isotopes were read either from the library tables or from
1232 // user-created disk files.
1233 //
1234 // This function wants to craft a list of unique isotope symbols exactly
1235 // as they are found in the member vector.
1236
1237 376 std::set<QString> symbol_set;
1238
1239 376 std::vector<QString> symbols;
1240
1241
3/4
✓ Branch 0 taken 81575 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 81575 times.
✓ Branch 3 taken 376 times.
81951 for(auto isotope_qsp : m_isotopes)
1242 {
1243
1/2
✓ Branch 1 taken 81575 times.
✗ Branch 2 not taken.
81575 QString symbol = isotope_qsp->getSymbol();
1244
1245
1/2
✓ Branch 1 taken 81575 times.
✗ Branch 2 not taken.
81575 auto res = symbol_set.insert(symbol);
1246 // res.second is true if the symbol was inserted, which mean it did
1247 // not exist in the set.
1248
2/2
✓ Branch 0 taken 23857 times.
✓ Branch 1 taken 57718 times.
81575 if(res.second)
1249
1/2
✓ Branch 1 taken 23857 times.
✗ Branch 2 not taken.
23857 symbols.push_back(symbol);
1250 81575 }
1251
1252 376 return symbols;
1253 376 }
1254
1255 /*!
1256 \brief Returns true if the collection contains at least one isotope of \a
1257 symbol, false otherwise.
1258
1259 If \a count is not nullptr, its value is set to the count of isotopes of \a
1260 symbol or unchanged if no isotopes by \a symbol were found.
1261 */
1262 bool
1263 522034 IsotopicData::containsSymbol(const QString &symbol, int &count) const
1264 {
1265 522034 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
1266
1267
2/2
✓ Branch 0 taken 522028 times.
✓ Branch 1 taken 6 times.
522034 if(iter_pair.first == m_isotopes.cend())
1268 return false;
1269
1270 // Now compute the distance between the two iterators to know how many
1271 // isotopes share the same chemical element symbol.
1272
1273 522028 count = std::distance(iter_pair.first, iter_pair.second);
1274
1275 522028 return true;
1276 }
1277
1278 /*!
1279 \brief Returns true if the collection contains at least one isotope of
1280 element \a name, false otherwise.
1281
1282 If \a count is not nullptr, its value is set to the count of isotopes of \a
1283 name element.
1284 */
1285 bool
1286 2 IsotopicData::containsName(const QString &name, int &count) const
1287 {
1288 2 IsotopeListCstIteratorPair iter_pair = getIsotopesByName(name);
1289
1290
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(iter_pair.first == m_isotopes.cend())
1291 return false;
1292
1293 // Now compute the distance between the two iterators to know how many
1294 // isotopes share the same chemical element symbol.
1295
1296 2 count = std::distance(iter_pair.first, iter_pair.second);
1297
1298 2 return true;
1299 }
1300
1301 /*!
1302 \brief Returns a string containing a stanza describing each isotope of \a
1303 symbol.
1304
1305 \sa Isotope::toString, Isotope::getMass
1306 */
1307 QString
1308 IsotopicData::isotopesAsStringBySymbol(const QString &symbol) const
1309 {
1310 if(symbol.isEmpty())
1311 qFatal("Programming error. The symbol cannot be empty.");
1312
1313 QString text;
1314
1315 IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);
1316
1317 if(iter_pair.first == m_isotopes.cend())
1318 {
1319 qDebug() << "The symbol was not found in the vector of isotopes.";
1320 return text;
1321 }
1322
1323 while(iter_pair.first != iter_pair.second)
1324 {
1325 text += (*iter_pair.first)->toString();
1326 text += "\n";
1327 }
1328
1329 return text;
1330 }
1331
1332 /*!
1333 \brief Returns true if the \a isotope_cqsp is isotope of its
1334 symbol having the greatest probability, false otherwise.
1335 */
1336 bool
1337 IsotopicData::isMonoMassIsotope(IsotopeCstQSPtr isotope_cqsp)
1338 {
1339 // Is the passed isotope the one that has the greatest abundance among
1340 // all the isotopes having the same symbol.
1341
1342 if(isotope_cqsp == nullptr)
1343 qFatalStream()
1344 << "Programming error. The isotope pointer cannot be nullptr.";
1345
1346 QString symbol(isotope_cqsp->getSymbol());
1347
1348 if(!m_symbolMonoMassMap.contains(symbol))
1349 qFatalStream() << "Programming error. The symbol was not found in the map.";
1350
1351 double mass = m_symbolMonoMassMap.value(symbol);
1352
1353 if(mass == isotope_cqsp->getMass())
1354 return true;
1355
1356 return false;
1357 }
1358
1359 /*!
1360 \brief Returns a constant reference to the container of
1361 \l{MsXpS::libXpertMassCore::Isotope}s.
1362 */
1363 const QList<IsotopeQSPtr> &
1364 12 IsotopicData::getIsotopes() const
1365 {
1366 12 return m_isotopes;
1367 }
1368
1369 /*!
1370 \brief Assigns member data from \a other to this instance's member data.
1371
1372 The copying of \a other into this collection is deep, making *this
1373 collection essentially identical to \a other.
1374
1375 Returns a reference to this collection.
1376 */
1377 IsotopicData &
1378 IsotopicData::operator=(const IsotopicData &other)
1379 {
1380 if(&other == this)
1381 return *this;
1382
1383 m_isotopes = other.m_isotopes;
1384 m_symbolMonoMassMap = other.m_symbolMonoMassMap;
1385 m_symbolAvgMassMap = other.m_symbolAvgMassMap;
1386
1387 return *this;
1388 }
1389
1390 /*!
1391 \brief Returns true if \a other and \c this are identical.
1392 */
1393 bool
1394 4 IsotopicData::operator==(const IsotopicData &other) const
1395 {
1396
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(&other == this)
1397 return true;
1398
1399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(m_isotopes.size() != other.m_isotopes.size())
1400 return false;
1401
1402
2/2
✓ Branch 0 taken 580 times.
✓ Branch 1 taken 4 times.
584 for(qsizetype iter = 0; iter < m_isotopes.size(); ++iter)
1403 {
1404 // qDebug() << "Now checking Isotope" << m_isotopes.at(iter)->getName();
1405
1406
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 580 times.
580 if(*m_isotopes.at(iter) != *other.m_isotopes.at(iter))
1407 {
1408 qWarning() << "At least one isotope differs in each data set.";
1409 return false;
1410 }
1411 }
1412
1413 // qDebug() << "Done checking the isotopes.";
1414
1415
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(m_symbolMonoMassMap != other.m_symbolMonoMassMap)
1416 return false;
1417
1418
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(m_symbolAvgMassMap != other.m_symbolAvgMassMap)
1419 return false;
1420
1421 // qDebug() << "The isotopic data are identical.";
1422
1423 return true;
1424 }
1425
1426 /*!
1427 \brief Returns true if \a other and \c this are different.
1428 */
1429 bool
1430 15 IsotopicData::operator!=(const IsotopicData &other) const
1431 {
1432
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if(&other == this)
1433 return false;
1434
1435 2 return !operator==(other);
1436 }
1437
1438 /*!
1439 \brief Returns the count of isotopes in the collection.
1440
1441 \note The count that is returned is for \e all isotopes, that is, the count
1442 if items in the collection.
1443 */
1444 qsizetype
1445 445248 IsotopicData::size() const
1446 {
1447 445248 return m_isotopes.size();
1448 }
1449
1450 /*!
1451 \brief Returns the validity status of this IsotopicData instance.
1452
1453 \sa validate(), validateBySymbol(), validateAllBySymbol()
1454 */
1455 bool
1456 1 IsotopicData::isValid()
1457 {
1458 1 return m_isValid;
1459 }
1460
1461 /*!
1462 \brief Returns the count of unique symbols.
1463 */
1464 qsizetype
1465 9 IsotopicData::getUniqueSymbolsCount() const
1466 {
1467 // Go trough the vector of IsotopeQSPtr and check how many different
1468 // symbols it contains.
1469
1470 9 std::set<QString> symbols_set;
1471
1472 9 QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.cbegin();
1473 9 QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();
1474
1475
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 9 times.
26 while(iter != iter_end)
1476 {
1477
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
34 symbols_set.insert((*iter)->getSymbol());
1478 17 ++iter;
1479 }
1480
1481 // Remove these checks because in fact it is possible that the values
1482 // be different: if appending new isotopes without forcing the udpate
1483 // of the maps.
1484
1485 // Sanity checks:
1486 // if(symbols_set.size() != m_symbolAvgMassMap.size())
1487 // qFatal("Not possible that the sizes (avg) do not match");
1488 //
1489 // if(symbols_set.size() != m_symbolMonoMassMap.size())
1490 // qFatal("Not possible that the sizes (mono) do not match");
1491
1492 9 return symbols_set.size();
1493 9 }
1494
1495 /*!
1496 \brief Validates this Isotope collection.
1497
1498 The validation involves iterating in the whole collection and for each item
1499 in it invoke its Isotope::validate(). If errors occurred during these
1500 validations, they are reported as strings in \a error_list_p.
1501
1502 Return true if no error was encountered, false otherwise.
1503 */
1504 bool
1505 4 IsotopicData::validate(ErrorList *error_list_p) const
1506 {
1507 4 int error_count = 0;
1508
1509 4 IsotopeListCstIterator iter_begin = m_isotopes.cbegin();
1510 4 IsotopeListCstIterator iter_end = m_isotopes.cend();
1511
1512 4 IsotopeListCstIterator iter = iter_begin;
1513
1514
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
14 while(iter != iter_end)
1515 {
1516 10 QString error_text = "";
1517 10 ErrorList isotope_error_list;
1518
1519
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 bool result = (*iter)->validate(&isotope_error_list);
1520
1521
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if(!result)
1522 {
1523 error_text.append(QString("Isotope at index %1:\n")
1524 .arg(std::distance(iter_begin, iter)));
1525
1526 error_text.append(Utils::joinErrorList(isotope_error_list, "\n"));
1527
1528 error_list_p->push_back(error_text);
1529
1530 ++error_count;
1531 }
1532
1533 10 ++iter;
1534 10 }
1535
1536
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(error_count)
1537 m_isValid = false;
1538 else
1539 4 m_isValid = true;
1540
1541 4 return m_isValid;
1542 }
1543
1544 /*!
1545 \brief Validates the \l{Isotope}s of \a symbol in this collection.
1546
1547 This function is more powerful than IsotopicData::validate()
1548 because it actually verifies the integrity of the data \e{symbol}-wise. For
1549 example, a set of isotopes by the same symbol cannot have a cumulated
1550 probability different than 1. If that were the case, the error would be
1551 reported.
1552
1553 Encountered errors are stored in \a error_list_p.
1554
1555 Returns true if validation succeeded, false otherwise.
1556
1557 \sa validateAllBySymbol(), getCumulatedProbabilitiesBySymbol()
1558 */
1559 bool
1560 6 IsotopicData::validateBySymbol(const QString &symbol,
1561 ErrorList *error_list_p) const
1562 {
1563 // This function is more powerful than the other one because it actually
1564 // looks the integrity of the data symbol-wise. For example, a set of
1565 // isotopes by the same symbol cannot have a cumulated probability greater
1566 // than 1. If that were the case, that would be reported.
1567
1568 // qDebug() << "symbol: " << symbol;
1569
1570 // Validating by symbol means looking into each isotope that has the same
1571 // 'symbol' and validating each one separately. However, it also means looking
1572 // if all the cumulated isotope probabilities (abundances) for a given symbol
1573 // are different than 1.
1574
1575 // Record the size of the error_list so that we can report if we added
1576 // errors in this block.
1577 6 qsizetype previous_error_count = error_list_p->size();
1578
1579 // The function below performs validation of the isotopes.
1580 6 double cumulated_probabilities =
1581 6 getCumulatedProbabilitiesBySymbol(symbol, error_list_p);
1582
1583
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if(cumulated_probabilities != 1)
1584 {
1585 2 QString prob_error =
1586 QString(
1587 2 "Isotope symbol %1: has cumulated probabilities not equal to 1.\n")
1588
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 .arg(symbol);
1589
1590
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 error_list_p->push_back(prob_error);
1591 2 }
1592
1593 // If we added errors, then return false.
1594 6 return !(error_list_p->size() > previous_error_count);
1595 }
1596
1597 /*!
1598 \brief Validates all the isotopes of the collection.
1599
1600 The validation of the \l{Isotope}s is performed by grouping them
1601 by symbol. See validateBySymbol().
1602
1603 Encountered errors are stored in \a error_list_p.
1604
1605 Returns true if all the Isotopes validated successfully, false otherwise.
1606
1607 \sa validateBySymbol(), validate(), getCumulatedProbabilitiesBySymbol()
1608 */
1609 bool
1610 IsotopicData::validateAllBySymbol(ErrorList *error_list_p) const
1611 {
1612 // This validation of all the isotopes as grouped by symbol is more
1613 // informative than the validation isotope by isotope idependently
1614 // because it can spot cumulated probabilities problems.
1615
1616 qsizetype previous_error_count = error_list_p->size();
1617
1618 std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();
1619
1620 for(auto symbol : all_symbols)
1621 {
1622 QString error_text = "";
1623
1624 validateBySymbol(symbol, error_list_p);
1625 }
1626
1627 // If we added errors, then return false.
1628 return !(error_list_p->size() > previous_error_count);
1629 }
1630
1631 /*!
1632 \brief Replaces \a old_isotope_sp by \a new_isotope_sp in this collection.
1633 */
1634 void
1635 1 IsotopicData::replace(IsotopeQSPtr old_isotope_sp, IsotopeQSPtr new_isotope_sp)
1636 {
1637 1 std::replace(
1638 m_isotopes.begin(), m_isotopes.end(), old_isotope_sp, new_isotope_sp);
1639 1 }
1640
1641 /*!
1642 \brief Clears (empties) the symbol/mass maps only, not the vector if Isotope
1643 instances.
1644
1645 These two containers are cleared:
1646
1647 \list
1648 \li m_symbolMonoMassMap
1649 \li m_symbolAvgMassMap
1650 \endlist
1651 */
1652 void
1653 155 IsotopicData::clearSymbolMassMaps()
1654 {
1655 155 m_symbolMonoMassMap.clear();
1656 155 m_symbolAvgMassMap.clear();
1657 155 }
1658
1659 /*!
1660 \brief Clears (empties) all the containers in this collection, essentially
1661 resetting it completely.
1662
1663 These three containers are cleared:
1664
1665 \list
1666 \li m_isotopes
1667 \li m_symbolMonoMassMap
1668 \li m_symbolAvgMassMap
1669 \endlist
1670
1671 \sa clearSymbolMassMaps()
1672 */
1673 void
1674 155 IsotopicData::clear()
1675 {
1676 155 m_isotopes.clear();
1677 155 clearSymbolMassMaps();
1678 155 }
1679
1680 void
1681 IsotopicData::registerJsConstructor(QJSEngine *engine)
1682
1683 {
1684 if(!engine)
1685 {
1686 qWarning() << "Cannot register IsotopicData class: engine is null";
1687 return;
1688 }
1689
1690 // Register the meta object as a constructor
1691
1692 QJSValue jsMetaObject =
1693 engine->newQMetaObject(&IsotopicData::staticMetaObject);
1694 engine->globalObject().setProperty("IsotopicData", jsMetaObject);
1695 }
1696
1697
1698 } // namespace libXpertMassCore
1699
1700 } // namespace MsXpS
1701
1702
1703 #if 0
1704
1705 Example from IsoSpec.
1706
1707 const int elementNumber = 2;
1708 const int isotopeNumbers[2] = {2,3};
1709
1710 const int atomCounts[2] = {2,1};
1711
1712
1713 const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
1714 const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};
1715
1716 const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};
1717
1718 const double hydrogen_probs[2] = {0.5, 0.5};
1719 const double oxygen_probs[3] = {0.5, 0.3, 0.2};
1720
1721 const double* probs[2] = {hydrogen_probs, oxygen_probs};
1722
1723 IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99);
1724
1725 #endif
1726