GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Formula.cpp
Date: 2025-11-20 01:41:33
Lines:
449/642
69.9%
Functions:
40/49
81.6%
Branches:
407/928
43.9%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 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 /////////////////////// Stdlib includes
35
36
37 /////////////////////// Qt includes
38 #include <QChar>
39 #include <QString>
40
41
42 /////////////////////// Local includes
43 #include "MsXpS/libXpertMassCore/jsclassregistrar.h"
44 #include "MsXpS/libXpertMassCore/globals.hpp"
45 #include "MsXpS/libXpertMassCore/Formula.hpp"
46 #include "MsXpS/libXpertMassCore/Utils.hpp"
47
48 namespace MsXpS
49 {
50 namespace libXpertMassCore
51 {
52
53
54 /*!
55 \class MsXpS::libXpertMassCore::Formula
56 \inmodule libXpertMassCore
57 \ingroup PolChemDefBuildingdBlocks
58 \inheaderfile Formula.hpp
59
60 \brief The Formula class provides sophisticated abstractions to work with
61 formulas.
62
63 There are two peculiarities with this Formula implementation:
64
65 \list
66 \li The \e{Actionformula}
67 \li The \e{Title}
68 \endlist
69
70 \e{\b{The action-formula}}: the main textual element in this Formula
71 class is the \e{action-formula} (member m_actionFormula). A formula is the
72 description of the atomic composition of a compound. For example, the string
73 \e{C2H6} is a formula. While the previous \e{C2H6} example describes a static
74 chemical object, a Formula can also describe a dynamic chemical event, like a
75 reaction, by describing what chemical entities are gained by the molecule during
76 the chemical reaction (the "plus" component of the action-formula) and what
77 chemical entities are lost by the molecule (the "minus" component). For example,
78 an acetylation reaction can be described by the loss of \e{H2O} with gain of
79 \e{CH3COOH}. The net chemical gain on the molecule will be \e{CH3CO}. In this
80 example, one would thus define an action-formula in the following way:
81 \e{-H20+CH3COOH}. The "minus" formula associated with the '-' action accounts
82 for the leaving group of the reaction, while the "plus" formula associated with
83 the '+' action accounts for the entering group of the reaction. Note that there
84 is no limitation on the amount of such actions, as one could have an action
85 formula like this \e{-H+CO2-H2O+C2H6}. An \e{action-formula} does not need to
86 have any action sign (+ or -), and if it has no sign, the action-formula is a
87 plus-signed formula by default, which is what the reader would expect for a
88 standard formula.
89
90 \e{\b{The title}}: the action-formula may be documented with a title: a prefix
91 text enclosed in double quotes, like the following: \e{"Decomposed adenine"
92 C5H4N5 +H}. This documentation element is called the \e{title}. Note that the
93 presence of a title in a formula does not change anything to its workings as
94 long as the \e{title} is effectively enclosed in double quotes. The title is by
95 no means for an action-formula to work correctly. It is mainly used in some
96 particular context, like the calculator. An action-formula behaves exactly the
97 same as a simple formula from an end user perspective. Behind the scenes,
98 functions are called to separate all the '+'-associated formulas from all the
99 '-'-associated formulas so that masses are correctly associated to each
100 "leaving" or "entering" chemical groups. Formulas that are '-'-associated are
101 stored in the so-called "minus formula", while '+'-associated ones are stored in
102 the "plus formula". Note that all the formulas in Formula are QString objects.
103
104 Upon parsing of the action-formula, the m_minusFormula and the m_plusFormula
105 members are populated with formulas (in the \e{-H+CO2-H2O+C2H6} example, the
106 "minus formula" would contain "HH2O", while the "plus formula" would contain
107 "CO2C2H6") and these are next used to account for the net formula.
108
109 \note A Formula instance is created in an invalid state (m_isValid is false).
110 Only when all the relevant data have been set and the Formula is validated
111 explicitely (\l{validate()}), the user of the Formula knows it is valid or not.
112 */
113
114 /*!
115 \enum MsXpS::libXpertMassCore::Formula::SplitResult
116
117 This enum type specifies the result of an action-formula parsing process:
118
119 \value NOT_SET
120 The value was not set
121 \value FAILURE
122 The splitting work failed
123 \value HAS_PLUS_COMPONENT
124 The action formula has a plus component
125 \value HAS_MINUS_COMPONENT
126 The action formula has a minus component
127 \value HAS_BOTH_COMPONENTS
128 The action formula has both plus and minus components
129 */
130
131
132 /*!
133 \variable MsXpS::libXpertMassCore::Formula::m_title
134
135 \brief String representing the title of the action-formula.
136
137 The title is the descriptive string in double quotes that is
138 associated to a formula, like this:
139
140 \c "Acetylation"-H2O+CH3COOH
141 */
142
143 /*!
144 \variable MsXpS::libXpertMassCore::Formula::m_actionFormula
145
146 \brief String representing the action-formula.
147 */
148
149 /*!
150 \variable MsXpS::libXpertMassCore::Formula::m_plusFormula
151
152 \brief String representing the "plus" component of the main m_actionFormula.
153
154 This member datum is set upon parsing of m_actionFormula.
155 */
156
157 /*!
158 \variable MsXpS::libXpertMassCore::Formula::m_minusFormula
159
160 \brief String representing the "minus" component of the main m_minusFormula.
161
162 This member datum is set upon parsing of m_actionFormula.
163 */
164
165 /*!
166 \variable MsXpS::libXpertMassCore::Formula::m_symbolCountMap
167
168 \brief Map relating the symbols (as keys) found in the formula and their
169 counts (atoms, in fact, as values).
170
171 Note that the count value type is double, which allows for interesting
172 things to be done with Formula. Also, the count value might be negative if the
173 net mass of an action-formula is negative.
174
175 \sa Formula::splitActionParts()
176 */
177
178 /*!
179 \variable int MsXpS::libXpertMassCore::Formula::m_forceCountIndex
180
181 \brief The m_forceCountIndex tells if when defining a chemical composition
182 formula, the index '1' is required when the count of a symbol is not specified
183 and thus considered to be '1' by default. If true, water should be described as
184 "H2O1", if false, it might be described as "H2O".
185 */
186
187
188 /*!
189 \variable MsXpS::libXpertMassCore::Formula::m_isValid
190
191 \brief The status of the formula.
192 */
193
194
195 /*!
196 \brief Constructs a Formula instance.
197 */
198 8361 Formula::Formula(QObject *parent): QObject(parent)
199 {
200 8361 }
201
202 /*!
203 \brief Constructs a Formula instance using the XML \a element according
204 to the \a version.
205 */
206 Formula::Formula(const QDomElement &element, int version, QObject *parent)
207 : QObject(parent)
208 {
209 bool result = renderXmlFormulaElement(element, version);
210
211 if(!result)
212 qCritical()
213 << "Failed rendering XML element for construction of Formula instance.";
214 }
215
216 /*!
217 \brief Constructs a formula initialized with the \a formula_string
218 action-formula string.
219
220 \a formula_string needs not be an action-formula, but it might be an
221 action-formula. This formula gets copied into the \c m_actionFormula without any
222 processing afterwards.
223
224 The default status of the Formula (m_isValid) is let to false because the
225 formula cannot be validated without reference isotopic data.
226 */
227 174873 Formula::Formula(const QString &formula_string, QObject *parent)
228
3/4
✓ Branch 1 taken 123084 times.
✓ Branch 2 taken 51789 times.
✓ Branch 4 taken 174873 times.
✗ Branch 5 not taken.
297957 : QObject(parent), m_actionFormula{formula_string}
229 {
230
1/2
✓ Branch 1 taken 174873 times.
✗ Branch 2 not taken.
174873 m_title = removeTitle();
231 174873 qDebug() << "The title:" << m_title;
232
233
1/2
✓ Branch 1 taken 174873 times.
✗ Branch 2 not taken.
174873 removeSpaces();
234 174873 qDebug() << "Formula after removing spaces:" << m_actionFormula;
235
236
2/2
✓ Branch 0 taken 51789 times.
✓ Branch 1 taken 123084 times.
174873 if(m_actionFormula.isEmpty())
237 {
238
2/4
✓ Branch 1 taken 51789 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 51789 times.
✗ Branch 5 not taken.
51789 qCritical() << "Formula created empty.";
239 }
240
241
3/5
✗ Branch 0 not taken.
✓ Branch 1 taken 174873 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 51876 times.
✓ Branch 4 taken 122997 times.
174873 if(!checkSyntax())
242 {
243
1/2
✓ Branch 1 taken 51876 times.
✗ Branch 2 not taken.
103752 qCritical() << "Formula constructed with an action formula that does not "
244
1/2
✓ Branch 1 taken 51876 times.
✗ Branch 2 not taken.
51876 "pass the checkSyntax test.";
245 }
246
247 // Because there has been no validation with IsotopicData, the default
248 // false value is maintained for m_isValid.
249 174873 }
250
251 /*!
252 \brief Constructs a formula as a copy of \a other.
253
254 The copy is deep with \e{all} the data copied from \a other to the new
255 formula. There is no processing afterwards.
256 */
257 12945 Formula::Formula(const Formula &other, QObject *parent)
258 : QObject(parent),
259 25890 m_title(other.m_title),
260
2/2
✓ Branch 0 taken 5776 times.
✓ Branch 1 taken 7169 times.
12945 m_actionFormula(other.m_actionFormula),
261
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12942 times.
12945 m_plusFormula(other.m_plusFormula),
262
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12942 times.
12945 m_minusFormula(other.m_minusFormula),
263
1/2
✓ Branch 1 taken 12945 times.
✗ Branch 2 not taken.
12945 m_symbolCountMap(other.m_symbolCountMap),
264
2/2
✓ Branch 1 taken 2389 times.
✓ Branch 2 taken 10556 times.
12945 m_isValid(other.m_isValid)
265 {
266 // GCOV_EXCL_START
267 // Sanity check
268 if(!removeTitle().isEmpty())
269 {
270 qCritical() << "The formula string still has a title.";
271 m_isValid = false;
272 }
273 // GCOV_EXCL_STOP
274
275
1/2
✓ Branch 1 taken 12945 times.
✗ Branch 2 not taken.
12945 removeSpaces();
276
277
2/2
✓ Branch 0 taken 7169 times.
✓ Branch 1 taken 5776 times.
12945 if(m_actionFormula.isEmpty())
278 {
279
1/2
✓ Branch 1 taken 7169 times.
✗ Branch 2 not taken.
14338 qCritical()
280
1/2
✓ Branch 1 taken 7169 times.
✗ Branch 2 not taken.
7169 << "Copy-constructed Formula created with empty action-formula.";
281 7169 m_isValid = false;
282 }
283
284
3/5
✗ Branch 0 not taken.
✓ Branch 1 taken 12945 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7169 times.
✓ Branch 4 taken 5776 times.
12945 if(!checkSyntax())
285 {
286
1/2
✓ Branch 1 taken 7169 times.
✗ Branch 2 not taken.
14338 qCritical()
287 << "Formula constructed with an action formula that does not pass the "
288
1/2
✓ Branch 1 taken 7169 times.
✗ Branch 2 not taken.
7169 "checkSyntax test.";
289 7169 m_isValid = false;
290 }
291 12945 }
292
293 /*!
294 \brief Destructs this formula.
295
296 There is nothing to be delete explicitly.
297 */
298 390522 Formula::~Formula()
299 {
300 390522 }
301
302 /*!
303 \brief Return a newly allocated Formula that is initialized using \a other and setting its parent to \a parent.
304 */
305 Formula *
306 Formula::clone(const Formula &other, QObject *parent)
307 {
308 Formula *copy_p = new Formula(parent);
309 copy_p->initialize(other);
310 return copy_p;
311 }
312
313 /*!
314 \brief Return a reference to this Formula after having initialized it using \a other.
315 */
316 Formula &
317 Formula::initialize(const Formula &other)
318 {
319 if(&other == this)
320 return *this;
321
322 m_title = other.m_title;
323 m_actionFormula = other.m_actionFormula;
324 m_plusFormula = other.m_plusFormula;
325 m_minusFormula = other.m_minusFormula;
326 m_symbolCountMap = other.m_symbolCountMap;
327 m_isValid = other.m_isValid;
328
329 // GCOV_EXCL_START
330 // Sanity check
331 if(!removeTitle().isEmpty())
332 {
333 qCritical() << "The formula string still has a title.";
334 m_isValid = false;
335 }
336 // GCOV_EXCL_STOP
337
338 removeSpaces();
339
340 if(m_actionFormula.isEmpty())
341 {
342 qCritical() << "Formula created empty.";
343 m_isValid = false;
344 }
345
346 if(!checkSyntax())
347 {
348 qCritical() << "Formula set to an action formula that does not pass the "
349 "checkSyntax test.";
350 m_isValid = false;
351 }
352
353 return *this;
354 }
355
356 //////////////// THE ACTIONFORMULA /////////////////////
357 /*! Sets the action-formula \a formula to this Formula.
358
359 The \a formula is copied to this m_actionFormula. Since the new formula was
360 not validated, the status of this formula (m_isValid) is set to false. No
361 other processing is performed afterwards.
362 */
363 void
364 11527 Formula::setActionFormula(const QString &formula)
365 {
366 11527 m_actionFormula = formula;
367 11527 m_title = removeTitle();
368
369 11527 removeSpaces();
370
371
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11525 times.
11527 if(m_actionFormula.isEmpty())
372 {
373
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 qCritical() << "Formula set to an empty string.";
374 }
375
376
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 11523 times.
11527 if(!checkSyntax())
377 8 qCritical() << "Formula set to an action formula that does not pass the "
378
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 "checkSyntax test.";
379
380 11527 m_isValid = false;
381 11527 }
382
383 /*! Sets the action-formula from \a formula to this Formula.
384
385 The action-formula from \a formula is copied to this m_actionFormula. Since
386 the new formula was not validated, the status of this formula (m_isValid) is
387 set to false. No other processing is performed afterwards.
388 */
389 void
390 859 Formula::setActionFormula(const Formula &formula)
391 {
392 859 m_actionFormula = formula.m_actionFormula;
393 859 m_title = removeTitle();
394
395
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 858 times.
859 if(m_actionFormula.isEmpty())
396 {
397
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 qCritical() << "Formula set to an empty string.";
398 }
399
400
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 858 times.
859 if(!checkSyntax())
401 2 qCritical() << "Formula set to an action formula that does not pass the "
402
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "checkSyntax test.";
403
404 859 m_isValid = false;
405 859 }
406
407 /*!
408 \brief Appends to this formula the \a action_formula.
409
410 The \a action_formula string is first stripped of its whitespace with
411 QString::simplified(). Then it is appended to the m_actionFormula only if:
412
413 \list
414
415 \li It is not empty (if empty the function does nothing and returns false)
416
417 \li It passes the checkSyntax() test (if not, the function does nothing and
418 returns false)
419
420 \endlist
421
422 The status of this Formula is set to false because there was not explicit
423 validation done.
424
425 The function returns false if nothing was changed and true if something was
426 actually appended to m_actionFormula.
427 */
428 bool
429 9 Formula::appendActionFormula(const QString &action_formula)
430 {
431 9 QString local_action_formula = action_formula.simplified();
432
433
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 if(local_action_formula.isEmpty())
434 return false;
435
436 // Remove the spaces before appending.
437
3/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
7 local_action_formula.remove(QRegularExpression("\\s+"));
438
439 // Removes the title from the action-formula
440
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 Formula temp_formula(local_action_formula);
441
442
3/5
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 6 times.
7 if(!temp_formula.checkSyntax())
443 {
444
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical()
445
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "Cannot append formula that does not pass the checkSyntax test.";
446
447 1 return false;
448 }
449 else
450 {
451 // We append the action-formula stripped of its potential title.
452
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 m_actionFormula.append(temp_formula.getActionFormula());
453 }
454
455 // The formula has not been validated formally.
456 6 m_isValid = false;
457
458 6 return true;
459 16 }
460
461 /*!
462 \brief Returns the action formula, along the the title if \a with_title is
463 true.
464 */
465 QString
466 11875 Formula::getActionFormula(bool with_title) const
467 {
468 // qDebug() << "The title is:" << m_title;
469
470
4/4
✓ Branch 0 taken 3368 times.
✓ Branch 1 taken 8507 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 3360 times.
11875 if(with_title && !m_title.isEmpty())
471
2/4
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
8 return QString("\"%1\"%2").arg(m_title).arg(m_actionFormula);
472
473
2/2
✓ Branch 0 taken 7932 times.
✓ Branch 1 taken 3935 times.
19807 return m_actionFormula;
474 }
475
476 //////////////// THE TITLE /////////////////////
477 /*!
478 \brief Sets the title leading component of a formula to \a title.
479
480 A fully self-described formula might look like this:
481
482 "Acetylation"+CH3COOH-H2O
483
484 The first string between double quotes is called the title.
485 */
486 void
487 2 Formula::setTitle(const QString &title)
488 {
489 2 m_title = title;
490 2 }
491
492 /*!
493 \brief Returns the "title" leading component of a formula.
494
495 A fully selfdescribed formula might look like this:
496
497 "Acetylation"+CH3COOH-H2O
498
499 The first string between quotes is called the title.
500 */
501 QString
502 23 Formula::getTitle() const
503 {
504
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 4 times.
23 return m_title;
505 }
506
507 /*!
508 \brief Returns the title from the member action-formula.
509
510 The \e{title} of a formula is the string, enclosed in
511 double quotes, that is located in front of the actual chemical
512 action-formula. This function removes that \e{title} string from the
513 member action-formula using a QRegularExpression.
514 */
515 QString
516 206034 Formula::extractTitle() const
517 {
518
1/2
✓ Branch 2 taken 206034 times.
✗ Branch 3 not taken.
206034 QRegularExpression regexp("^\"(.*)\"");
519
520
1/2
✓ Branch 1 taken 206034 times.
✗ Branch 2 not taken.
206034 QRegularExpressionMatch match = regexp.match(m_actionFormula);
521
522
3/4
✓ Branch 1 taken 206034 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 205558 times.
✓ Branch 4 taken 476 times.
206034 if(!match.hasMatch())
523 {
524 // qDebug() << "Has no match.";
525 205558 return QString();
526 }
527
528 // qDebug() << "Match:" << match.captured(0);
529
530
1/2
✓ Branch 1 taken 476 times.
✗ Branch 2 not taken.
476 return match.captured(1);
531 206034 }
532
533 /*!
534 \brief Removes the title from m_actionFormula and returns it.
535
536 The \e{title} of a formula is the string, enclosed in
537 double quotes, that is located in front of the actual chemical
538 action-formula. This function removes that \e{title} string from the
539 member action-formula using a QRegularExpression.
540
541 The caller may use the returned title string to set it to m_title.
542
543 \sa Formula::extractTitle(), Formula::setTitle()
544 */
545 QString
546 206034 Formula::removeTitle()
547 {
548 206034 QString title = extractTitle();
549
550
2/2
✓ Branch 0 taken 476 times.
✓ Branch 1 taken 205558 times.
206034 if(!title.isEmpty())
551 {
552
2/4
✓ Branch 1 taken 476 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 476 times.
✗ Branch 5 not taken.
476 QRegularExpression regexp("^(\".*\")");
553
1/2
✓ Branch 1 taken 476 times.
✗ Branch 2 not taken.
476 m_actionFormula = m_actionFormula.remove(regexp);
554 476 }
555
556 206034 return title;
557 }
558
559 //////////////// THE ATOM INDEX LOGIC /////////////////////
560
561 /*!
562 \brief Sets m_forceCountIndex to \a forceCountIndex.
563
564 When a formula contains a chemical element in a single copy, it is standard
565 practice to omit the count index: H2O is the same as H2O1. If forceCountIndex is
566 true, then the formula has to be in the form H2O1. This is required for some
567 specific calculations. The status (m_isValid) is set to false.
568 */
569 void
570 12 Formula::setForceCountIndex(bool forceCountIndex)
571 {
572 12 m_forceCountIndex = forceCountIndex;
573 12 }
574
575 /*!
576 \brief Returns true if single atoms should have a count index, false otherwise.
577
578 The force count index is set to true, when a formula needs to have atom indices
579 set even if the count for these atoms is 1. For example, H2O1 as compared to
580 H2O.
581 */
582 bool
583 1 Formula::isForceCountIndex() const
584 {
585 1 return m_forceCountIndex;
586 }
587
588 //////////////// THE SYNTAX CHECKING LOGIC /////////////////////
589
590 #if 0
591
592 Old version that parsed the action-formula char by char.
593 bool
594 Formula::checkSyntax(const QString &formula, bool forceCountIndex)
595 {
596 // Static function.
597
598 // qDebug() << "Checking syntax with formula:" << formula;
599
600 QChar curChar;
601
602 bool gotUpper = false;
603 bool wasSign = false;
604 bool wasDigit = false;
605
606 // Because the formula that we are analyzing might contain a title
607 // and spaces , we first remove these. But make a local copy of
608 // the member datum.
609
610 QString localFormula = formula;
611
612 // One formula can be like this:
613
614 // "Decomposed adenine" C5H4N5 +H
615
616 // The "Decomposed adenine" is the title
617 // The C5H4N5 +H is the formula.
618
619 localFormula.remove(QRegularExpression("\".*\""));
620
621 // We want to remove all the possibly-existing spaces.
622
623 localFormula.remove(QRegularExpression("\\s+"));
624
625
626 for(int iter = 0; iter < localFormula.length(); ++iter)
627 {
628 curChar = localFormula.at(iter);
629
630 // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
631 //<< "Current character:" << curChar;
632
633 // FIXME One improvement that would ease modelling the Averagine would
634 // be to silently allow double formula indices (that is, double atom
635 // counts). They would not be compulsory
636
637 if(curChar.category() == QChar::Number_DecimalDigit)
638 {
639 // We are parsing a digit.
640
641 // We may not have a digit after a +/- sign.
642 if(wasSign)
643 return false;
644
645 wasSign = false;
646 wasDigit = true;
647
648 continue;
649 }
650 else if(curChar.category() == QChar::Letter_Lowercase)
651 {
652 // Current character is lowercase, which means we are inside
653 // of an atom symbol, such as Ca(the 'a') or Nob(either
654 // 'o' or 'b'). Thus, gotUpper should be true !
655
656 if(!gotUpper)
657 return false;
658
659 // We may not have a lowercase character after a +/- sign.
660 if(wasSign)
661 return false;
662
663 // Let the people know that we have parsed a lowercase char
664 // and not a digit.
665 wasSign = false;
666
667 wasDigit = false;
668 }
669 else if(curChar.category() == QChar::Letter_Uppercase)
670 {
671 // Current character is uppercase, which means that we are
672 // at the beginning of an atom symbol.
673
674 // There are two cases:
675 // 1. We are starting for the very beginning of the formula, and
676 // nothing came before this upper case character. That's fine.
677 // 2. We had previously parsed a segment of the formula, and in this
678 // case, we are closing a segment. If the parameter
679 // obligatoryCountIndex is true, then we need to ensure that the
680 // previous element had an associated number, even it the count
681 // element is 1. This is required for the IsoSpec stuff in the gui
682 // programs.
683
684 if(iter > 0)
685 {
686 if(forceCountIndex)
687 {
688 if(!wasDigit)
689 {
690 qDebug()
691 << "Returning false because upper case char was not"
692 "preceded by digit while not at the first char of "
693 "the formula";
694
695 return false;
696 }
697 }
698 }
699
700 // Let the people know what we got:
701
702 wasSign = false;
703 gotUpper = true;
704 wasDigit = false;
705 }
706 else
707 {
708 if(curChar != '+' && curChar != '-')
709 return false;
710 else
711 {
712 // We may not have 2 +/- signs in a raw.
713 if(wasSign)
714 return false;
715 }
716
717 wasSign = true;
718 gotUpper = false;
719 wasDigit = false;
720 }
721 }
722 // end for (int iter = 0 ; iter < localFormula.length() ; ++iter)
723
724 // Note that if we want an obligatory count index, then, at the end of the
725 // formula, *compulsorily* we must have parsed a digit.
726
727 if(forceCountIndex && !wasDigit)
728 {
729 qDebug()
730 << "Returning false because the formula does not end with a digit.";
731
732 return false;
733 }
734
735 // At this point we found no error condition.
736 return true;
737 }
738 #endif
739
740 /*!
741 \brief Returns true if the member action-formula is syntactically valid, false
742 otherwise.
743
744 \sa checkSyntax(const QString &formula, bool force_count_index)
745 */
746 bool
747 392250 Formula::checkSyntax() const
748 {
749 // The default formula is always m_actionFormula.
750
751
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
392250 return checkSyntax(m_actionFormula, m_forceCountIndex);
752 }
753
754 /*!
755 \brief Returns true if the \a formula_string action-formula is syntactically
756 valid, false otherwise.
757
758 If \a force_count_index is true, the syntax check accounts for the
759 requirement that all the symbols in the formula must be indexed, even if that
760 symbol's count is 1. This means that H2O would not pass the check, while H2O1
761 would.
762
763 The formula is first stripped of its title (if any), then all the spaces are
764 removed.
765
766 MsXpS::libXpertMassCore::Formula::subFormulaRegExp is then used to extract each
767 "plus" and / or "minus" component while checking its syntactic validity.
768
769 \note The syntax checking code does not verify that the action-formula is
770 chemically valid, that is, the "Cz4" symbol / count pair would check even if
771 the Cz chemical element does not exist.
772
773 \sa validate()
774 */
775 bool
776 392250 Formula::checkSyntax(const QString &formula_string, bool force_count_index)
777 {
778 // qDebug() << "Checking syntax of formula string" << formula_string;
779
780 // Because the formula that we are analyzing might contain a title
781 // and spaces , we first remove these. But make a local copy of
782 // the member datum.
783
784
2/2
✓ Branch 0 taken 279813 times.
✓ Branch 1 taken 112437 times.
392250 QString local_formula_string = formula_string;
785
786 // One formula can be like this:
787
788 // "Decomposed adenine" C5H4N5 +H
789
790 // The "Decomposed adenine" is the title
791 // The C5H4N5 +H is the formula.
792
793
3/6
✓ Branch 1 taken 392250 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 392250 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 392250 times.
✗ Branch 8 not taken.
392250 local_formula_string.remove(QRegularExpression("\".*\""));
794
795 // We want to remove all the possibly-existing spaces.
796
797
3/6
✓ Branch 1 taken 392250 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 392250 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 392250 times.
✗ Branch 8 not taken.
392250 local_formula_string.remove(QRegularExpression("\\s+"));
798
799
2/2
✓ Branch 0 taken 112437 times.
✓ Branch 1 taken 279813 times.
392250 if(local_formula_string.isEmpty())
800 {
801
2/4
✓ Branch 1 taken 112437 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 112437 times.
✗ Branch 5 not taken.
112437 qWarning() << "The action formula string is empty.";
802 112437 return false;
803 }
804
805 // qDebug() << "The formula is:" << local_formula_string;
806
807 // The raw formula might include:
808 // +/- sign before the symbol
809 // then the symbol (one uppercase any lowercase)
810 // then the count as an integer or a double.
811
812 // Attention, the regular expression logic below works by finding
813 // patterns that match the regexp, BUT that does not means that
814 // spurious substrings at the beginning or at the end cannot be
815 // present and go undetected, like this: "3Cz3H12O6N14L2", where
816 // the '3' on the left is not seen.
817
818 // We thus need to first ensure that the string never begins with
819 // something else than a +/- sign (optionally) and a letter (uppercase).
820
2/4
✓ Branch 1 taken 279813 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 279813 times.
✗ Branch 5 not taken.
279813 QRegularExpression start_of_formula("^[+-]?[A-Z]");
821
822
1/2
✓ Branch 1 taken 279813 times.
✗ Branch 2 not taken.
279813 QRegularExpressionMatch match = start_of_formula.match(local_formula_string);
823
3/4
✓ Branch 1 taken 279813 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 176 times.
✓ Branch 4 taken 279637 times.
279813 if(!match.hasMatch())
824 {
825
3/6
✓ Branch 1 taken 176 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 176 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 176 times.
✗ Branch 8 not taken.
176 qCritical() << "Error at start of formula string" << formula_string;
826
827 176 return false;
828 }
829
830 // Like wise for formulas that do not end either by [A-Z] or [a-z] or \\d.
831
2/4
✓ Branch 1 taken 279637 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 279637 times.
✗ Branch 5 not taken.
279637 QRegularExpression end_of_formula("[A-Za-z\\d]$");
832
1/2
✓ Branch 1 taken 279637 times.
✗ Branch 2 not taken.
279637 match = end_of_formula.match(local_formula_string);
833
3/4
✓ Branch 1 taken 279637 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 279634 times.
279637 if(!match.hasMatch())
834 {
835
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 qCritical() << "Error at end of formula string.";
836
837 3 return false;
838 }
839
840
1/2
✓ Branch 1 taken 279634 times.
✗ Branch 2 not taken.
279634 for(const QRegularExpressionMatch &match :
841
4/6
✓ Branch 1 taken 279634 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 279634 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1046750 times.
✓ Branch 7 taken 279632 times.
1326382 Utils::subFormulaRegExp.globalMatch(local_formula_string))
842 {
843
1/2
✓ Branch 1 taken 1046750 times.
✗ Branch 2 not taken.
1046750 QString full_match = match.captured(0);
844
845 // qDebug() << "The full sub-match:" << full_match;
846
847
1/2
✓ Branch 1 taken 1046750 times.
✗ Branch 2 not taken.
1046750 QString sign = match.captured(1);
848
1/2
✓ Branch 1 taken 1046750 times.
✗ Branch 2 not taken.
1046750 QString symbol = match.captured(2);
849
1/2
✓ Branch 1 taken 1046750 times.
✗ Branch 2 not taken.
1046750 QString count_string = match.captured(3);
850
851
2/2
✓ Branch 0 taken 1013579 times.
✓ Branch 1 taken 33171 times.
1046750 if(!count_string.isEmpty())
852 {
853 1013579 bool ok = false;
854 // Verify that it correctly converts to double.
855
1/2
✓ Branch 1 taken 1013579 times.
✗ Branch 2 not taken.
1013579 count_string.toDouble(&ok);
856
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1013579 times.
1013579 if(!ok)
857 return false;
858 }
859 else
860 {
861
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 33169 times.
33171 if(force_count_index)
862 {
863
4/8
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
2 qCritical() << "Error: symbol" << symbol << "has no index.";
864
865 2 return false;
866 }
867 else
868 {
869 // qDebug() << "Symbol" << symbol
870 // << "has no index but that is tolerated.";
871 }
872 }
873
874 // qDebug() << "Sign:" << match.captured(1) << "Symbol:" <<
875 // match.captured(2)
876 // << "Count:" << match.captured(3);
877
1/2
✓ Branch 4 taken 1046748 times.
✗ Branch 5 not taken.
1326384 }
878
879 // qDebug() << "Returning true";
880
881 // qDebug() << "Checked syntax of formula " << formula_string
882 // << "returning true";
883
884 279632 return true;
885 672063 }
886
887 //////////////// OPERATORS /////////////////////
888 /*!
889 \brief Initializes all the member data of this formula by copying to it the
890 data from \a other.
891
892 The copy is deep with \e{all} the data from \a other being copied into this
893 formula.
894
895 There is no processing afterwards.
896 */
897 Formula &
898 5830 Formula::operator=(const Formula &other)
899 {
900
1/2
✓ Branch 0 taken 5830 times.
✗ Branch 1 not taken.
5830 if(&other == this)
901 return *this;
902
903 5830 m_title = other.m_title;
904 5830 m_actionFormula = other.m_actionFormula;
905 5830 m_plusFormula = other.m_plusFormula;
906 5830 m_minusFormula = other.m_minusFormula;
907 5830 m_symbolCountMap = other.m_symbolCountMap;
908 5830 m_isValid = other.m_isValid;
909
910 // GCOV_EXCL_START
911 // Sanity check
912 if(!removeTitle().isEmpty())
913 {
914 qCritical() << "The formula string still has a title.";
915 m_isValid = false;
916 }
917 // GCOV_EXCL_STOP
918
919 5830 removeSpaces();
920
921
2/2
✓ Branch 0 taken 1616 times.
✓ Branch 1 taken 4214 times.
5830 if(m_actionFormula.isEmpty())
922 {
923
1/2
✓ Branch 2 taken 1616 times.
✗ Branch 3 not taken.
1616 qCritical() << "Formula created empty.";
924 1616 m_isValid = false;
925 }
926
927
2/2
✓ Branch 1 taken 1618 times.
✓ Branch 2 taken 4212 times.
5830 if(!checkSyntax())
928 {
929 3236 qCritical() << "Formula set to an action formula that does not pass the "
930
1/2
✓ Branch 1 taken 1618 times.
✗ Branch 2 not taken.
1618 "checkSyntax test.";
931 1618 m_isValid = false;
932 }
933
934 return *this;
935 }
936
937 /*! Returns true if this Formula and \a other are identical, false otherwise.
938
939 The comparison is only performed on the title and action-formula, not on
940 any other member data that actually derive from the processing of the
941 action-formula.
942 */
943 bool
944 72 Formula::operator==(const Formula &other) const
945 {
946
2/2
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 1 times.
72 if(&other == this)
947 return true;
948
949
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 4 times.
71 if(m_title != other.m_title || m_actionFormula != other.m_actionFormula)
950 return false;
951
952 return true;
953 }
954
955 /*! Returns true if this Formula and \a other are different, false otherwise.
956
957 Returns the negated result of operator==().
958 */
959 bool
960 37 Formula::operator!=(const Formula &other) const
961 {
962
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 1 times.
37 if(&other == this)
963 return false;
964
965 36 return !operator==(other);
966 }
967
968 //////////////// THE SUB-FORMULAS OPERATIONS /////////////////////
969
970
971 /*!
972 \brief Tells the "plus" ('+') and "minus" ('-') parts in the member
973 action-formula.
974
975 Parses the m_actionFormula action-formula and separates all the minus
976 components of that action-formula from all the plus components. The different
977 components are set to \a plus_formula and \a minus_formula.
978
979 At the end of the split work, each sub-formula (\a plus_formula and \a
980 minus_formula) is actually parsed for validity, using the \a isotopic_data_csp
981 IsotopicData as reference.
982
983 If \a times is not 1, then the accounting of the plus/minus formulas is
984 compounded by this factor.
985
986 If \a store is true, the symbol/count data obtained while
987 parsing of the plus/minus action-formula components are stored in
988 \a symbol_count_map.
989
990 If \a reset is true, the symbol/count data in
991 \a symbol_count_map are reset before the parsing operation. Setting this
992 parameter to false may be useful if the caller needs to "accumulate" the
993 accounting of the formulas.
994
995 The parsing of the action-formula is performed by performing its deconstruction
996 using \l{Utils::subFormulaRegExp}.
997
998 Returns FormulaSplitResult::FAILURE if the splitting failed,
999 FormulaSplitResult::HAS_PLUS_COMPONENT if at least one of the components of the
1000 action-formula was found to be of type plus,
1001 FormulaSplitResult::HAS_MINUS_COMPONENT if at least one of the components of the
1002 action-formula was found to be of type minus. The result can be an OR'ing of
1003 both values (FormulaSplitResult::HAS_BOTH_COMPONENTS) in the m_actionFormula
1004 action-formula.
1005
1006 Because this function does not modify member data, by writing the results of the
1007 computations to the passed variables, it is declared const.
1008
1009 If this function completes successfully, then that validates it successfully
1010 (syntax ok, and symbols known to the reference isotopic data), and m_isValid
1011 is set to true, otherwise m_isValid is set to false.
1012 */
1013 Formula::SplitResult
1014 164578 Formula::splitActionParts(IsotopicDataCstSPtr isotopic_data_csp,
1015 QString &plus_formula,
1016 QString &minus_formula,
1017 std::map<QString, double> &symbol_count_map,
1018 double times,
1019 bool store,
1020 bool reset) const
1021 {
1022
2/4
✓ Branch 0 taken 164578 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 164578 times.
164578 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
1023 164578 !isotopic_data_csp->size())
1024 {
1025 qCritical(
1026 "Cannot split action parts of Formula without available IsotopicData.");
1027
1028 m_isValid = false;
1029 return SplitResult::FAILURE;
1030 }
1031
1032 164578 SplitResult formula_split_result = SplitResult::NOT_SET;
1033
1034 // We are asked to put all the '+' components of the formula
1035 // into corresponding formula and the same for the '-' components.
1036
1037 164578 plus_formula.clear();
1038 164578 minus_formula.clear();
1039
1040
2/2
✓ Branch 0 taken 156323 times.
✓ Branch 1 taken 8255 times.
164578 if(reset)
1041 156323 symbol_count_map.clear();
1042
1043 #if 0
1044
1045 // This old version tried to save computing work, but it then anyways
1046 // calls for parsing of the formula which is the most computing-intensive
1047 // part. Thus we now rely on the regular expression to simultaneously
1048 // check the syntax, divide the formula into its '+' and '-' parts,
1049 // and finally check that the symbol is known to the isotopic data.
1050 ^
1051 // If the formula does not contain any '-' character, then we
1052 // can approximate that all the formula is a '+' formula, that is, a
1053 // plusFormula:
1054
1055 if(actions() == '+')
1056 {
1057 // qDebug() << "Only plus actions.";
1058
1059 plus_formula.append(formula);
1060
1061 // At this point we want to make sure that we have a correct
1062 // formula. Remove all the occurrences of the '+' sign.
1063 plus_formula.replace(QString("+"), QString(""));
1064
1065 if(plus_formula.length() > 0)
1066 {
1067 // qDebug() << "splitActionParts: with plus_formula:" <<
1068 // plus_formula;
1069
1070 if(!parse(isotopic_data_csp, plus_formula, times, store, reset))
1071 return FormulaSplitResult::FAILURE;
1072 else
1073 return FORMULA_SPLIT_PLUS;
1074 }
1075 }
1076 // End of
1077 // if(actions() == '+')
1078
1079 // If we did not return at previous block that means there are at least one
1080 // '-' component in the formula. we truly have to iterate in the formula...
1081 #endif
1082
1083
1084 // See the explanations in the header file for the member datum
1085 // m_subFormulaRegExp and its use with globalMatch(). One thing that is
1086 // important to see, is that the RegExp matches a triad : [ [sign or not]
1087 // [symbol] [count] ], so, if we have, says, formula "-H2O", it would match:
1088
1089 // First '-' 'H' '2'
1090 // Second <no sign> 'O' <no count = 1>
1091
1092 // The problem is that at second match, the algo thinks that O1 is a +
1093 // formula, while in fact it is part of a larger minus formula: -H2O. So we
1094 // need to check if, after a '-' in a formula, there comes or not a '+'. If so
1095 // we close the minus formula and start a plus formula, if not, we continue
1096 // adding matches to the minus formula.
1097
1098 // qDebug() << "Now regex parsing with globalMatch feature of formula:"
1099 // << formula;
1100
1101 164578 bool was_minus_formula = false;
1102
1103 164578 Utils utils;
1104
1105
1/2
✓ Branch 1 taken 164578 times.
✗ Branch 2 not taken.
164578 for(const QRegularExpressionMatch &match :
1106
4/6
✓ Branch 1 taken 164578 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 164578 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 522032 times.
✓ Branch 7 taken 164572 times.
686604 Utils::subFormulaRegExp.globalMatch(m_actionFormula))
1107 {
1108
1/2
✓ Branch 1 taken 522032 times.
✗ Branch 2 not taken.
522032 QString sub_match = match.captured(0);
1109
1110 // qDebug() << "Entering [+-]?<symbol><count?> sub-match:" << sub_match;
1111
1112
1/2
✓ Branch 1 taken 522032 times.
✗ Branch 2 not taken.
522032 QString sign = match.captured(1);
1113
1/2
✓ Branch 1 taken 522032 times.
✗ Branch 2 not taken.
522032 QString symbol = match.captured(2);
1114
1/2
✓ Branch 1 taken 522032 times.
✗ Branch 2 not taken.
522032 QString count_as_string = match.captured(3);
1115 522032 double count_as_double = 1.0;
1116
1117
2/2
✓ Branch 0 taken 505657 times.
✓ Branch 1 taken 16375 times.
522032 if(!count_as_string.isEmpty())
1118 {
1119 505657 bool ok = false;
1120
1/2
✓ Branch 1 taken 505657 times.
✗ Branch 2 not taken.
505657 count_as_double = count_as_string.toDouble(&ok);
1121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 505657 times.
505657 if(!ok)
1122 {
1123 m_isValid = false;
1124 return SplitResult::FAILURE;
1125 }
1126 }
1127 else
1128 {
1129
1/2
✓ Branch 1 taken 16375 times.
✗ Branch 2 not taken.
16375 count_as_string = "1";
1130 }
1131
1132 // Check that the symbol is known to the isotopic data.
1133 // qDebug() << "The symbol:" << symbol << "with count:" <<
1134 // count_as_double;
1135 522032 int count = 0;
1136
3/4
✓ Branch 1 taken 522032 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 522026 times.
522032 if(!isotopic_data_csp->containsSymbol(symbol, count))
1137 {
1138 6 m_isValid = false;
1139 6 return SplitResult::FAILURE;
1140 }
1141
1142 // Determine if there was a sign
1143
2/2
✓ Branch 1 taken 5551 times.
✓ Branch 2 taken 516475 times.
522026 if(sign == "-")
1144 {
1145 // qDebug() << "Appending found minus formula:"
1146 // << QString("%1%2").arg(symbol).arg(count_as_string);
1147
1148
1/2
✓ Branch 1 taken 5551 times.
✗ Branch 2 not taken.
5551 minus_formula.append(
1149
3/6
✓ Branch 1 taken 5551 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5551 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 5551 times.
✗ Branch 8 not taken.
11102 QString("%1%2").arg(symbol).arg(count_as_string));
1150
1151
2/2
✓ Branch 0 taken 3097 times.
✓ Branch 1 taken 2454 times.
5551 formula_split_result |= SplitResult::HAS_MINUS_COMPONENT;
1152
1153
2/2
✓ Branch 0 taken 3097 times.
✓ Branch 1 taken 2454 times.
5551 if(store)
1154 {
1155 // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
1156 // << count_as_double * static_cast<double>(times);
1157
1158 3097 accountSymbolCountPair(symbol_count_map,
1159 symbol,
1160
1/2
✓ Branch 1 taken 3097 times.
✗ Branch 2 not taken.
3097 -1 * count_as_double *
1161 static_cast<double>(times));
1162
1163 // qDebug() << " ...done.";
1164 }
1165
1166 // Let next round know that we are inside a minus formula group.
1167 was_minus_formula = true;
1168 }
1169
4/4
✓ Branch 0 taken 505572 times.
✓ Branch 1 taken 10903 times.
✓ Branch 2 taken 502835 times.
✓ Branch 3 taken 2737 times.
516475 else if(sign.isEmpty() && was_minus_formula)
1170 {
1171 // qDebug() << "Appending new unsigned formula to the minus formula:"
1172 // << QString("%1%2").arg(symbol).arg(count_as_string);
1173
1174
1/2
✓ Branch 1 taken 2737 times.
✗ Branch 2 not taken.
2737 minus_formula.append(
1175
3/6
✓ Branch 1 taken 2737 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2737 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2737 times.
✗ Branch 8 not taken.
5474 QString("%1%2").arg(symbol).arg(count_as_string));
1176
1177
2/2
✓ Branch 0 taken 1262 times.
✓ Branch 1 taken 1475 times.
2737 if(store)
1178 {
1179 // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
1180 // << count_as_double * static_cast<double>(times);
1181
1182 1262 accountSymbolCountPair(symbol_count_map,
1183 symbol,
1184
1/2
✓ Branch 1 taken 1262 times.
✗ Branch 2 not taken.
1262 -1 * count_as_double *
1185 static_cast<double>(times));
1186
1187 // qDebug() << " ...done.";
1188 }
1189
1190 // Let next round know that we are still inside a minus formula group.
1191 was_minus_formula = true;
1192 }
1193 else
1194 // Either there was a '+' sign or there was no sign, but
1195 // we were not continuing a minus formula, thus we are parsing
1196 // a true '+' formula.
1197 {
1198 // qDebug() << "Appending found plus formula:"
1199 // << QString("%1%2").arg(symbol).arg(count_as_string);
1200
1201
4/8
✓ Branch 1 taken 513738 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 513738 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 513738 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 513738 times.
✗ Branch 11 not taken.
1027476 plus_formula.append(QString("%1%2").arg(symbol).arg(count_as_string));
1202
1203
2/2
✓ Branch 0 taken 286025 times.
✓ Branch 1 taken 227713 times.
513738 formula_split_result |= SplitResult::HAS_PLUS_COMPONENT;
1204
1205
2/2
✓ Branch 0 taken 286025 times.
✓ Branch 1 taken 227713 times.
513738 if(store)
1206 {
1207 // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
1208 // << count_as_double * static_cast<double>(times);
1209
1210
1/2
✓ Branch 1 taken 286025 times.
✗ Branch 2 not taken.
286025 accountSymbolCountPair(symbol_count_map,
1211 symbol,
1212 count_as_double *
1213 static_cast<double>(times));
1214
1215 // qDebug() << " ...done.";
1216 }
1217
1218 was_minus_formula = false;
1219 }
1220
1/2
✓ Branch 4 taken 522026 times.
✗ Branch 5 not taken.
686610 }
1221
1222 // qDebug() << "Formula" << formula << "splits"
1223 //<< "(+)" << plus_formula << "(-)" << minus_formula;
1224
1225 164572 m_isValid = true;
1226
1227 164572 return formula_split_result;
1228 164578 }
1229
1230 /*!
1231 \brief Tells the "plus" ('+') and "minus" ('-') parts in the member
1232 actionformula.
1233
1234 Parses the m_actionFormula action-formula and separates all the minus
1235 components of that action-formula from all the plus components. The different
1236 components are set to their corresponding formula (m_minusFormula and
1237 m_plusFormula).
1238
1239 This function delegate its work to the other splitActionParts() passing
1240 arguments m_plusFormula, m_minusFormula, m_symbolCountMap, such that this
1241 function modifies the content of this very object.
1242
1243 In all the computations above, reference isotopic data are accessed at \a
1244 isotopic_data_csp. If \a times is not 1, then that value is used to compound the
1245 results of the computations. If \a store is true, then the results of the
1246 computations are stored in this object. If \a reset is true, then the
1247 intermediate computation values and objects are reset.
1248 */
1249 Formula::SplitResult
1250 73969 Formula::splitActionParts(IsotopicDataCstSPtr isotopic_data_csp,
1251 double times,
1252 bool store,
1253 bool reset)
1254 {
1255
2/4
✓ Branch 1 taken 73969 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 73969 times.
✗ Branch 4 not taken.
73969 return splitActionParts(isotopic_data_csp,
1256 73969 m_plusFormula,
1257 73969 m_minusFormula,
1258 73969 m_symbolCountMap,
1259 times,
1260 store,
1261 73969 reset);
1262 }
1263
1264 /*!
1265 \brief Calls actions(const QString &formula) on this Formula's
1266 action-formula m_actionFormula. Returns '+' if it only contains "plus"
1267 elements or '-' if at least one "minus" element was found.
1268
1269 If m_actionFormula contains no sign at all, then it is considered to contain
1270 only '+' elements and the function returns '+'. If at least one element is found
1271 associated to a '-', then the "minus" action prevails and the function returns
1272 '-'.
1273
1274 \sa actions(const QString &formula), splitActionParts()
1275 */
1276 QChar
1277 1 Formula::actions() const
1278 {
1279 1 return actions(m_actionFormula);
1280 }
1281
1282 /*!
1283 \brief Returns '+' if \a formula only contains "plus" elements or '-'
1284 if at least one "minus" element was found.
1285
1286 If \a formula contains no sign at all, then it is considered to contain only
1287 '+' elements and the function returns '+'. If at least one element is found
1288 associated to a '-', then the "minus" action prevails and the function
1289 returns '-'.
1290
1291 \sa actions(), splitActionParts()
1292 */
1293 QChar
1294 1 Formula::actions(const QString &formula)
1295 {
1296 1 double minusCount = formula.count('-', Qt::CaseInsensitive);
1297
1298
1/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 return (minusCount == 0 ? '+' : '-');
1299 }
1300
1301 /*!
1302 \brief Returns true if the member "minus" formula component is not empty,
1303 false otherwise.
1304 */
1305 bool
1306 Formula::hasNetMinusPart()
1307 {
1308 return m_minusFormula.size();
1309 }
1310
1311 /*!
1312 \brief Returns the m_plusFormula formula.
1313 */
1314 QString
1315 10 Formula::getPlusFormula() const
1316 {
1317
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 return m_plusFormula;
1318 }
1319
1320 /* ! No doc
1321 \brief Returns the m_minusFormula formula.
1322 */
1323 QString
1324 10 Formula::getMinusFormula() const
1325 {
1326
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 return m_minusFormula;
1327 }
1328
1329 //////////////// VALIDATIONS /////////////////////
1330 /*!
1331 \brief Returns true if the formula validates successfully, false otherwise.
1332
1333 The validation uses the \a isotopic_data_csp reference data and involves:
1334
1335 \list
1336 \li Checking that the member action-formula is not empty and has a proper
1337 syntax. Returns false otherwise;
1338
1339 \li Splitting the action-formula into a plus_formula and a minus_formula
1340 components using \l{splitActionParts()}). If that step fails, returns false;
1341
1342 \li Verifying that both the plus_formula and the minus_formula are not
1343 empty. Returns false otherwise.
1344 \endlist
1345
1346 If errors are encountered, meaningful messages are stored in \a error_list_p
1347 (which is not cleared).
1348
1349 If the validation is successful, m_isValid is set to true, otherwise it is set
1350 to false.
1351 */
1352 bool
1353 87176 Formula::validate(IsotopicDataCstSPtr isotopic_data_csp,
1354 ErrorList *error_list_p) const
1355 {
1356
1/2
✓ Branch 0 taken 87176 times.
✗ Branch 1 not taken.
87176 qsizetype error_count = error_list_p->size();
1357
1358
2/4
✓ Branch 0 taken 87176 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 87176 times.
87176 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
1359 87176 !isotopic_data_csp->size())
1360 {
1361 qCritical()
1362 << "Cannot validate a Formula if the isotopic data are unavailable.";
1363 error_list_p->push_back(
1364 "Cannot validate a Formula if the isotopic data are unavailable");
1365 }
1366
1367 // qDebug() << "isotopic_data_csp.get():" << isotopic_data_csp.get();
1368
1369
2/2
✓ Branch 0 taken 25972 times.
✓ Branch 1 taken 61204 times.
87176 if(m_actionFormula.isEmpty())
1370 {
1371
1/2
✓ Branch 2 taken 25972 times.
✗ Branch 3 not taken.
25972 qCritical() << "Cannot validate a Formula that is empty.";
1372 51944 error_list_p->push_back("Cannot validate a Formula that is empty");
1373 }
1374
1375
2/2
✓ Branch 1 taken 26009 times.
✓ Branch 2 taken 61167 times.
87176 if(!checkSyntax())
1376 {
1377 52018 qCritical()
1378
1/2
✓ Branch 1 taken 26009 times.
✗ Branch 2 not taken.
26009 << "Cannot validate a Formula that fails the syntax check test.";
1379 52018 error_list_p->push_back(
1380 "Cannot validate a Formula that fails the syntax check test");
1381 }
1382
1383 // qDebug() << "Now splitting formula" << m_actionFormula << "into its action
1384 // parts.";
1385
1386 87176 QString plus_formula;
1387 87176 QString minus_formula;
1388 87176 std::map<QString, double> symbol_count_map;
1389
1390
2/4
✓ Branch 2 taken 87176 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 87176 times.
✗ Branch 5 not taken.
87176 SplitResult result = splitActionParts(isotopic_data_csp,
1391 plus_formula,
1392 minus_formula,
1393 symbol_count_map,
1394 1,
1395 /*store*/ false,
1396 /*reset*/ true);
1397
1398
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 87172 times.
87176 if(result == SplitResult::FAILURE)
1399 {
1400
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 qCritical() << "Failed splitting the Formula.";
1401
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 error_list_p->push_back("Failed splitting the Formula");
1402 }
1403
1404 // Both the action formulas cannot be empty.
1405
4/4
✓ Branch 0 taken 27655 times.
✓ Branch 1 taken 59521 times.
✓ Branch 2 taken 25981 times.
✓ Branch 3 taken 1674 times.
87176 if(!plus_formula.size() && !minus_formula.size())
1406 {
1407
2/4
✓ Branch 1 taken 25981 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 25981 times.
✗ Branch 5 not taken.
25981 qCritical() << "Both the plus and minus formulas are empty.";
1408
1/2
✓ Branch 1 taken 25981 times.
✗ Branch 2 not taken.
51962 error_list_p->push_back("Both the plus and minus formulas are empty");
1409 }
1410
1411 // If we added errors, then that means that the Monomer was not valid.
1412 87176 m_isValid = (error_list_p->size() > error_count ? false : true);
1413
1414 87176 return m_isValid;
1415 87176 }
1416
1417 /*!
1418 \brief Returns true if the formula validates successfully, false otherwise.
1419
1420 See the other validation function for the validation logic.
1421
1422 This function allows to store in member data the results of the validation
1423 process if \a store is set to true. If \a reset is true, the member data are
1424 first cleared.
1425
1426 If the validation is successful, m_isValid is set to true, otherwise it is set
1427 to false.
1428 */
1429 bool
1430 3433 Formula::validate(IsotopicDataCstSPtr isotopic_data_csp,
1431 bool store,
1432 bool reset,
1433 ErrorList *error_list_p)
1434 {
1435
1/2
✓ Branch 0 taken 3433 times.
✗ Branch 1 not taken.
3433 qsizetype error_count = error_list_p->size();
1436
1437
2/4
✓ Branch 0 taken 3433 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3433 times.
3433 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
1438 3433 !isotopic_data_csp->size())
1439 {
1440 qCritical()
1441 << "Cannot validate a Formula if the isotopic data are unavailable.";
1442 error_list_p->push_back(
1443 "Cannot validate a Formula if the isotopic data are unavailable");
1444 }
1445
1446 // qDebug() << "isotopic_data_csp.get():" << isotopic_data_csp.get();
1447
1448
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3433 times.
3433 if(m_actionFormula.isEmpty())
1449 {
1450 qCritical() << "Cannot validate a Formula that is empty.";
1451 error_list_p->push_back("Cannot validate a Formula that is empty");
1452 }
1453
1454
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3430 times.
3433 if(!checkSyntax())
1455 {
1456 6 qCritical()
1457
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 << "Cannot validate a Formula that fails the syntax check test.";
1458 6 error_list_p->push_back(
1459 "Cannot validate a Formula that fails the syntax check test");
1460 }
1461
1462 // qDebug() << "Now splitting formula" << m_actionFormula << "into its action
1463 // parts.";
1464
1465
2/4
✓ Branch 1 taken 3433 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3433 times.
✗ Branch 4 not taken.
3433 SplitResult result = splitActionParts(isotopic_data_csp,
1466 3433 m_plusFormula,
1467 3433 m_minusFormula,
1468 3433 m_symbolCountMap,
1469 1,
1470 /*store*/ store,
1471 /*reset*/ reset);
1472
1473
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3433 times.
3433 if(result == SplitResult::FAILURE)
1474 {
1475 qCritical() << "Failed splitting the Formula.";
1476 error_list_p->push_back("Failed splitting the Formula");
1477 }
1478
1479 // Both the action formulas cannot be empty.
1480
4/4
✓ Branch 0 taken 244 times.
✓ Branch 1 taken 3189 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 243 times.
3433 if(!m_plusFormula.size() && !m_minusFormula.size())
1481 {
1482
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 qCritical() << "Both the plus and minus formulas are empty.";
1483 2 error_list_p->push_back("Both the plus and minus formulas are empty");
1484 }
1485
1486 // If we added errors, then that means that the Monomer was not valid.
1487 3433 m_isValid = (error_list_p->size() > error_count ? false : true);
1488
1489 3433 return m_isValid;
1490 }
1491
1492 /*!
1493 \brief Returns the status of the formula, that is, the result of validate().
1494 */
1495 bool
1496 71 Formula::isValid() const
1497 {
1498 71 return m_isValid;
1499 }
1500
1501 //////////////// THE SYMBOLS-COUNT OPERATIONS /////////////////////
1502
1503 /*!
1504 \brief Returns a const reference to the std::map<QString, double> container
1505 that relates chemical symbols with corresponding counts.
1506 */
1507 const std::map<QString, double> &
1508 29 Formula::getSymbolCountMapCstRef() const
1509 {
1510 29 return m_symbolCountMap;
1511 }
1512
1513 /*!
1514 \brief Returns the count value associated with key \a symbol in the symbol /
1515 count member map m_symbolCountMap.
1516 */
1517 double
1518 Formula::symbolCount(const QString &symbol) const
1519 {
1520 // Return the symbol index.
1521
1522 std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();
1523
1524 std::map<QString, double>::const_iterator iter =
1525 m_symbolCountMap.find(symbol);
1526
1527 if(iter == iter_end)
1528 return 0;
1529
1530 return iter->second;
1531 }
1532
1533 /*!
1534 \brief Accounts this Formula's action-formula (m_actionFormula) in the symbol /
1535 count member container (m_symbolCountMap).
1536
1537 Calls splitActionParts() to actually parse m_actionFormula and account its
1538 components to m_symbolCountMap. The accounting of the symbol / count can be
1539 compounded by the \a times factor.
1540
1541 While splitting the "plus" and "minus" components of the action-formula, their
1542 validity is checked against the reference isotopic data \a isotopic_data_csp.
1543
1544 This function is used when processively accounting many different formulas
1545 into the symbol / count map. The formula is set to a new value and this
1546 function is called without resetting the symbol / count map, effectively adding
1547 formulas onto formulas sequentially.
1548
1549 Returns true if no error was encountered, false otherwise.
1550
1551 \sa splitActionParts(), Polymer::elementalComposition
1552 */
1553 bool
1554 8233 Formula::accountSymbolCounts(IsotopicDataCstSPtr isotopic_data_csp, int times)
1555 {
1556 // GCOV_EXCL_START
1557 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
1558 !isotopic_data_csp->size())
1559 qFatal("Programming error. The isotopic data pointer cannot be nullptr.");
1560 // GCOV_EXCL_STOP
1561
1562 // Note the 'times' param below.
1563
2/4
✓ Branch 2 taken 8233 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8233 times.
16466 if(splitActionParts(
1564
1/2
✓ Branch 0 taken 8233 times.
✗ Branch 1 not taken.
8233 isotopic_data_csp, times, /*store*/ true, /*reset*/ false) ==
1565 SplitResult::FAILURE)
1566 {
1567 m_isValid = false;
1568 return false;
1569 }
1570
1571 return true;
1572 }
1573
1574 /*!
1575 \brief Accounts for \a symbol and corresponding \a count in the \a
1576 symbol_count_map map.
1577
1578 The symbol_count_map relates each atom (chemical element) symbol with its
1579 occurrence count as encountered while parsing the member action-formula.
1580
1581 If the symbol was not encountered yet, a new key/value pair is created.
1582 Otherwise, the count value is updated.
1583
1584 Returns the new count status for \a symbol.
1585 */
1586 double
1587 297459 Formula::accountSymbolCountPair(std::map<QString, double> &symbol_count_map,
1588 const QString &symbol,
1589 double count) const
1590 {
1591 // We receive a symbol and we need to account for it count count in the member
1592 // symbol count map (count might be < 0).
1593
1594 // Try to insert the new symbol/count pairinto the map. Check if that was done
1595 // or not. If the result.second is false, then that means the the insert
1596 // function did not perform because a pair by that symbol existed already. In
1597 // that case we just need to increment the count for the the pair.
1598
1599 // qDebug() << "Accounting symbol:" << symbol << "for count:" << count;
1600
1601 297459 double new_count = 0;
1602
1603
1/2
✓ Branch 0 taken 297459 times.
✗ Branch 1 not taken.
297459 std::pair<std::map<QString, double>::iterator, bool> res =
1604
1/2
✓ Branch 1 taken 297459 times.
✗ Branch 2 not taken.
297459 symbol_count_map.insert(std::pair<QString, double>(symbol, count));
1605
1606
2/2
✓ Branch 0 taken 33828 times.
✓ Branch 1 taken 263631 times.
297459 if(!res.second)
1607 {
1608 // qDebug() << "The symbol was already in the symbol/count map. with
1609 // count:"
1610 // << res.first->second;
1611
1612 // One pair by that symbol key existed already, just update the count and
1613 // store that value for reporting.
1614 33828 res.first->second += count;
1615 33828 new_count = res.first->second;
1616 // new_count might be <= 0.
1617
1618 // qDebug() << "For symbol" << symbol << "the new count:" << new_count;
1619 }
1620 else
1621 {
1622 // qDebug() << "Symbol" << symbol
1623 // << "was not there already, setting the count to:" << count;
1624
1625 // We just effectively added during the insert call above a new pair to
1626 // the map by key symbol with value count.
1627 new_count = count;
1628 }
1629
1630 // We should check if the symbol has now a count of 0. In that case, we remove
1631 // the symbol altogether because we do not want to list naught symbols in the
1632 // final formula.
1633
1634
2/2
✓ Branch 0 taken 212 times.
✓ Branch 1 taken 297247 times.
297459 if(!new_count)
1635 {
1636 // qDebug() << "For symbol" << symbol
1637 //<< "the new count is 0. Thus we erase the map item altogether.";
1638
1639 212 symbol_count_map.erase(symbol);
1640 }
1641
1642 // Update what's the text of the formula to represent what is in
1643 // atomCount list.
1644
1645 // qDebug() << "The formula now can be reduced to:" << elementalComposition();
1646
1647 297459 return new_count;
1648 }
1649
1650 /*!
1651 \brief Accounts for \a symbol and corresponding \a count in the member map.
1652
1653 The m_symbolCountMap relates each atom (chemical element) symbol with its
1654 occurrence count as encountered while parsing the member action-formula.
1655
1656 If the symbol was not encountered yet, a new key/value pair is created.
1657 Otherwise, the count value is updated.
1658
1659 Returns the new count status for \a symbol.
1660 */
1661 double
1662 7075 Formula::accountSymbolCountPair(const QString &symbol, double count)
1663 {
1664 7075 return accountSymbolCountPair(m_symbolCountMap, symbol, count);
1665 }
1666
1667 /*!
1668 \brief Accounts into this Formula the \a formula_string action-formula using
1669 \a isotopic_data_csp as reference data using \a times as a compounding factor.
1670 The result of the operation is set to \a ok.
1671
1672 The \a formula_string formula is converted into a temporary Formula and
1673 processed:
1674
1675 \list
1676 \li First validate() is called on the temporary formula, with storage of the
1677 symbol/count data;
1678 \li The symbol/count data thus generated is used to update the member map.
1679 \li The m_actionFormula is set to the result of elementalComposition().
1680 \endlist
1681
1682 Returns the size of the member symbol/count m_symbolCountMap.
1683 */
1684 std::size_t
1685 3115 Formula::accountFormula(const QString &formula_string,
1686 IsotopicDataCstSPtr isotopic_data_csp,
1687 double times,
1688 bool &ok)
1689 {
1690 3115 qDebug() << "Accounting in this formula:" << m_actionFormula
1691 << "the external formula:" << formula_string;
1692
1693 // qDebug() << "Before having merged the external formula's map into this one,
1694 // " "this one has size:"
1695 //<< m_symbolCountMap.size();
1696
1697 // We get a formula as an elemental composition text string and we want to
1698 // account for that formula in *this formula.
1699
1700 // First off, validate the formula text, and take advantage to store
1701 // the resulting symbol/count pairs we'll use later to update *this Formula.
1702
1703 3115 Formula temp_formula(formula_string);
1704 3115 ErrorList error_list;
1705
4/6
✓ Branch 2 taken 3115 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3115 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3113 times.
6230 if(!temp_formula.validate(isotopic_data_csp,
1706 /*store*/ true,
1707 /*reset*/ true,
1708 &error_list))
1709 {
1710
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
2 qCritical() << "Formula:" << formula_string
1711
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 << "failed to validate with errors:"
1712
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
4 << Utils::joinErrorList(error_list, ", ");
1713 2 ok = false;
1714 2 return 0;
1715 }
1716
1717 // Now, for each item in the formula's symbol/count map, aggregate the found
1718 // data to *this symbol/count map. We'll have "merged" or "aggreated" the
1719 // other formula into *this one.
1720
1721 3113 std::map<QString, double>::const_iterator the_iterator_cst =
1722 3113 temp_formula.m_symbolCountMap.cbegin();
1723 3113 std::map<QString, double>::const_iterator the_end_iterator_cst =
1724 3113 temp_formula.m_symbolCountMap.cend();
1725
1726
2/2
✓ Branch 0 taken 7075 times.
✓ Branch 1 taken 3113 times.
10188 while(the_iterator_cst != the_end_iterator_cst)
1727 {
1728 // qDebug() << "Iterating in symbol/count pair:" << iter->first << "-" <<
1729 // iter->second;
1730
1731
1/2
✓ Branch 1 taken 7075 times.
✗ Branch 2 not taken.
7075 accountSymbolCountPair((*the_iterator_cst).first,
1732
1/2
✓ Branch 1 taken 7075 times.
✗ Branch 2 not taken.
7075 (*the_iterator_cst).second * times);
1733 7075 ++the_iterator_cst;
1734 }
1735
1736 // qDebug() << "After having merged the external formula's map into this one,
1737 // " "this one has size:"
1738 //<< m_symbolCountMap.size();
1739
1740 // Update what's the text of the action-formula to represent what is in
1741 // atomCount list.
1742
1/2
✓ Branch 1 taken 3113 times.
✗ Branch 2 not taken.
3113 m_actionFormula = elementalComposition();
1743
1/2
✓ Branch 1 taken 3113 times.
✗ Branch 2 not taken.
3113 m_title = "Has changed";
1744
1745 3113 qDebug() << "And now this formula has text: " << m_actionFormula;
1746
1747 3113 ok = true;
1748 3113 return m_symbolCountMap.size();
1749 3115 }
1750
1751 //////////////// ELEMENTAL COMPOSITION /////////////////////
1752 /*!
1753 \brief Returns a formula matching the contents of the memeber symbol / count
1754 map.
1755
1756 The returned formula is formatted according to the IUPAC convention about the
1757 ordering of the chemical elements: CxxHxxNxxOxxSxxPxx.
1758
1759 The "plus" components are output first and the "minus" components after.
1760
1761 If \a symbol_count_pairs_p is not nullptr, each symbol / count pair is added
1762 to it.
1763 */
1764 QString
1765 3548 Formula::elementalComposition(
1766 std::vector<std::pair<QString, double>> *symbol_count_pairs_p) const
1767 {
1768 // Iterate in the symbol count member map and for each item output the symbol
1769 // string accompanied by the corresponding count. Note that the count for any
1770 // given symbol might be negative. We want to craft an elemental composition
1771 // that accounts for "actions", that is a +elemental formula and a -elemental
1772 // formula.
1773
1774 3548 std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
1775 3548 std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();
1776
1777 #if 0
1778
1779 qDebug() << "While computing the elemental composition corresponding to the "
1780 "symbol/count map:";
1781 for(auto pair : m_symbolCountMap)
1782 qDebug().noquote() << "(" << pair.first << "," << pair.second << ")";
1783
1784 #endif
1785
1786 3548 QStringList negativeStringList;
1787 3548 QStringList positiveStringList;
1788
1789
2/2
✓ Branch 0 taken 16291 times.
✓ Branch 1 taken 3548 times.
19839 while(iter != iter_end)
1790 {
1791
1/2
✓ Branch 0 taken 16291 times.
✗ Branch 1 not taken.
16291 QString symbol = iter->first;
1792
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16291 times.
16291 double count = iter->second;
1793
1794
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16291 times.
16291 if(count < 0)
1795 {
1796 negativeStringList.append(
1797 QString("%1%2").arg(symbol).arg(-1 * count));
1798 }
1799 else
1800 {
1801
3/6
✓ Branch 1 taken 16291 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16291 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 16291 times.
✗ Branch 8 not taken.
32582 positiveStringList.append(QString("%1%2").arg(symbol).arg(count));
1802 }
1803
1804 16291 ++iter;
1805 16291 }
1806
1807 // We want to provide a formula that lists the positive component
1808 // first and the negative component last.
1809
1810 // Each positive/negative component will list the atoms in the
1811 // conventional order : CxxHxxNxxOxx and all the rest in
1812 // alphabetical order.
1813
1814 // We want to provide for each positive and negative components of the
1815 // initial formula object, an elemental formula that complies with the
1816 // convention : first the C atom, next the H, N, O, S, P atoms and all the
1817 // subsequent ones in alphabetical order.
1818
1819 // Sort the lists.
1820
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 negativeStringList.sort();
1821
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 positiveStringList.sort();
1822
1823 // Thus we look for the four C, H, N, O, S,P atoms, and we create the
1824 // initial part of the elemental formula. Each time we find one
1825 // such atom we remove it from the list, so that we can later just
1826 // append all the remaining atoms, since we have sorted the lists
1827 // above.
1828
1829 // The positive component
1830 // ======================
1831
1832 3548 int symbol_index_in_list = 0;
1833 3548 QString positiveComponentString;
1834
1835 // Carbon
1836
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1837
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*"));
1838
2/2
✓ Branch 0 taken 3546 times.
✓ Branch 1 taken 2 times.
3548 if(symbol_index_in_list != -1)
1839 {
1840
1/2
✓ Branch 1 taken 3546 times.
✗ Branch 2 not taken.
3546 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1841
1/2
✓ Branch 1 taken 3546 times.
✗ Branch 2 not taken.
3546 positiveStringList.removeAt(symbol_index_in_list);
1842
1843
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3542 times.
3546 if(symbol_count_pairs_p)
1844 4 symbol_count_pairs_p->push_back(
1845
4/8
✓ 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.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
8 std::pair<QString, double>("C", m_symbolCountMap.at("C")));
1846 }
1847
1848 // Hydrogen
1849
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1850
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*"));
1851
2/2
✓ Branch 0 taken 3546 times.
✓ Branch 1 taken 2 times.
3548 if(symbol_index_in_list != -1)
1852 {
1853
1/2
✓ Branch 1 taken 3546 times.
✗ Branch 2 not taken.
3546 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1854
1/2
✓ Branch 1 taken 3546 times.
✗ Branch 2 not taken.
3546 positiveStringList.removeAt(symbol_index_in_list);
1855
1856
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3542 times.
3546 if(symbol_count_pairs_p)
1857 4 symbol_count_pairs_p->push_back(
1858
4/8
✓ 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.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
8 std::pair<QString, double>("H", m_symbolCountMap.at("H")));
1859 }
1860
1861 // Nitrogen
1862
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1863
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*"));
1864
2/2
✓ Branch 0 taken 3544 times.
✓ Branch 1 taken 4 times.
3548 if(symbol_index_in_list != -1)
1865 {
1866
1/2
✓ Branch 1 taken 3544 times.
✗ Branch 2 not taken.
3544 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1867
1/2
✓ Branch 1 taken 3544 times.
✗ Branch 2 not taken.
3544 positiveStringList.removeAt(symbol_index_in_list);
1868
1869
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3540 times.
3544 if(symbol_count_pairs_p)
1870 4 symbol_count_pairs_p->push_back(
1871
4/8
✓ 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.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
8 std::pair<QString, double>("N", m_symbolCountMap.at("N")));
1872 }
1873
1874 // Oxygen
1875
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1876
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*"));
1877
2/2
✓ Branch 0 taken 3430 times.
✓ Branch 1 taken 118 times.
3548 if(symbol_index_in_list != -1)
1878 {
1879
1/2
✓ Branch 1 taken 3430 times.
✗ Branch 2 not taken.
3430 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1880
1/2
✓ Branch 1 taken 3430 times.
✗ Branch 2 not taken.
3430 positiveStringList.removeAt(symbol_index_in_list);
1881
1882
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3426 times.
3430 if(symbol_count_pairs_p)
1883 4 symbol_count_pairs_p->push_back(
1884
4/8
✓ 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.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
8 std::pair<QString, double>("O", m_symbolCountMap.at("O")));
1885 }
1886
1887 // Sulfur
1888
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1889
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*"));
1890
2/2
✓ Branch 0 taken 1847 times.
✓ Branch 1 taken 1701 times.
3548 if(symbol_index_in_list != -1)
1891 {
1892
1/2
✓ Branch 1 taken 1847 times.
✗ Branch 2 not taken.
1847 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1893
1/2
✓ Branch 1 taken 1847 times.
✗ Branch 2 not taken.
1847 positiveStringList.removeAt(symbol_index_in_list);
1894
1895
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1843 times.
1847 if(symbol_count_pairs_p)
1896 4 symbol_count_pairs_p->push_back(
1897
4/8
✓ 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.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
8 std::pair<QString, double>("S", m_symbolCountMap.at("S")));
1898 }
1899
1900 // Phosphorus
1901
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1902
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 positiveStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*"));
1903
2/2
✓ Branch 0 taken 378 times.
✓ Branch 1 taken 3170 times.
3548 if(symbol_index_in_list != -1)
1904 {
1905
1/2
✓ Branch 1 taken 378 times.
✗ Branch 2 not taken.
378 positiveComponentString += positiveStringList.at(symbol_index_in_list);
1906
1/2
✓ Branch 1 taken 378 times.
✗ Branch 2 not taken.
378 positiveStringList.removeAt(symbol_index_in_list);
1907
1908
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 378 times.
378 if(symbol_count_pairs_p)
1909 symbol_count_pairs_p->push_back(
1910 std::pair<QString, double>("P", m_symbolCountMap.at("P")));
1911 }
1912
1913 // Go on with all the other ones, if any...
1914
1915
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 for(int iter = 0; iter < positiveStringList.size(); ++iter)
1916 {
1917 positiveComponentString += positiveStringList.at(iter);
1918
1919 QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)");
1920 QRegularExpressionMatch match = regexp.match(positiveStringList.at(iter));
1921
1922 if(match.hasMatch())
1923 {
1924 QString symbol = match.captured(1);
1925 QString howMany = match.captured(2);
1926
1927 bool ok = false;
1928 double count = howMany.toDouble(&ok);
1929
1930 if(!count && !ok)
1931 qFatal(
1932 "Fatal error at %s@%d -- %s(). "
1933 "Failed to parse an atom count."
1934 "Program aborted.",
1935 __FILE__,
1936 __LINE__,
1937 __FUNCTION__);
1938
1939 if(symbol_count_pairs_p)
1940 symbol_count_pairs_p->push_back(
1941 std::pair<QString, double>(symbol, count));
1942 }
1943 }
1944
1945 // qDebug() << __FILE__ << __LINE__
1946 //<< "positiveComponentString:" << positiveComponentString;
1947
1948
1949 // The negative component
1950 // ======================
1951
1952 3548 QString negativeComponentString;
1953
1954 // Carbon
1955
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1956
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*"));
1957
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
1958 {
1959 negativeComponentString += negativeStringList.at(symbol_index_in_list);
1960 negativeStringList.removeAt(symbol_index_in_list);
1961
1962 if(symbol_count_pairs_p)
1963 symbol_count_pairs_p->push_back(
1964 std::pair<QString, double>("C", m_symbolCountMap.at("C")));
1965 }
1966
1967 // Hydrogen
1968
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1969
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*"));
1970
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
1971 {
1972 negativeComponentString += negativeStringList.at(symbol_index_in_list);
1973 negativeStringList.removeAt(symbol_index_in_list);
1974
1975 if(symbol_count_pairs_p)
1976 symbol_count_pairs_p->push_back(
1977 std::pair<QString, double>("H", m_symbolCountMap.at("H")));
1978 }
1979
1980 // Nitrogen
1981
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1982
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*"));
1983
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
1984 {
1985 negativeComponentString += negativeStringList.at(symbol_index_in_list);
1986 negativeStringList.removeAt(symbol_index_in_list);
1987
1988 if(symbol_count_pairs_p)
1989 symbol_count_pairs_p->push_back(
1990 std::pair<QString, double>("N", m_symbolCountMap.at("N")));
1991 }
1992
1993 // Oxygen
1994
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
1995
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*"));
1996
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
1997 {
1998 negativeComponentString += negativeStringList.at(symbol_index_in_list);
1999 negativeStringList.removeAt(symbol_index_in_list);
2000
2001 if(symbol_count_pairs_p)
2002 symbol_count_pairs_p->push_back(
2003 std::pair<QString, double>("O", m_symbolCountMap.at("O")));
2004 }
2005
2006 // Sulfur
2007
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
2008
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*"));
2009
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
2010 {
2011 negativeComponentString += negativeStringList.at(symbol_index_in_list);
2012 negativeStringList.removeAt(symbol_index_in_list);
2013
2014 if(symbol_count_pairs_p)
2015 symbol_count_pairs_p->push_back(
2016 std::pair<QString, double>("S", m_symbolCountMap.at("S")));
2017 }
2018
2019 // Phosphorus
2020
1/2
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
3548 symbol_index_in_list =
2021
2/4
✓ Branch 1 taken 3548 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3548 times.
✗ Branch 5 not taken.
7096 negativeStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*"));
2022
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(symbol_index_in_list != -1)
2023 {
2024 negativeComponentString += negativeStringList.at(symbol_index_in_list);
2025 negativeStringList.removeAt(symbol_index_in_list);
2026
2027 if(symbol_count_pairs_p)
2028 symbol_count_pairs_p->push_back(
2029 std::pair<QString, double>("P", m_symbolCountMap.at("P")));
2030 }
2031
2032 // Go on with all the other ones, if any...
2033
2034
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 for(int iter = 0; iter < negativeStringList.size(); ++iter)
2035 {
2036 negativeComponentString += negativeStringList.at(iter);
2037
2038 QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)");
2039 QRegularExpressionMatch match = regexp.match(negativeStringList.at(iter));
2040
2041 if(match.hasMatch())
2042 {
2043 QString symbol = match.captured(1);
2044 QString howMany = match.captured(2);
2045
2046 bool ok = false;
2047 double count = howMany.toInt(&ok, 10);
2048
2049 if(!count && !ok)
2050 qFatal(
2051 "Fatal error at %s@%d -- %s(). "
2052 "Failed to parse an atom count."
2053 "Program aborted.",
2054 __FILE__,
2055 __LINE__,
2056 __FUNCTION__);
2057
2058 if(symbol_count_pairs_p)
2059 symbol_count_pairs_p->push_back(
2060 std::pair<QString, double>(symbol, count));
2061 }
2062 }
2063
2064
2065 // qDebug() << __FILE__ << __LINE__
2066 //<< "negativeComponentString:" << negativeComponentString;
2067
2068 // Create the final elemental formula that comprises both the
2069 // positive and negative element. First the positive element and
2070 // then the negative one. Only append the negative one, prepended
2071 // with '-' if the string is non-empty.
2072
2073
2/2
✓ Branch 0 taken 3546 times.
✓ Branch 1 taken 2 times.
3548 QString elementalComposition = positiveComponentString;
2074
2075
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3548 times.
3548 if(!negativeComponentString.isEmpty())
2076 elementalComposition += QString("-%1").arg(negativeComponentString);
2077
2078 // qDebug() << __FILE__ << __LINE__
2079 // <<"elementalComposition:" << elementalComposition;
2080
2081 3548 return elementalComposition;
2082 3548 }
2083
2084 //////////////// MASS OPERATIONS /////////////////////
2085
2086 /*!
2087 \brief Accounts this formula's monoisotopic and average masses into \a mono
2088 and \a avg, using \a times as a compounding factor.
2089
2090 The masses corresponding to the member action-formula are
2091 calculated first and then the \a mono and \a avg parameters are updated
2092 by incrementing their value with the calculated values. This incrementation
2093 might be compounded by that \a times factor.
2094
2095 The masses of the member action-formula are computed using data from \a
2096 isotopic_data_csp.
2097
2098 Sets \a ok to false if the calculation failed, to true otherwise.
2099
2100 Returns this object.
2101
2102 \sa splitActionParts()
2103 */
2104 Formula &
2105 91665 Formula::accountMasses(bool &ok,
2106 IsotopicDataCstSPtr isotopic_data_csp,
2107 double &mono,
2108 double &avg,
2109 double times)
2110 {
2111 // GCOV_EXCL_START
2112 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
2113 !isotopic_data_csp->size())
2114 qFatal("Programming error. The isotopic data pointer cannot be nullptr.");
2115 // GCOV_EXCL_STOP
2116
2117 // Note the 'times' param below that ensures we create proper symbol/count
2118 // map items by taking that compounding factor into account.
2119
2120 // qDebug() << qSetRealNumberPrecision(6)
2121 // << "We get two mono and avg variables with values:" << mono <<
2122 // "-"
2123 // << avg << "and times:" << times;
2124
2125
2/2
✓ Branch 1 taken 25929 times.
✓ Branch 2 taken 65736 times.
91665 if(!checkSyntax())
2126 {
2127 25929 qDebug() << "The checkSyntax test failed for " << m_actionFormula;
2128
2129 25929 m_isValid = false;
2130 25929 ok = false;
2131 25929 return *this;
2132 }
2133
2134
3/4
✓ Branch 2 taken 65736 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 65734 times.
131472 if(splitActionParts(
2135
1/2
✓ Branch 0 taken 65736 times.
✗ Branch 1 not taken.
65736 isotopic_data_csp, times, true /* store */, true /* reset */) ==
2136 SplitResult::FAILURE)
2137 {
2138 2 qDebug() << "The formula splitting into actions failed.";
2139 2 m_isValid = false;
2140 2 ok = false;
2141 2 return *this;
2142 }
2143
2144 // qDebug() << "after splitActionParts:"
2145 // << "store: true ; reset: true"
2146 // << "m_actionFormula:" << m_actionFormula << "text" << toString();
2147
2148 // At this point m_symbolCountMap has all the symbol/count pairs needed to
2149 // account for the masses.
2150
2151 65734 std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
2152 65734 std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();
2153
2154 // for(auto item : m_symbolCountMap)
2155 // qDebug() << "One symbol count item:" << item.first << "/" << item.second;
2156
2157 65734 bool res = false;
2158
2159
2/2
✓ Branch 0 taken 249877 times.
✓ Branch 1 taken 65734 times.
315611 while(iter != iter_end)
2160 {
2161
1/2
✓ Branch 0 taken 249877 times.
✗ Branch 1 not taken.
249877 QString symbol = iter->first;
2162
2163 // qDebug() << "Getting masses for symbol:" << symbol;
2164
2165
1/2
✓ Branch 1 taken 249877 times.
✗ Branch 2 not taken.
249877 double mono_mass =
2166
1/2
✓ Branch 1 taken 249877 times.
✗ Branch 2 not taken.
249877 isotopic_data_csp->getMonoMassBySymbol(iter->first, res);
2167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249877 times.
249877 if(!res)
2168 {
2169 qWarning() << "Failed to get the mono mass.";
2170 ok = false;
2171 return *this;
2172 }
2173
1/2
✓ Branch 1 taken 249877 times.
✗ Branch 2 not taken.
249877 mono += mono_mass * iter->second;
2174
2175 249877 ok = false;
2176
2177
1/2
✓ Branch 1 taken 249877 times.
✗ Branch 2 not taken.
249877 double avg_mass = isotopic_data_csp->getAvgMassBySymbol(iter->first, res);
2178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249877 times.
249877 if(!res)
2179 {
2180 qWarning() << "Failed to get the avg mass.";
2181 ok = false;
2182 return *this;
2183 }
2184 249877 avg += avg_mass * iter->second;
2185
2186
2187 249877 ++iter;
2188 249877 }
2189
2190 65734 ok = true;
2191 65734 m_isValid = true;
2192
2193 65734 return *this;
2194 }
2195
2196 /*!
2197 \brief Accounts the \a formula monoisotopic and average masses into \a mono
2198 and \a avg, using \a times as a compounding factor.
2199
2200 The masses corresponding to the \a formula are calculated first and then the \a
2201 mono and \a avg parameters are updated by incrementing their value with the
2202 calculated values. This incrementation might be compounded by that \a times
2203 factor.
2204
2205 The masses of the \a formula are computed using data from \a isotopic_data_csp.
2206
2207 Sets ok to false if the calculation failed, to true otherwise.
2208
2209 Returns this object.
2210
2211 \sa splitActionParts()
2212 */
2213 Formula &
2214 Formula::accountMasses(Formula &formula,
2215 bool &ok,
2216 IsotopicDataCstSPtr isotopic_data_csp,
2217 double &mono,
2218 double &avg,
2219 double times)
2220 {
2221 // GCOV_EXCL_START
2222 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr ||
2223 !isotopic_data_csp->size())
2224 qFatal("Programming error. The isotopic data pointer cannot be nullptr.");
2225 // GCOV_EXCL_STOP
2226
2227 // Note the 'times' param below that ensures we create proper symbol/count
2228 // map items by taking that compounding factor into account.
2229
2230 // qDebug() << qSetRealNumberPrecision(6)
2231 // << "We get two mono and avg variables with values:" << mono <<
2232 // "-"
2233 // << avg << "and times:" << times;
2234
2235 if(!formula.checkSyntax())
2236 {
2237 qDebug() << "The checkSyntax test failed for " << formula.m_actionFormula;
2238
2239 formula.m_isValid = false;
2240 ok = false;
2241 return formula;
2242 }
2243
2244 if(formula.splitActionParts(isotopic_data_csp,
2245 formula.m_plusFormula,
2246 formula.m_minusFormula,
2247 formula.m_symbolCountMap,
2248 times,
2249 true /* store */,
2250 true /* reset */) == SplitResult::FAILURE)
2251 {
2252 formula.m_isValid = false;
2253 ok = false;
2254 return formula;
2255 }
2256
2257 // qDebug() << "after splitActionParts:"
2258 // << "store: true ; reset: true"
2259 // << "m_actionFormula:" << m_actionFormula << "text" << toString();
2260
2261 // At this point m_symbolCountMap has all the symbol/count pairs needed to
2262 // account for the masses.
2263
2264 std::map<QString, double>::const_iterator iter =
2265 formula.m_symbolCountMap.cbegin();
2266 std::map<QString, double>::const_iterator iter_end =
2267 formula.m_symbolCountMap.cend();
2268
2269 // for(auto item : m_symbolCountMap)
2270 // qDebug() << "One symbol count item:" << item.first << "/" << item.second;
2271
2272 bool res = false;
2273
2274 while(iter != iter_end)
2275 {
2276 QString symbol = iter->first;
2277
2278 // qDebug() << "Getting masses for symbol:" << symbol;
2279
2280 double mono_mass =
2281 isotopic_data_csp->getMonoMassBySymbol(iter->first, res);
2282 if(!res)
2283 {
2284 qWarning() << "Failed to get the mono mass.";
2285 ok = false;
2286 return formula;
2287 }
2288 mono += mono_mass * iter->second;
2289
2290 ok = false;
2291
2292 double avg_mass = isotopic_data_csp->getAvgMassBySymbol(iter->first, res);
2293 if(!res)
2294 {
2295 qWarning() << "Failed to get the avg mass.";
2296 ok = false;
2297 return formula;
2298 }
2299 avg += avg_mass * iter->second;
2300
2301
2302 ++iter;
2303 }
2304
2305 ok = true;
2306 formula.m_isValid = true;
2307
2308 return formula;
2309 }
2310
2311 //////////////// XML DATA LOADING WRITING /////////////////////
2312 /*!
2313 \brief Parses a formula XML \a element according to \a version and sets the data
2314 to the member action-formula checking it syntax.
2315
2316 Returns true if parsing and syntax checking were successful, false
2317 otherwise.
2318
2319 \sa checkSyntax()
2320 */
2321 bool
2322 3915 Formula::renderXmlFormulaElement(const QDomElement &element,
2323 [[maybe_unused]] int version)
2324 {
2325
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 3915 times.
7830 if(element.tagName() != "formula")
2326 {
2327 qCritical() << "The element tag is not 'formula'";
2328 return false;
2329 }
2330
2331 // Will take care of removing the title and setting it to m_title.
2332
1/2
✓ Branch 2 taken 3915 times.
✗ Branch 3 not taken.
3915 setActionFormula(element.text());
2333
2334 // qDebug() << "Now set the action-formula to " << m_actionFormula;
2335
2336 // Do not forget that we might have a title associated with the
2337 // formula and spaces. checkSyntax() should care of removing these
2338 // title and spaces before checking for chemical syntax
2339 // correctness.
2340
2341 // Remember, syntax checking does not mean that the chemical
2342 // validity is assessed. For example "+HWKPTR" would pass
2343 // the checkSyntax() test.
2344
2345
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3913 times.
3915 if(!checkSyntax())
2346 {
2347
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 qCritical() << "Failed to check syntax for formula:" << m_actionFormula;
2348 2 return false;
2349 }
2350
2351 return true;
2352 }
2353
2354 /*!
2355 \brief Returns a string containing a formula XML element
2356 documenting this Formula instance.
2357
2358 \a offset and \a indent define the formatting of the XML element.
2359 */
2360 QString
2361 1 Formula::formatXmlFormulaElement(int offset, const QString &indent)
2362 {
2363
2364 1 int newOffset;
2365 1 int iter = 0;
2366
2367 1 QString lead("");
2368 1 QString string;
2369
2370 // Prepare the lead.
2371 1 newOffset = offset;
2372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 while(iter < newOffset)
2373 {
2374 lead += indent;
2375 ++iter;
2376 }
2377
2378
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 string += QString("%1<formula>%2</formula>\n")
2379
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 .arg(lead)
2380
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
2 .arg(getActionFormula(/*with title*/ true));
2381
2382 1 return string;
2383 1 }
2384
2385 //////////////// UTILS /////////////////////
2386
2387 /*!
2388 \brief Removes \e{all} the space characters from the member action-formula.
2389
2390 Spaces can be placed anywhere in formula for more readability. However, it
2391 might be required that these character spaces be removed. This function does
2392 just this, using a QRegularExpression.
2393
2394 Returns the number of removed characters.
2395 */
2396 int
2397 205175 Formula::removeSpaces()
2398 {
2399 205175 int length = m_actionFormula.length();
2400
2401 // We want to remove all the possibly-existing spaces.
2402
2403
2/4
✓ Branch 2 taken 205175 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 205175 times.
✗ Branch 6 not taken.
205175 m_actionFormula.remove(QRegularExpression("\\s+"));
2404
2405 // Return the number of removed characters.
2406 205175 return (length - m_actionFormula.length());
2407 }
2408
2409 /*!
2410 \brief Returns the total count of symbols (atoms) in this formula.
2411
2412 The determination is performed by summing up all the count values for all the
2413 symbols in the member symbol / count pairs in the member map m_symbolCountMap.
2414 */
2415 double
2416 Formula::totalAtoms() const
2417 {
2418 double total_atom_count = 0;
2419
2420 std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
2421 std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();
2422
2423 while(iter != iter_end)
2424 {
2425 total_atom_count += iter->second;
2426 ++iter;
2427 }
2428
2429 return total_atom_count;
2430 }
2431
2432 /*!
2433 \brief Returns the total count of isotopes in this formula using \a
2434 isotopic_data_csp as the reference isotopic data.
2435
2436 The determination is performed by summing up all the isotope counts for
2437 all the symbols keys in the member symbol / count map m_symbolCountMap.
2438 */
2439 double
2440 Formula::totalIsotopes(IsotopicDataCstSPtr isotopic_data_csp) const
2441 {
2442 double total_isotope_count = 0;
2443
2444 std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
2445 std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();
2446
2447 while(iter != iter_end)
2448 {
2449 total_isotope_count +=
2450 iter->second * isotopic_data_csp->getIsotopeCountBySymbol(iter->first);
2451
2452 ++iter;
2453 }
2454
2455 return total_isotope_count;
2456 }
2457
2458 /*!
2459 \brief Clears \e{all} the formula member data.
2460 */
2461 void
2462 24 Formula::clear()
2463 {
2464 24 m_title.clear();
2465 24 m_actionFormula.clear();
2466 24 m_plusFormula.clear();
2467 24 m_minusFormula.clear();
2468 24 m_forceCountIndex = false;
2469 24 m_symbolCountMap.clear();
2470
2471 24 m_isValid = false;
2472 24 }
2473
2474 void
2475 Formula::registerJsConstructor(QJSEngine *engine)
2476 {
2477 if(!engine)
2478 {
2479 qWarning() << "Cannot register class: engine is null";
2480 return;
2481 }
2482
2483 // Register the meta object as a constructor
2484 QJSValue jsMetaObject = engine->newQMetaObject(&Formula::staticMetaObject);
2485 engine->globalObject().setProperty("Formula", jsMetaObject);
2486 }
2487
2488 //////////////// PRIVATE FUNCTIONS /////////////////////
2489 //////////////// PRIVATE FUNCTIONS /////////////////////
2490 //////////////// PRIVATE FUNCTIONS /////////////////////
2491
2492 // GCOV_EXCL_START
2493 /*!
2494 \brief Sets the m_plusFormula formula to \a formula.
2495 */
2496 void
2497 Formula::setPlusFormula(const QString &formula)
2498 {
2499 m_plusFormula = formula;
2500 }
2501
2502 /*!
2503 \brief Sets the m_minusFormula formula to \a formula.
2504 */
2505 void
2506 Formula::setMinusFormula(const QString &formula)
2507 {
2508 m_minusFormula = formula;
2509 }
2510
2511 // GCOV_EXCL_STOP
2512
2513
2514
2/4
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
8 MSXPS_REGISTER_JS_CLASS(MsXpS::libXpertMassCore, Formula)
2515
2516 } // namespace libXpertMassCore
2517 } // namespace MsXpS
2518