GCC Code Coverage Report


./
File: src/XpertMass/IsotopicData.cpp
Date: 2024-08-24 11:26:06
Lines:
266/361
73.7%
Functions:
33/39
84.6%
Branches:
151/326
46.3%

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