GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/IsotopicDataManualConfigHandler.cpp
Date: 2025-11-20 01:41:33
Lines:
153/181
84.5%
Functions:
5/10
50.0%
Branches:
126/248
50.8%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2020 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the MsXpertSuite project.
10 *
11 * The MsXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Std lib includes
35 #include <set>
36
37
38 /////////////////////// Qt includes
39 #include <QDebug>
40 #include <QFile>
41 #include <QIODevice>
42
43
44 /////////////////////// IsoSpec
45 #include <IsoSpec++/isoSpec++.h>
46 #include <IsoSpec++/element_tables.h>
47
48
49 /////////////////////// Local includes
50 #include "MsXpS/libXpertMassCore/globals.hpp"
51 #include "MsXpS/libXpertMassCore/IsotopicDataManualConfigHandler.hpp"
52
53 namespace MsXpS
54 {
55
56 namespace libXpertMassCore
57 {
58
59
60 /*!
61 \class MsXpS::libXpertMassCore::IsotopicDataManualConfigHandler
62 \inmodule libXpertMassCore
63 \ingroup PolChemDefBuildingdBlocks
64 \inheaderfile IsotopicDataManualConfigHandler.hpp
65
66 \brief The IsotopicDataManualConfigHandler class handles a peculiar kind of
67 \l{IsotopicData} that cannot be handled with the other handlers.
68
69 This kind of IsotopicData handler is typically used when definition of brand
70 new chemical element isotopes are to be performed. For example, if one works
71 with radioactive carbon C14, then that isotope is not available in the
72 IsoSpec's library and cannot be inserted in these data using the
73 \l{IsotopicDataUserConfigHandler}.
74
75 One interesting feature of this kind of IsotopicData formalism is that these
76 data collectively describe a chemical elemental composition formula,
77 like, for glucose: C6H12O6. Each chemical element is decribed using isotopes
78 with mass/abundance pairs. There is thus virtually no limitation on the
79 complexity of the isotopic distribution to be defined. The format of these data
80 stored on file is nothing like the format used to store Library- or
81 User-Config- isotopic data (\l{IsotopicDataLibraryHandler},
82 \l{IsotopicDataUserConfigHandler}).
83
84 In the example below we are defining the isotopic composition of
85 radiolabelled glucose (C6H12O6) where only two of the the six C atoms are 14[C]
86 atoms with a radiolabelling efficiency of 95%:
87
88 \code
89 [Element]
90 symbol C count 4
91 # The four atoms that are not labelled appear with natural
92 # abundances for both stable isotopes
93 [Isotopes] 2
94 mass 12.0 prob 0.989
95 mass 13.003354 prob 0.010788
96 [Element]
97 # The two atoms that are 95% labelled with 14[C] need to use
98 # a new artificial symbol
99 symbol Cx count 2
100 [Isotopes] 3
101 # 5% of non labelled atoms are of natural 12[C] abundance
102 mass 12.000 prob 0.05*0.989211941850466
103 # 5% of non labelled atoms are of natural 13[C] abundance
104 mass 13.0033548352 prob 0.05*0.010788058149533084
105 # The remaining 95% of the atoms are 14[C] atoms with abundance 100%
106 mass 14.003241989 prob 0.95*1.00
107 [Element]
108 symbol H count 12
109 [Isotopes] 2
110 mass 1.0078250 prob 0.99988
111 mass 2.01410177 prob 0.00011570
112 [Element]
113 symbol O count 6
114 [Isotopes] 3
115 mass 15.9949 prob 0.99756
116 mass 16.999 prob 0.000380
117 mass 17.999 prob 0.002051
118 \endcode
119
120 Comments are allowed and are on lines that have as their first non-space
121 character the '#' character. These lines are ignored when loading data.
122
123 The data can be loaded from and written to file.
124
125 \sa IsotopicDataLibraryHandler, IsotopicDataUserConfigHandler
126 */
127
128 /*!
129 \typealias MsXpS::libXpertMassCore::SymbolCountMap
130
131 Alias for to std::map<QString, int>.
132 */
133
134 /*!
135 \variable MsXpS::libXpertMassCore::IsotopicDataManualConfigHandler::m_symbolCountMap
136
137 Holds symbol/count pairs to document the count of any symbol in the isotopic
138 data. This symbol count is stored in the file using the following format:
139
140 \code
141 [Element]
142 symbol C count 6
143 \endcode
144
145 The number of carbon atoms in the formula being defined is 6, as in glucose:
146 C6H1206.
147 */
148
149 /*!
150 \typedef MsXpS::libXpertMassCore::IsotopicDataManualConfigHandlerSPtr
151 \relates IsotopicDataManualConfigHandler
152
153 Synonym for std::shared_ptr<IsotopicDataManualConfigHandler>.
154 */
155
156 /*!
157 \typedef MsXpS::libXpertMassCore::IsotopicDataManualConfigHandlerCstSPtr
158 \relates IsotopicDataManualConfigHandler
159
160 Synonym for std::shared_ptr<const IsotopicDataManualConfigHandler>.
161 */
162
163 /*!
164 \brief Constructs the \l{IsotopicDataManualConfigHandler} with \a file_name.
165 */
166 17 IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler(
167 17 const QString &file_name)
168 17 : IsotopicDataBaseHandler(file_name)
169 {
170 17 }
171
172 /*!
173 \brief Constructs the \l{IsotopicDataManualConfigHandler}.
174
175 \a isotopic_data_sp Isotopic data
176
177 \a file_name File name
178 */
179 IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler(
180 IsotopicDataSPtr isotopic_data_sp, const QString &file_name)
181 : IsotopicDataBaseHandler(isotopic_data_sp, file_name)
182 {
183 }
184
185 /*!
186 \brief Destructs the \l{IsotopicDataManualConfigHandler}.
187 */
188 36 IsotopicDataManualConfigHandler::~IsotopicDataManualConfigHandler()
189 {
190 // qDebug();
191 36 }
192
193 /*!
194 \brief Assigns \a map to m_symbolCountMap.
195 */
196 void
197 IsotopicDataManualConfigHandler::setSymbolCountMap(const SymbolCountMap &map)
198 {
199 m_symbolCountMap = map;
200 }
201
202 /*!
203 \brief Returns a reference to m_symbolCountMap.
204 */
205 const SymbolCountMap &
206 IsotopicDataManualConfigHandler::getSymbolCountMap() const
207 {
208 return m_symbolCountMap;
209 }
210
211 /*!
212 \brief Loads isotopic data from \a file_name.
213
214 Returns the count of \l{Isotope}s that were allocated and stored in the
215 msp_isotopicData member.
216 */
217 qsizetype
218 10 IsotopicDataManualConfigHandler::loadData(const QString &file_name)
219 {
220 // File format:
221 //
222 // [Element]
223 // symbol C count 6
224 // [Isotopes] 2
225 // mass 12.0 prob 0.989
226 // mass 13.003354 prob 0.010788
227 // [Element]
228 // symbol H count 13
229 // [Isotopes] 2
230 // mass 1.0078250 prob 0.99988
231 // mass 2.01410177 prob 0.00011570
232 // [Element]
233 // symbol O count 6
234 // [Isotopes] 3
235 // mass 15.9949 prob 0.99756
236 // mass 16.999 prob 0.000380
237 // mass 17.999 prob 0.002051
238 //
239
240
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 7 times.
10 QString local_file_name = file_name;
241
242
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 if(local_file_name.isEmpty())
243 7 local_file_name = m_fileName;
244
245 // See the Isotope::toString() function that is used to write the isotopic
246 // data to file. We thus expect exactly that format from the file.
247
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if(local_file_name.isEmpty())
249 {
250 qCritical("File name is emtpy. Failed to open file for reading.");
251 return 0;
252 }
253
254
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 QFile file(local_file_name);
255
256
3/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 6 times.
10 if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
257 {
258
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 qCritical("Failed to open file for reading.");
259 4 return 0;
260 }
261
262 // qDebug() << "Loading isotopic data from file:" << local_file_name;
263
264 // File-parsing helper variables.
265 6 bool was_started_one_element = false;
266 6 bool was_symbol_count_line = false;
267 6 bool was_isotopes_count_line = false;
268 6 bool was_mass_prob_line = false;
269
270 // Instantiate a symbol that we'll use as a place holder for the element name.
271
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QString symbol = "";
272
273 // Instantiate a count to document the symbol count.
274 6 qsizetype element_count = 0;
275
276 // Instantiate a count to document the number of isotopes that were defined
277 // for a given element.
278 6 qsizetype element_isotope_count = 0;
279
280 // Increment each time an isotope is parsed and added to the vector *for a
281 // given element stanza*. This helps sanity checking.
282 6 qsizetype added_isotopes_for_element = 0;
283
284 // Maintain a counter of the whole count of isotopes for sanity checks.
285 6 qsizetype all_parsed_isotopes_count = 0;
286
287 6 double mass = 0;
288 6 double prob = 0;
289
290 6 bool ok = false;
291
292
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 QRegularExpression comment_regexp("^\\s*#");
293
294
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QRegularExpression symbol_count_regexp(
295
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 "^\\s*symbol\\s+([A-Z][a-z]?)\\s+count\\s+(\\d+)");
296
297
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 QRegularExpression isotopes_regexp("^\\s*\\[Isotopes\\]\\s(\\d+)");
298
299 // QRegularExpression massProbRegexp = QRegularExpression(
300 //"^\\s+mass\\s+(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\s+prob\\s+([^\\d^\\.^-]+)(-?"
301 //"\\d*\\.?\\d*[e]?[-]?[+]?\\d*)");
302
303
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QRegularExpression mass_prob_regexp = QRegularExpression(
304 "^\\s*mass\\s(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\sprob\\s(\\d*\\.?\\d*[e]?["
305 "-]"
306 "?["
307
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 "+]?\\d*)");
308
309 // qDebug() << "The mass prob regexp is valid?" <<
310 // massProbRegexp.isValid();
311
312 // mass 13.003354835200
313 // prob 0.010788058149533083507343178553128382191061973571777343750000
314
315 // Make sure we clear the room.
316
317
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 msp_isotopicData->clear();
318
319 6 QList<IsotopeQSPtr> element_isotopes;
320
321 // This set is to ensure that we do not have twice the same element frame
322 // (that is, with the same symbol).
323
324
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 std::set<QString> symbol_set;
325
326 // File format:
327
328 // [Element]
329 // symbol C count 6
330 // [Isotopes] 2
331 // mass 12.0 prob 0.989
332 // mass 13.003354 prob 0.010788
333
334
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QTextStream in_stream(&file);
335
336
3/4
✓ Branch 1 taken 180 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 174 times.
✓ Branch 4 taken 6 times.
180 while(!in_stream.atEnd())
337 {
338
1/2
✓ Branch 1 taken 174 times.
✗ Branch 2 not taken.
174 QString line = in_stream.readLine();
339
340 // Ignore empty lines
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 174 times.
174 if(line.length() < 1)
342 continue;
343
344
345
1/2
✓ Branch 1 taken 174 times.
✗ Branch 2 not taken.
174 line = line.simplified();
346
347 // qDebug() << "Current line:" << line;
348
349 // Ignore comment lines
350
1/2
✓ Branch 1 taken 174 times.
✗ Branch 2 not taken.
174 QRegularExpressionMatch match = comment_regexp.match(line);
351
3/4
✓ Branch 1 taken 174 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 132 times.
174 if(match.hasMatch())
352 42 continue;
353
354
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 108 times.
132 if(line == "[Element]")
355 {
356 // qDebug() << "That's the [Element] stanza opening line.";
357
358 // We are starting a new Element stanza. It cannot be that we both
359 // have already started one Element stanza and that not a single
360 // mass and probability line had been encountered. Either this is the
361 // very first Element stanza that we read and was_started_one_element
362 // is false or we were reading one Element stanza that has finished
363 // and then was_mass_prob_line has to be true.
364
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(was_started_one_element && !was_mass_prob_line)
365 {
366 qDebug() << "Error: one element is complete but has no isotopes.";
367 return 0;
368 }
369
370
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 6 times.
24 if(was_started_one_element)
371 {
372
373 // qDebug()
374 //<< "We had already seen the [Element] stanza opening line.";
375
376 // We are starting a new Element configuration stanza, but in
377 // fact another was already cooking. We need to terminate it.
378
379 // Sanity check: the number of purportedly listed isotopes needs
380 // to be identical to the number of isotopes actually added to the
381 // vector of isotopes.
382
383
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if(element_isotope_count != element_isotopes.size() ||
384
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 added_isotopes_for_element != element_isotopes.size())
385 {
386 qDebug() << "Error. We did not parse the expected number of "
387 "isotopes.";
388 return 0;
389 }
390
391 // qDebug() << "And there are the right number of isotopes
392 // cooked.";
393
394 // At this point we have everything we need to add this new
395 // chemical set.
396
397 // qDebug() << "Creating new chemical set.";
398
399
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
✗ Branch 4 not taken.
18 if(!newChemicalSet(
400 symbol, element_count, element_isotopes, false))
401 {
402 qDebug() << "Failed to add new chemical set.";
403 return 0;
404 }
405
406 // qDebug() << "The completed chemical set: "
407 //"symbol/element_count/isotope_count:"
408 //<< symbol << "/" << element_count << "/"
409 //<< element_isotopes.size();
410
411 // Sanity check: the total count of added isotopes for this symbol
412 // needs to correct.
413
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if(added_isotopes_for_element !=
415
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 msp_isotopicData->getIsotopeCountBySymbol(symbol))
416 qFatal("Programming error.");
417
418 // Now clear for next run.
419
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 symbol = "";
420 18 element_isotope_count = 0;
421 18 added_isotopes_for_element = 0;
422 18 element_isotopes.clear();
423 }
424
425 // Tell that we actually have entered the first line of an Element
426 // stanza.
427 24 was_started_one_element = true;
428
429 // Reset all the other values so that we know we are just at the
430 // beginning of the parsing work.
431 24 was_symbol_count_line = false;
432 24 was_isotopes_count_line = false;
433 24 was_mass_prob_line = false;
434
435 // Go the next line.
436 24 continue;
437 }
438
439 // At this point we are already inside of the [Element] stanza. Parse the
440 // various lines inside it.
441
442 // File format:
443
444 // [Element]
445 // symbol C count 6
446 // [Isotopes] 2
447 // mass 12.0 prob 0.989
448 // mass 13.003354 prob 0.010788
449
450
1/2
✓ Branch 1 taken 108 times.
✗ Branch 2 not taken.
108 match = symbol_count_regexp.match(line);
451
3/4
✓ Branch 1 taken 108 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 84 times.
108 if(match.hasMatch())
452 {
453
454 // qDebug() << "Matched the symbol count line.";
455
456 // If we are parsing "symbol C count 100", then it is not possible
457 // that we did not encounter before [Element] or that we already have
458 // parsed one same line as "symbol C count 100".
459
460
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(!was_started_one_element || was_symbol_count_line)
461 {
462 qDebug() << "Error encountered in the symbol/count line.";
463 return 0;
464 }
465
466
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 symbol = match.captured(1);
467
468
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 element_count = match.captured(2).toInt(&ok);
469
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 if(!ok || !element_count)
470 {
471 qDebug() << "Error encountered in the symbol/count line.";
472 return 0;
473 }
474
475 // Now check if that symbol was encountered already, which would be an
476 // error.
477
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 auto res = symbol_set.insert(symbol);
478
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(!res.second)
479 {
480 // We did not insert the symbol because one already existed. That
481 // is an error.
482
483 qDebug() << "An element by symbol" << symbol
484 << "has already been processed: "
485 "this is not permitted.";
486 return 0;
487 }
488
489 // qDebug() << "Processed element symbol:" << symbol
490 //<< "with count:" << element_count;
491
492 // Do not store the element symbol/count pair yet, we'll wait to
493 // encounter a new Element stanza which will close this one.
494
495 24 was_symbol_count_line = true;
496 24 was_isotopes_count_line = false;
497 24 was_mass_prob_line = false;
498
499 24 continue;
500 24 }
501 // End of
502 // line matched the symbol/count regexp
503
504 // File format:
505
506 // [Element]
507 // symbol C count 6
508 // [Isotopes] 2
509 // mass 12.0 prob 0.989
510 // mass 13.003354 prob 0.010788
511
512
1/2
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
84 match = isotopes_regexp.match(line);
513
3/4
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✓ Branch 4 taken 60 times.
84 if(match.hasMatch())
514 {
515
516 // qDebug() << "Matched the [Isotopes] count stanza opening line.";
517
518 // We cannot be parsing the [Isotopes] stanza opening header if we
519 // have not previously parsed the symbol/count line.
520
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(!was_symbol_count_line)
521 {
522 qDebug() << "Error encounteredd in the isotopes lines.";
523 return 0;
524 }
525
526 // Store the number of isotopes for the current Element.
527
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 element_isotope_count = match.captured(1).toInt(&ok);
528
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 if(!ok || !element_isotope_count)
529 {
530 qDebug() << "Error encounteredd in the isotopes lines.";
531 return 0;
532 }
533
534 // qDebug() << "The isotope count is:" << element_isotope_count;
535
536 24 was_isotopes_count_line = true;
537 24 was_symbol_count_line = false;
538 24 was_mass_prob_line = false;
539
540 24 continue;
541 }
542 // End of
543 // line matched the [Isotopes] count regexp
544
545 // File format:
546
547 // [Element]
548 // symbol C count 6
549 // [Isotopes] 2
550 // mass 12.0 prob 0.989
551 // mass 13.003354 prob 0.010788
552
553
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 match = mass_prob_regexp.match(line);
554
2/4
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 60 times.
✗ Branch 4 not taken.
60 if(match.hasMatch())
555 {
556
557 // qDebug() << "Matched the mass prob line.";
558
559 // If we match an isotope's mass/prob line, either --- at previous
560 // line --- we had seen the [Isotopes] stanza opening line or we had
561 // seen another mass prob line.
562
563
564
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 if(!was_isotopes_count_line && !was_mass_prob_line)
565 {
566 qDebug() << "Error encountered in the mass/prob line.";
567 return 0;
568 }
569
570
2/4
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
60 mass = match.captured(1).toDouble(&ok);
571
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 if(!ok)
572 {
573 qDebug() << "Error encountered in the mass/prob line.";
574 return 0;
575 }
576
577
2/4
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
60 prob = match.captured(2).toDouble(&ok);
578
1/2
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
60 if(!ok)
579 {
580 qDebug() << "Error encountered in the mass/prob line.";
581 return 0;
582 }
583
584 // At this point we have everything we need to actually create the
585 // isotope by symbol and mass and prob. There are a number of fields
586 // that are left to value 0 but this is of no worries.
587
588 // qDebug() << "Iterated in isotope:" << symbol << ":" << mass << "/"
589 //<< prob;
590
591 // At this point create a brand new Isotope with the relevant data.
592
593 // Isotope::Isotope(
594 // QString element,
595 // QString symbol,
596 // double mass,
597 // double probability)
598
599 // There are a number of fields that are left to value 0 but this is
600 // of no worries. Store the isotope in the vector of isotopes that
601 // will be added to the isotopic data later when finishing the parsing
602 // of the element frame widget.
603
604 60 element_isotopes.push_back(
605
1/2
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
60 QSharedPointer<Isotope>::create(symbol, symbol, mass, prob));
606
607 60 ++added_isotopes_for_element;
608 60 ++all_parsed_isotopes_count;
609
610 // qDebug() << "The atom now is:" << atom.asText();
611
612 60 was_mass_prob_line = true;
613 60 was_isotopes_count_line = false;
614 60 was_symbol_count_line = false;
615
616 60 continue;
617 }
618 // End of
619 // line matched the isotope mass/prob regexp
620 348 }
621
622
623 // We have finished iterating in the file's lines but we were parsing an atom,
624 // append it.
625
626 // Sanity check
627
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(!was_started_one_element)
628 {
629 qDebug() << "Error: not a single element could be parsed.";
630 return 0;
631 }
632
633 // Sanity check: the number of purportedly listed isotopes needs
634 // to be identical to the number of isotopes actually added to the
635 // vector of isotopes.
636
637
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(element_isotope_count != element_isotopes.size() ||
638
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 added_isotopes_for_element != element_isotopes.size())
639 {
640 qDebug() << "Error. We did not parse the expected number of "
641 "isotopes.";
642 return 0;
643 }
644
645 // At this point we have everything we need to add this new
646 // chemical set. Do not yet update the mass maps, we'll do at the end of the
647 // process.
648
649
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if(!newChemicalSet(symbol, element_count, element_isotopes, false))
650 {
651 qDebug() << "Failed to add new chemical set.";
652 return 0;
653 }
654
655 // qDebug() << "The completed chemical set: "
656 //"symbol/element_count/isotope_count:"
657 //<< symbol << "/" << element_count << "/" << element_isotopes.size();
658
659 // Sanity check: the total count of added isotopes for this symbol
660 // needs to correct.
661
662
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(added_isotopes_for_element !=
663
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 msp_isotopicData->getIsotopeCountBySymbol(symbol))
664 qFatal("Programming error.");
665
666 // Sanity check: the total count of isotopes added to the isotopic data member
667 // datum needs to match the total count of parsed isotopes.
668
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if(all_parsed_isotopes_count != msp_isotopicData->size())
669 qFatal("Programming error.");
670
671 // We have touched the isotopic data, ensure the maps are current.
672
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if(!msp_isotopicData->updateMassMaps())
673 qFatal("Programming error. Failed to update the mass maps.");
674
675 6 m_fileName = local_file_name;
676
677
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 return msp_isotopicData->size();
678 20 }
679
680 /*!
681 \brief Write all the IsotopicData to \a file_name.
682
683 If \a file_name is empty, \l{m_fileName} is tried. If both are empty, the
684 function returns 0. If any one of the file names are correct (\a file_name takes
685 precedence over \l{m_fileName}), then \l{m_fileName} is set to that file name.
686
687 The format of the file consists in a single line of data per \l{Isotope} as
688 created using Isotope::toString(). Each isotope is output to
689 its own line.
690
691 Returns the count of \l{Isotope}s written to file or 0 if the file does not
692 exist or is not readable.
693 */
694 qsizetype
695 1 IsotopicDataManualConfigHandler::writeData(const QString &file_name)
696 {
697
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if(file_name.isEmpty() && m_fileName.isEmpty())
698 return 0;
699
700 1 QString temp_file_name;
701
702 // The passed filename takes precedence over the member datum. So copy
703 // that file name to the member datum.
704
705
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(!file_name.isEmpty())
706 1 temp_file_name = file_name;
707 else
708 temp_file_name = m_fileName;
709
710
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QFile file(temp_file_name);
711
712
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
713 {
714 qDebug("Failed to open file for writing.");
715 return false;
716 }
717
718
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QTextStream out(&file);
719
720 1 out
721
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "# This file contains isotopic data in a format that can accommodate\n";
722
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out << "# comments in the form of lines beginning with the '#' character.\n";
723
724 1 qsizetype isotope_count = 0;
725
726 // We want to write the isotopic data exactly in the same order as we might
727 // have loaded them. This is why we need to iterated in the vector of isotopes
728 // and not the in the map that is ordered according to the symbols (the keys).
729
730 1 QString last_symbol;
731
732
5/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 10 times.
✓ Branch 9 taken 1 times.
11 for(auto item : msp_isotopicData->m_isotopes)
733 {
734
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 QString symbol = item->getSymbol();
735
736 // We only write the  [Element] and [Isotopes] stanza header once for each
737 // new symbol found in the iterated items.
738
739
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 if(symbol != last_symbol)
740 {
741
742 // This is the first time we encounter an isotope by symbol, we open a
743 // new Element stanza.
744
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 out << "[Element]\n";
745
746 // We now need to write the symbol count line. If the symbol key is
747 // not found, throws an exception.
748
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 int symbol_count = m_symbolCountMap.at(symbol);
749 4 out
750
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
12 << QString("\tsymbol %1 count %2\n").arg(symbol).arg(symbol_count);
751
752 // We also need to write the Isotopes stanza:
753 4 int symbol_isotope_count =
754
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 msp_isotopicData->getIsotopeCountBySymbol(symbol);
755
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
12 out << QString("\t[Isotopes] %1\n").arg(symbol_isotope_count);
756 }
757
758 // At this point we can write the currently iterated isotope's mass:prob
759 // pair.
760
761
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 out << QString("\t\tmass %1 prob %2\n")
762
2/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
20 .arg(item->getMass(), 0, 'f', 60)
763
3/6
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 10 times.
✗ Branch 8 not taken.
20 .arg(item->getProbability(), 0, 'f', 60);
764
765 10 last_symbol = symbol;
766
767 10 ++isotope_count;
768 10 }
769
770
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out.flush();
771
772
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 file.close();
773
774
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(isotope_count != msp_isotopicData->size())
775 qFatal("Programming error. Failed to write all the isotopes to file.");
776
777 // Now we know that temp_file_name is fine. Store into m_fileName.
778 1 m_fileName = temp_file_name;
779
780 1 return isotope_count;
781 1 }
782
783 /*!
784 \brief Add a set of \l{Isotope}s belonging to chemical element \a symbol.
785
786 The count of atoms of \a symbol is defined with \a element_count. The
787 isotopes to be added are provided in \a isotopes.
788
789 If the new chemical data pertain to a symbol that was already added to the
790 IsotopicData, then that is an error.
791
792 If \a update_maps is true, the \l{IsotopicData} maps need to be updated.
793
794 Returns false if the new chemical data pertain to a \a symbol that was
795 already added to the IsotopicData.
796
797 \sa IsotopicData::updateMonoMassMap
798 \sa IsotopicData::updateAvgMassMap
799 */
800 bool
801 24 IsotopicDataManualConfigHandler::newChemicalSet(
802 const QString &symbol,
803 int element_count,
804 const QList<IsotopeQSPtr> &isotopes,
805 bool update_maps)
806 {
807 // It is of uttermost importance that we update the symbol/count pair because
808 // that is going to be used when performing the IsoSpec arrays configuration
809 // and later isotopic cluster calculations.
810
811 24 std::pair<SymbolCountMapIter, bool> res =
812
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
48 m_symbolCountMap.insert(std::pair<QString, int>(symbol, element_count));
813
814
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(!res.second)
815 {
816 // Sanity check: it is not possible to add new chemical sets by a given
817 // symbol multiple times.
818
819 qDebug() << "Error: isotopes by that symbol: " << symbol
820 << "were already found in the isotopic data set.";
821
822 return false;
823 }
824
825 24 qsizetype count_before = msp_isotopicData->size();
826
827 // Update the mass maps according to second param below.
828 24 msp_isotopicData->appendNewIsotopes(isotopes, update_maps);
829
830 24 qsizetype count_after = msp_isotopicData->size();
831
832
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if(count_after - count_before != isotopes.size())
833 qFatal("Programming error.");
834
835 return true;
836 }
837
838 /*!
839 \brief Returns a string containing a formula corresponding to the
840 IsotopicData.
841
842 The formula is actually computed using the symbol/count pairs stored in
843 m_symbolCountMap.
844 */
845 QString
846 IsotopicDataManualConfigHandler::craftFormula() const
847 {
848 QString formula;
849
850 for(auto item : m_symbolCountMap)
851 formula.append(QString("%1%2").arg(item.first).arg(item.second));
852
853 return formula;
854 }
855
856 /*!
857 \brief Returns the count of \l{Isotope}s in this collection.
858 */
859 qsizetype
860 IsotopicDataManualConfigHandler::checkConsistency()
861 {
862 return msp_isotopicData->size();
863 }
864
865
866 } // namespace libXpertMassCore
867
868 } // namespace MsXpS
869
870
871 #if 0
872
873 Example from IsoSpec.
874
875 const int elementNumber = 2;
876 const int isotopeNumbers[2] = {2,3};
877
878 const int atomCounts[2] = {2,1};
879
880
881 const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
882 const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};
883
884 const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};
885
886 const double hydrogen_probs[2] = {0.5, 0.5};
887 const double oxygen_probs[3] = {0.5, 0.3, 0.2};
888
889 const double* probs[2] = {hydrogen_probs, oxygen_probs};
890
891 IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99);
892
893 #endif
894