GCC Code Coverage Report


./
File: src/XpertMass/IsotopicDataManualConfigHandler.cpp
Date: 2024-08-24 11:26:06
Lines:
142/208
68.3%
Functions:
5/10
50.0%
Branches:
131/334
39.2%

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