GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Ionizer.cpp
Date: 2025-11-20 01:41:33
Lines:
291/430
67.7%
Functions:
29/40
72.5%
Branches:
186/534
34.8%

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
39
40 /////////////////////// Local includes
41 #include "MsXpS/libXpertMassCore/globals.hpp"
42 #include "MsXpS/libXpertMassCore/Utils.hpp"
43 #include "MsXpS/libXpertMassCore/Ionizer.hpp"
44
45
46 int ionizerMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Ionizer>(
47 "MsXpS::libXpertMassCore::Ionizer");
48
49 int ionizerPtrMetaTypeId =
50 qRegisterMetaType<MsXpS::libXpertMassCore::Ionizer *>(
51 "MsXpS::libXpertMassCore::IonizerPtr");
52
53 namespace MsXpS
54 {
55 namespace libXpertMassCore
56 {
57
58
59 /*!
60 \class MsXpS::libXpertMassCore::Ionizer
61 \inmodule libXpertMassCore
62 \ingroup PolChemDefBuildingdBlocks
63 \inheaderfile Ionizer.hpp
64
65 \brief The Ionizer class provides abstractions to ionize analytes.
66
67 Ionizations are chemical reactions that bring a charge (or more
68 charges) to an analyte. In this Ionizer class, that process is modelled along
69 with the idea that a previously ionized analyte might be re-ionized with
70 a different ionization chemistry (that is, either a different ionization agent,
71 or a different ionization nominal charge or a different ionization level).
72
73 To provide the basis for re-ionization, the class provides two ionization
74 rules describing the ionization state (present and future) of the analyte
75 managed by the Ionizer:
76
77 \list
78
79 \li one ionization rule (formula, nominal charge, level) for the "current state"
80 of ionization;
81
82 \li another ionization rule for the "ionization to come", that is the rule that
83 will be applied to the analyte managed by this Ionizer when the ionize()
84 function will be called.
85
86 \endlist
87
88 The class data members that describe the current ionization state of the
89 analyte managed by the Ionizer have the "currentState" prefix to their name (eg
90 \e m_currentStateFormula). The class data members that describe the so-called
91 "ionization-to-come" ionization rule have no prefix associated to their name (eg
92 \e m_formula).
93
94 The ionization chemical reaction is described by the
95 member \l Formula instances \l Ionizer::m_formula (ionization to come) and \l
96 Ionizer::m_currentStateFormula (current state of ionization). The electric
97 charge that is brought by this reaction is described by the members \l
98 Ionizer::m_nominalCharge and \l
99 Ionizer::m_currentStateNominalCharge. The
100 ionization level is described by the members \l Ionizer::m_level and \l
101 Ionizer::m_currentStateLevel. The ionization level is typically set to the
102 number of ionization reactions, that is, for example, it would be set to 10 for
103 a protein to be ionized ten times by protonation, each protonation event
104 bringing a nominal charge of 1.
105
106 An Ionizer like the following, if used to ionize a molecule of Mr 1000
107
108 \list
109 \li Formula +H
110 \li Charge 1
111 \li Level 1
112 \endlist
113
114 would lead to an ion of mass 1001 and of m/z 1001.
115
116 In protein chemistry, the ionization reaction is mainly a
117 protonation reaction, which brings a single charge. Thus, the Formula would be
118 "+H" and the charge 1. In MALDI, we would have a single protonation, thus
119 level would be set to 1 by default. In electrospray ionization, more than one
120 ionization reaction occur, and we could have a protein that is 25+, thus
121 having an ionization level of 25, for example.
122
123 An Ionizer like the following, if used to ionize a molecule of Mr 1000
124
125 \list
126 \li Formula +H
127 \li Charge 1
128 \li Level 4
129 \endlist
130
131 would lead to an ion of mass 1004 and of m/z 251 (1004 / 4).
132
133 An Ionizer like the following, if used to ionize a molecule of Mr 1000
134
135 \list
136 \li Formula +Mg (in fact Mg2+)
137 \li Charge 2
138 \li Level 4
139 \endlist
140
141 would lead to an ion of mass 1000 + (24 * 4) and of m/z 137 = (1096 / 8).
142
143 \note The real nature of the ionization is beared by the Formula (with, for
144 example in protein chemistry, "+H" for protonation and, in nucleic acids
145 chemistry, "-H" for deprotonation).
146
147 Thus, an Ionizer is valid if it generates a m/z ratio after ionization of the
148 analyte that is different than the previous (M) and if its member Formula
149 validates successfully. This means that the following should be true:
150
151 \list
152 \li The Formula should be valid (that is, should contain at least
153 one symbol (which might have a very small mass, like when the ionization is
154 done by electron gain or loss);
155
156 \li The charge is >0 (the ionization event should bring one
157 charge, otherwise there is no ionization). To reset the ionization
158 to 0 (that is to deionize the analyte, set the level to 0);
159
160 \li The level is >= 0 (if the level is 0, then the analyte is
161 considered not ionized);
162 \endlist
163
164 \note We do not consider compulsory that the Formula brings
165 a mass difference whatsoever, because some ionizations might not involve heavy
166 mass transfers, like electron gain or electron loss. However, the member Formula
167 must validate successfully and that means that it cannot be empty. In that case,
168 use a zero-sum formula like (-H+H) that has no weight.
169
170 The Ionizer class should be used with caution because its internal state
171 conditions heavily the reliability of the ionization calculations. The best way
172 to proceed is to construct the ionizer fully at start and then only use the
173 functional features of the class (ionize(), deionize(), molecularMasses()).
174 */
175
176
177 /*!
178 \variable int MsXpS::libXpertMassCore::Ionizer::mcsp_isotopicData
179
180 \brief The IsotopicData required to actually account for the ionization
181 formula.
182
183 \sa m_currentStateFormula, m_formula
184 */
185
186 /*!
187 \variable int MsXpS::libXpertMassCore::Ionizer::m_formula
188
189 \brief The Formula that is used to perform the ionization.
190
191 For a protonation event, that would be "+H", for example. For a
192 deprotonation event, that would be "-H".
193 */
194
195 /*!
196 \variable int MsXpS::libXpertMassCore::Ionizer::m_currentStateFormula
197
198
199 \brief The Formula that was last used to ionize the analyte
200
201 This formula is only used when an analyte is being ionized. If this formula
202 and the current state of ionization of the analyte is true, then it is used to
203 first deionize the analyte.
204 */
205
206 /*!
207 \variable int MsXpS::libXpertMassCore::Ionizer::m_nominalCharge
208
209 \brief The charge that is brought to the molecule when it is ionized by the
210 Ionizer with an ionization level (\l m_level) of 1. For a protonation, that
211 would be \e 1.
212 */
213
214 /*!
215 \variable int MsXpS::libXpertMassCore::Ionizer::m_currentStateNominalCharge
216
217 \brief The charge that is currently bore by the analyte when it was last
218 ionized.
219 */
220
221 /*!
222 \variable int MsXpS::libXpertMassCore::Ionizer::m_level
223
224 \brief The number of times this IonizRule is used to ionize a molecule.
225
226 For example, applying this Ionizer to a molecule
227
228 \list
229 \li Formula +H
230 \li Charge 1
231 \li Level 4
232 \endlist
233
234 would protonate it 4 times, with a charge of 4 and an increment in mass of the
235 mass of 4 times 1 proton. The m_level should be construed as "how many times
236 should the ionization reaction (formula / nominal charge) be performed."
237 */
238
239 /*!
240
241 \variable int MsXpS::libXpertMassCore::Ionizer::m_currentStateLevel
242
243 \brief The number of times the "ionization-to-come" formula and nominal charge
244 will be applied to the analyte managed by this Ionizer upon next call to \l
245 ionize().
246 */
247
248 /*!
249 \variable int MsXpS::libXpertMassCore::Ionizer::m_isValid
250
251 \brief Tells if this Ionizer is valid, both for the current state and for the
252 ionization-to-come member data.
253
254 \sa isValid(), validate()
255 */
256
257 /*!
258 \variable int MsXpS::libXpertMassCore::Ionizer::m_isCurrentStateValid
259
260 \brief Tells if the current ionization state of the analyte managed by this
261 Ionizer is valid.
262
263 \sa isValid(), isCurrentStateValid(), validate(), validateCurrentState()
264 */
265
266
267 /*!
268 \brief Constructs an Ionizer initialized as an empty object.
269
270 The instantiated Ionizer is invalid.
271 */
272
2/4
✓ Branch 2 taken 1000 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1000 times.
✗ Branch 6 not taken.
1000 Ionizer::Ionizer(QObject *parent): QObject(parent)
273 {
274
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
1000 }
275
276 /*!
277 \brief Constructs an Ionizer initialized only with the IsotopicData.
278
279 The instantiated Ionizer is invalid.
280 */
281 2 Ionizer::Ionizer(IsotopicDataCstSPtr isotopic_data_csp, QObject *parent)
282
2/4
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
2 : QObject(parent), mcsp_isotopicData(isotopic_data_csp)
283 {
284
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
2 }
285
286 /*!
287 \brief Constructs an Ionizer initialized with \a isotopic_data_csp, \a formula,
288 \a charge, \a level.
289
290 The current state member data are unitialized and the analyte managed by this
291 Ionizer is thus in an un-ionized state. Instead, if ionize() is called, the
292 ionization is performed using the data passed to this constructor.
293
294 After setting the member data, \l validate() is called and the \l m_isValid
295 member is set to the result of the validation.
296
297 Note that the validation first checks the ionization-to-come members and then
298 also checks the ionization current state members.
299
300 \sa validate(), validateCurrentState()
301 */
302 32 Ionizer::Ionizer(IsotopicDataCstSPtr isotopic_data_csp,
303 const Formula &formula,
304 int charge,
305 int level,
306 32 QObject *parent)
307 : QObject(parent),
308 64 mcsp_isotopicData(isotopic_data_csp),
309
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 m_formula(formula),
310 32 m_nominalCharge(charge),
311
1/2
✓ Branch 3 taken 32 times.
✗ Branch 4 not taken.
64 m_level(level)
312 {
313 32 ErrorList error_list;
314
1/2
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
32 m_isValid = validate(&error_list);
315
316 // qDebug() << "The ionizer formula:" << m_formula.getActionFormula();
317
318
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if(!m_isValid)
319 qCritical() << "The Ionizer just constructed is invalid, with errors:"
320 << Utils::joinErrorList(error_list, ", ");
321
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
32 }
322
323 /*!
324 \brief Constructs an Ionizer initialized with \a isotopic_data_csp, \a
325 formula_string, \a charge, \a level.
326
327 The current state member data are unitialized and the analyte managed by this
328 Ionizer is thus in an un-ionized state. Instead, if ionize() is called, the
329 ionization is performed using the data passed to this constructor.
330
331 After setting the member data, \l validate() is called and the \l m_isValid
332 member is set to the result of the validation.
333
334 Note that the validation first checks the ionization-to-come members and then
335 also checks the ionization current state members.
336
337 \sa validate(), validateCurrentState()
338 */
339 37 Ionizer::Ionizer(IsotopicDataCstSPtr isotopic_data_csp,
340 const QString &formula_string,
341 int charge,
342 int level,
343 37 QObject *parent)
344 : QObject(parent),
345 74 mcsp_isotopicData(isotopic_data_csp),
346
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 m_formula(Formula(formula_string)),
347 37 m_nominalCharge(charge),
348
1/2
✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
74 m_level(level)
349 {
350 37 ErrorList error_list;
351
1/2
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
37 m_isValid = validate(&error_list);
352
353 // qDebug() << "The ionizer formula:" << m_formula.getActionFormula();
354
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
37 if(!m_isValid)
356 qCritical() << "The Ionizer just constructed is invalid, with errors:"
357 << Utils::joinErrorList(error_list, ", ");
358
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
37 }
359
360 /*!
361 \brief Constructs an Ionizer as a copy of \a other.
362
363 After setting the member data, \l validate() is called and the \l m_isValid
364 member is set to the result of the validation.
365 */
366 4325 Ionizer::Ionizer(const Ionizer &other, QObject *parent)
367 : QObject(parent),
368 8650 mcsp_isotopicData(other.mcsp_isotopicData),
369
1/2
✓ Branch 1 taken 4325 times.
✗ Branch 2 not taken.
4325 m_formula(other.m_formula),
370 4325 m_nominalCharge(other.m_nominalCharge),
371 4325 m_level(other.m_level),
372 4325 m_currentStateFormula(other.m_currentStateFormula),
373 4325 m_currentStateNominalCharge(other.m_currentStateNominalCharge),
374 4325 m_currentStateLevel(other.m_currentStateLevel),
375
1/2
✓ Branch 3 taken 4325 times.
✗ Branch 4 not taken.
8650 m_isCurrentStateValid(other.m_isCurrentStateValid)
376 {
377 4325 ErrorList error_list;
378
1/2
✓ Branch 1 taken 4325 times.
✗ Branch 2 not taken.
4325 m_isValid = validate(&error_list);
379
380
2/2
✓ Branch 0 taken 2711 times.
✓ Branch 1 taken 1614 times.
4325 if(!m_isValid)
381
2/4
✓ Branch 1 taken 2711 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2711 times.
✗ Branch 5 not taken.
5422 qCritical() << "The Ionizer just constructed is invalid, with errors:"
382
3/6
✓ Branch 1 taken 2711 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2711 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2711 times.
✗ Branch 8 not taken.
5422 << Utils::joinErrorList(error_list, ", ");
383
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4325 }
384
385 /*!
386 \ brief Initializes this Ionizer instance using* \a other.
387
388 After setting the member data, \l validate() is called and the \l m_isValid
389 member is set to the result of the validation.
390 */
391 void
392 8 Ionizer::initialize(const Ionizer &other)
393 {
394 8 mcsp_isotopicData = other.mcsp_isotopicData;
395 8 m_formula = other.m_formula;
396 8 m_nominalCharge = other.m_nominalCharge;
397 8 m_level = other.m_level;
398 8 m_currentStateFormula = other.m_currentStateFormula;
399 8 m_currentStateNominalCharge = other.m_currentStateNominalCharge;
400 8 m_currentStateLevel = other.m_currentStateLevel;
401 8 m_isCurrentStateValid = other.m_isCurrentStateValid;
402
403 8 ErrorList error_list;
404
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 m_isValid = validate(&error_list);
405
406
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if(!m_isValid)
407 qCritical() << "The Ionizer just constructed is invalid, with errors:"
408 << Utils::joinErrorList(error_list, ", ");
409 8 }
410
411 /*!
412 \brief Sets the IsotopicData to \a isotopic_data_csp.
413
414 After setting the member data, \l validate() is called and the \l m_isValid
415 member is set to the result of the validation.
416 */
417 void
418 78 Ionizer::setIsotopicDataCstSPtr(IsotopicDataCstSPtr isotopic_data_csp)
419 {
420 78 mcsp_isotopicData = isotopic_data_csp;
421
422 78 ErrorList error_list;
423
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 m_isValid = validate(&error_list);
424
425
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 1 times.
78 if(!m_isValid)
426
2/4
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
154 qCritical() << "The Ionizer is invalid, with errors:"
427
3/6
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 77 times.
✗ Branch 8 not taken.
154 << Utils::joinErrorList(error_list, ", ");
428 78 }
429
430 /*!
431 \brief Returns the IsotopicData.
432 */
433 IsotopicDataCstSPtr
434 6 Ionizer::getIsotopicDataCstSPtr() const
435 {
436 6 return mcsp_isotopicData;
437 }
438
439 /*!
440 \brief Sets the Formula to \a formula.
441
442 The m_formula describes the reaction to be applied to an analyte when the
443 ionize() function gets called.
444
445 After setting the member data, \l validate() is called and the \l m_isValid
446 member is set to the result of the validation.
447 */
448 void
449 Ionizer::setFormula(const Formula &formula)
450 {
451 m_formula = formula;
452
453 ErrorList error_list;
454 m_isValid = validate(&error_list);
455
456 if(!m_isValid)
457 {
458 qCritical() << "The Ionizer did not validate successfully, with errors:"
459 << Utils::joinErrorList(error_list, ", ");
460 }
461 }
462
463 /*!
464 \brief Sets the Formula to \a formula_string.
465
466 The m_formula describes the reaction to be applied to an analyte when the
467 ionize() function gets called.
468
469 After setting the member data, \l validate() is called and the \l m_isValid
470 member is set to the result of the validation.
471 */
472 void
473 13 Ionizer::setFormula(const QString &formula_string)
474 {
475
476
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 m_formula = Formula(formula_string);
477
478 13 ErrorList error_list;
479
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 m_isValid = validate(&error_list);
480
481
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 if(!m_isValid)
482 {
483
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
24 qCritical() << "The Ionizer did not validate successfully, with errors:"
484
3/6
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
24 << Utils::joinErrorList(error_list, ", ");
485 }
486 13 }
487
488 /*!
489 \brief Returns a const reference to the Formula.
490 */
491 const Formula &
492 1597 Ionizer::getFormulaCstRef() const
493 {
494 1597 return m_formula;
495 }
496
497 /*!
498 \brief Returns a reference to the Formula.
499 */
500 Formula &
501 Ionizer::getFormulaRef()
502 {
503 return m_formula;
504 }
505
506 /*!
507 \brief Returns a const reference to the current state Formula.
508
509 The m_currentStateFormula describes the formula that has been already applied to
510 the analyte as it has already been ionized.
511 */
512 const Formula &
513 Ionizer::getCurrentStateFormulaCstRef() const
514 {
515 return m_currentStateFormula;
516 }
517
518 /*!
519 \brief Sets the nominal charge to \a nominal_charge.
520
521 The m_nominalCharge describes the charge that is brought to the analyte by the
522 m_formula member when ionize() is called.
523
524 After setting the member data, \l validate() is called and the \l m_isValid
525 member is set to the result of the validation.
526 */
527 void
528 13 Ionizer::setNominalCharge(int nominal_charge)
529 {
530 13 m_nominalCharge = nominal_charge;
531
532 13 ErrorList error_list;
533
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 m_isValid = validate(&error_list);
534
535
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10 times.
13 if(!m_isValid)
536 {
537
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 qCritical() << "The Ionizer did not validate successfully, with errors:"
538
3/6
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
6 << Utils::joinErrorList(error_list, ", ");
539 }
540 13 }
541
542 /*!
543 \brief Returns the nominal charge.
544 */
545 int
546 12 Ionizer::getNominalCharge() const
547 {
548 12 return m_nominalCharge;
549 }
550
551 /*!
552 \brief Returns the current state nominal charge.
553
554 The m_currentStateNominalCharge member datum describes the charge that was
555 brought by the formula to the analyte when it was ionized (independently from
556 the ionization level).
557 */
558 int
559 Ionizer::getCurrentStateNominalCharge() const
560 {
561 return m_currentStateNominalCharge;
562 }
563
564 /*!
565 \brief Returns the total charge.
566
567 The total charge is the product (m_nominalCharge * m_level). This charge will be
568 brought to the analyte when ionize() gets called.
569 */
570 int
571 6449 Ionizer::charge() const
572 {
573 6449 return m_nominalCharge * m_level;
574 }
575
576 /*!
577 \brief Returns the last status total charge.
578
579 The total charge is the product (m_currentStateNominalCharge *
580 m_currentStateLevel). This current state charge is the charge that was brought
581 to the analyte when ionize() was called previously. It thus reflects the current
582 charge of the analyte managed by this Ionizer.
583 */
584 int
585 2099 Ionizer::currentStateCharge() const
586 {
587 2099 return m_currentStateNominalCharge * m_currentStateLevel;
588 }
589
590 /*!
591 \brief Sets the current state member data.
592
593 This is useful when starting from an ionized state (for a Formula, for example)
594 that will require to be ionized later in a different manner. See, for example,
595 in MassXpert3, the MzCalculationDlg::getSrcIonizerData() function.
596
597 The three parameters thus describe in full the current ionization status of the
598 analyte managed by this Ionizer:
599
600 \list
601
602 \li \a formula The formula that describes the current ionization state;
603
604 \li \a nominal_charge The charge that is brought by the reaction described in
605 the formula;
606
607 \li \a level The count of ionization events to be applied to the analyte managed
608 by this Ionizer instance.
609
610 \endlist
611
612 The m_isCurrentStateValid is set to the result of the validation of the current
613 state of this Ionizer.
614 */
615 void
616 Ionizer::forceCurrentState(const Formula &formula,
617 int nominal_charge,
618 int level)
619 {
620 m_currentStateFormula = formula;
621 m_currentStateNominalCharge = nominal_charge;
622 m_currentStateLevel = level;
623
624 ErrorList error_list;
625 m_isCurrentStateValid = validateCurrentState(&error_list);
626 }
627
628 /*!
629 \brief Sets the current state ionization level to \a level.
630
631 This function resets the current state ionization level and is practical to use
632 for setting the analyte managed by this Ionizer to an un-ionized state even if
633 the other members of the current state are valid (the formula and the nominal
634 charged are correctly described).
635 */
636 void
637 7 Ionizer::forceCurrentStateLevel(int level)
638 {
639 7 m_currentStateLevel = level;
640 7 }
641
642 /*!
643 \brief Sets the ionization level to \a level.
644
645 This ionization level describes the number of times the ionization formula and
646 the nominal charge need to be applied when ionize() gets called on the managed
647 analyte.
648
649 After setting the member data, \l validate() is called and the \l m_isValid
650 member is set to the result of the validation.
651 */
652 void
653 1594 Ionizer::setLevel(int level)
654 {
655 1594 m_level = level;
656
657 1594 ErrorList error_list;
658
1/2
✓ Branch 1 taken 1594 times.
✗ Branch 2 not taken.
1594 m_isValid = validate(&error_list);
659
660
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1591 times.
1594 if(!m_isValid)
661 {
662
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 qCritical() << "The Ionizer did not validate successfully, with errors:"
663
3/6
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
6 << Utils::joinErrorList(error_list, ", ");
664 }
665 1594 }
666
667 /*!
668 \brief Returns the ionization level.
669
670 This ionization level describes the number of times the ionization formula and
671 the nominal charge need to be applied when ionize() gets called on the managed
672 analyte.
673 */
674 int
675 1590 Ionizer::getLevel() const
676 {
677 1590 return m_level;
678 }
679
680 /*!
681 \brief Returns the ionization level.
682
683 This ionization level describes the number of times the ionization formula and
684 the nominal charge have been applied when ionize() was called on the managed
685 analyte.
686 */
687 int
688 Ionizer::getCurrentStateLevel() const
689 {
690 return m_currentStateLevel;
691 }
692
693 /*!
694 \brief
695
696 Allocates on the stack an Ionizer instance and initializes its
697 ionization-to-come data using the current state data from this Ionizer. The
698 current ionization state of the returned Ionizer instance are reset to nothing,
699 effectively setting the managed analyte to a non-ionized state.
700
701 Returns the allocated Ionizer instance.
702 */
703 Ionizer
704 Ionizer::makeIonizerWithCurrentStateData()
705 {
706 Ionizer ionizer(mcsp_isotopicData);
707 ionizer.m_formula = m_currentStateFormula;
708 ionizer.m_nominalCharge = m_currentStateNominalCharge;
709 ionizer.m_level = m_currentStateLevel;
710 ionizer.m_isValid = m_isCurrentStateValid;
711
712 // All the current state of the new Ionizer are set to un-ionized,
713 // which is good.
714 ErrorList error_list;
715
716 if(!ionizer.validate(&error_list))
717 qFatal() << "The created ionizer is not valid.";
718
719 return ionizer;
720 }
721
722 /*!
723 \brief Returns the current ionization status.
724
725 The analyte managed by this Ionizer is considered ionized if the current state
726 nominal charge multiplied by the ionization level is not 0.
727
728 \sa currentStateCharge()
729 */
730 bool
731 425 Ionizer::isIonized() const
732 {
733 425 return currentStateCharge() ? true : false;
734 }
735
736 /*!
737 \brief Returns the outcome of the ionization process.
738
739 The \a mono and \a avg masses are the masses of the analyte to be ionized. These
740 masses are passed as non const because they are modified to become m/z values
741 accounting for the ionization process (or to become Mr values accounting for the
742 deionization process).
743
744 The ionization is performed by accounting into \a mono and \a avg the formula of
745 the ionization rule compounded by the level of ionization that is required.
746 Then, the resulting masses are divided by the charge (that is the product of
747 m_nominalCharge by m_level), thus becoming the m/z value of the ionized analyte.
748
749 If this Ionizer instance's m_isIonized member value is true, the function does
750 not do anything and returns Enums::IonizationOutcome::UNCHANGED.
751
752 The modification of \a mono and \a avg is "atomic", that it, it occurs only if
753 the ionization is successful and leads to values different than those initially
754 passed to the function.
755 */
756 Enums::IonizationOutcome
757 1622 Ionizer::ionize(double &mono, double &avg) const
758 {
759 // qDebug() << "Ionizing with this ionizer:" << toString()
760 // << "The current masses are:" << mono << "-" << avg;
761
762
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1622 times.
1622 if(!m_isValid)
763 {
764 qCritical() << "Cannot perform ionization with invalid Ionizer.";
765 return Enums::IonizationOutcome::FAILED;
766 }
767
768
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1617 times.
1622 if(currentStateCharge())
769 {
770 // qDebug() << "Asking for ionization of an analyte that is already ionized. "
771 // "Performing deionization first.";
772
773 5 Enums::IonizationOutcome ionization_outcome = deionize(mono, avg);
774
775
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if(ionization_outcome == Enums::IonizationOutcome::FAILED)
776 qFatalStream() << "Failed to deionize.";
777 }
778
779
1/3
✗ Branch 0 not taken.
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
1622 if(!charge())
780 {
781 // qDebug()
782 // << "Asking for ionization of an analyte with ionization charge 0. "
783 // "Performing no action.";
784
785 return Enums::IonizationOutcome::UNCHANGED;
786 }
787
788 // qDebug() << qSetRealNumberPrecision(10)
789 // << "Before ionization, Ionizer:" << toString()
790 // << "with entering masses: " << mono << "-" << avg << "\n\n";
791
792 1622 double temp_mono = 0;
793 1622 double temp_avg = 0;
794
795 // Account for the ionization rule formula in the temp masses.
796
797 // Note the times(m_level) param to call below.
798 1622 bool ok = false;
799
800 // To enforce constness of the function
801 1622 Formula temp_formula(m_formula);
802
803
1/2
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
1622 temp_formula.accountMasses(
804 1622 ok, mcsp_isotopicData, temp_mono, temp_avg, m_level);
805
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1622 times.
1622 if(!ok)
806 {
807 qCritical() << "Failed to account the masses of the ionizer formula.";
808 return Enums::IonizationOutcome::FAILED;
809 }
810
811 /*qDebug() << qSetRealNumberPrecision(10)
812 << "The ionization formula:" << temp_formula.getActionFormula()
813 1622 << "resolved to computed masses:" << temp_mono << "-" << temp_avg*/;
814
815 // Add the ionization mass to the the masses of the entity to be ionized.
816
817 1622 temp_mono += mono;
818 1622 temp_avg += avg;
819
820 // qDebug() << qSetRealNumberPrecision(10)
821 // << "The ionized analyte has masses:" << temp_mono << "-" << temp_avg;
822
823 // Now account for the charge and thus produce m/z instead of m.
824
825
1/2
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
1622 temp_mono /= charge();
826
1/2
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
1622 temp_avg /= charge();
827
828 // qDebug() << qSetRealNumberPrecision(10)
829 // << "The ionized analyte has m/z:" << temp_mono << "-" << temp_avg;
830
831 // Now establish if we did actually change something in the process:
832
3/6
✓ Branch 0 taken 1622 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1622 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1622 times.
1622 if((temp_mono != mono && temp_avg == avg) ||
833 (temp_mono == mono && temp_avg != avg))
834 qFatalStream() << "Programming error. Ionization failed.";
835
836
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1622 if(temp_mono == mono && temp_avg == avg)
837 qFatalStream() << "Programming error. Ionization failed.";
838
839 // Yes, ionization changed something.
840 1622 mono = temp_mono;
841 1622 avg = temp_avg;
842
843
1/2
✓ Branch 1 taken 1622 times.
✗ Branch 2 not taken.
1622 m_currentStateFormula = m_formula;
844 1622 m_currentStateNominalCharge = m_nominalCharge;
845 1622 m_currentStateLevel = m_level;
846 // m_wasIonized = true;
847 1622 m_isCurrentStateValid = true;
848
849 // qDebug() << qSetRealNumberPrecision(10)
850 // << "After ionization, "
851 // "exiting masses: "
852 // << mono << "-" << avg << "\n\n";
853
854 1622 return Enums::IonizationOutcome::IONIZED;
855 1622 }
856
857 /*!
858 \brief Attempts to deionize the analyte and returns the outcome.
859
860 The analyte (of which the masses are passed as modifiable \a mono and \a avg
861 arguments) is deionized using m_formula, m_nominalCharge and m_level.
862
863 The following logic is applied:
864
865 \list 1
866
867 \li If this Ionizable is not ionized, this function does not do anything and
868 returns Enums::IonizationOutcome::UNCHANGED.
869
870 \li If this Ionizer is not valid, this function returns
871 Enums::IonizationOutcome::FAILED because it makes no sense to try to change the
872 ionization of an analyte if the Ionizer is invalid.
873
874 \li The deionization process is carried out.
875
876 \endlist
877
878 If the deionization process leads to masses different to \a mono and \a avg,
879 then it is deemed successful and Enums::IonizationOutcome::DEIONIZED is
880 returned, otherwise Enums::IonizationOutcome::UNCHANGED is returned.
881 */
882 Enums::IonizationOutcome
883 14 Ionizer::deionize(double &mono, double &avg) const
884 {
885 14 qDebug() << qSetRealNumberPrecision(10)
886 << "Asking for deionization with ionizer:" << toString()
887 << "and masses:" << mono << "-" << avg << "\n\n";
888
889
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(!m_isValid)
890 {
891 qCritical() << "Cannot perform ionization with invalid Ionizer.";
892 return Enums::IonizationOutcome::FAILED;
893 }
894
895
1/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if(!currentStateCharge())
896 {
897 qInfo() << "Asking for deionization of an analyte that is not ionized. "
898 "Performing no action.";
899 return Enums::IonizationOutcome::UNCHANGED;
900 }
901
902 // Reverse the electrical effect of ionization using the last ionization
903 // state.
904
905 14 double temp_mono = mono * currentStateCharge();
906 14 double temp_avg = avg * currentStateCharge();
907
908 14 qDebug() << qSetRealNumberPrecision(10)
909 << "After reversal of the by-the-charge division, new masses:"
910 << temp_mono << "-" << temp_avg;
911
912 // Use the last Formula to reverse the chemical effect of ionization.
913
914 // Note the negated 'times'(- m_ionizeRule.level())param to call
915 // below so that we revert the chemical action that led level
916 // times to the ionization of the analyte. Note that we do not
917 // have any compound(level * charge) because we are dealing with
918 // matter here, not charges, thus only 'level' is to be taken into
919 // consideration.
920 14 bool ok = false;
921 // To enforce constness of the function
922 14 Formula temp_formula(m_currentStateFormula);
923
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 temp_formula.accountMasses(
924 14 ok, mcsp_isotopicData, temp_mono, temp_avg, -m_currentStateLevel);
925
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(!ok)
926 qFatalStream() << "Failed to account the masses of the ionizer formula.";
927
928 14 qDebug()
929 << qSetRealNumberPrecision(10)
930 << "After having removed the ionization last formula masses from the "
931 "previously computed (M+z) mono and avg masses: we now get Mr masses:"
932 << temp_mono << "-" << temp_avg;
933
934 // Now establish if we did actually change something in the process:
935
3/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
14 if((temp_mono != mono && temp_avg == avg) ||
936
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 (temp_mono == mono && temp_avg != avg) ||
937 (temp_mono == mono && temp_avg == avg))
938 qFatalStream() << "The deionization failed.";
939
940 // Yes, deionization changed something.
941 14 mono = temp_mono;
942 14 avg = temp_avg;
943
944
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 m_currentStateFormula.clear();
945 14 m_currentStateNominalCharge = 0;
946 14 m_currentStateLevel = 0;
947 14 m_isCurrentStateValid = true;
948
949 14 return Enums::IonizationOutcome::DEIONIZED;
950 14 }
951
952 /*!
953 \brief Sets the molecular mass of the analyte of which the masses are passed as
954 modifiable parameters in \a mono and \a avg.
955
956 The analyte is first deionized (on copy data) and if the deionization process
957 was successful, \a mono and \a avg are set to the masses of the deionized
958 analyte (that is, Mr molecular masses).
959
960 If the analyte is not ionized, returns Enums::IonizationOutcome::UNCHANGED (and
961 nothing is changed). If the analyte was actually ionized, then if it is first
962 successfully deionized, then the analyte is set to deionized and the \a mono
963 and \a avg masses are set to the Mr molecular mass.
964 */
965 Enums::IonizationOutcome
966 Ionizer::molecularMasses(double &mono, double &avg) const
967 {
968 qDebug() << "Asking for computation of molecular masses with ionizer"
969 << toString() << "and masses" << mono << "-" << avg;
970
971 if(!currentStateCharge())
972 {
973 qInfo() << "Asking for molecular masses of a non-ionized analyte. "
974 "Leaving them unchanged.";
975 return Enums::IonizationOutcome::UNCHANGED;
976 }
977
978 double temp_mono = mono;
979 double temp_avg = avg;
980
981 Enums::IonizationOutcome outcome = deionize(temp_mono, temp_avg);
982
983 if(outcome == Enums::IonizationOutcome::FAILED)
984 qFatalStream() << "Programming error. Failed to deionize.";
985
986 if(outcome == Enums::IonizationOutcome::DEIONIZED)
987 {
988 mono = temp_mono;
989 avg = temp_avg;
990
991 m_currentStateFormula = m_formula;
992 m_currentStateNominalCharge = m_nominalCharge;
993 m_currentStateLevel = 0;
994 m_isCurrentStateValid = true;
995 }
996
997 return outcome;
998 }
999
1000 /*!
1001 \brief Assigns \a other to this Ionizer.
1002
1003 After setting the member data, \l validate() is called and the \l m_isValid
1004 member is set to the result of the validation.
1005
1006 Returns a reference to this ionization rule.
1007 */
1008 Ionizer &
1009 1608 Ionizer::operator=(const Ionizer &other)
1010 {
1011
2/2
✓ Branch 0 taken 1607 times.
✓ Branch 1 taken 1 times.
1608 if(&other == this)
1012 return *this;
1013
1014 1607 mcsp_isotopicData = other.mcsp_isotopicData;
1015 1607 m_formula = other.m_formula;
1016 1607 m_nominalCharge = other.m_nominalCharge;
1017 1607 m_level = other.m_level;
1018 1607 m_isValid = other.m_isValid;
1019
1020 1607 m_currentStateFormula = other.m_currentStateFormula;
1021 1607 m_currentStateNominalCharge = other.m_currentStateNominalCharge;
1022 1607 m_currentStateLevel = other.m_currentStateLevel;
1023 1607 m_isCurrentStateValid = other.m_isCurrentStateValid;
1024
1025 1607 return *this;
1026 }
1027
1028 /*!
1029 \brief Returns true if this Ionizer is identical to \a other, false
1030 otherwise.
1031
1032 \note The comparison of the IsotopicData is deep, with a true comparison of all
1033 the Isotope instances, not only of the shared pointers.
1034 */
1035 bool
1036 17 Ionizer::operator==(const Ionizer &other) const
1037 {
1038
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 3 times.
17 if(&other == this)
1039 return true;
1040
1041
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 if(mcsp_isotopicData != nullptr && other.mcsp_isotopicData != nullptr)
1042 {
1043 14 qDebug() << "Now checking the isotopic data";
1044
1045
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if(*mcsp_isotopicData.get() != *other.mcsp_isotopicData.get())
1046 {
1047 qDebug() << "The isotopic data are not identical.";
1048 return false;
1049 }
1050 }
1051
1052
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if(m_formula != other.m_formula)
1053 {
1054 qDebug() << "The formulas are different.";
1055 return false;
1056 }
1057
1058
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_nominalCharge != other.m_nominalCharge)
1059 {
1060 qDebug() << "The nominal charges are different.";
1061 return false;
1062 }
1063
1064
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_level != other.m_level)
1065 {
1066 qDebug() << "The levels are different.";
1067 return false;
1068 }
1069
1070
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_isValid != other.m_isValid)
1071 {
1072 qDebug() << "The validity status are different.";
1073 return false;
1074 }
1075
1076
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
14 if(m_currentStateFormula != other.m_currentStateFormula)
1077 {
1078 qDebug() << "The current formulas are different.";
1079 return false;
1080 }
1081
1082
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_currentStateNominalCharge != other.m_currentStateNominalCharge)
1083 {
1084 qDebug() << "The current nominal charges are different.";
1085 return false;
1086 }
1087
1088
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_currentStateLevel != other.m_currentStateLevel)
1089 {
1090 qDebug() << "The current levels are different.";
1091 return false;
1092 }
1093
1094
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(m_isCurrentStateValid != other.m_isCurrentStateValid)
1095 {
1096 qDebug() << "The current validity statuses are different.";
1097 return false;
1098 }
1099
1100 return true;
1101 }
1102
1103 /*!
1104 \brief Returns true if this Ionizer is different than \a other, false
1105 otherwise.
1106
1107 Returns the negated result of operator==().
1108 */
1109 bool
1110 8 Ionizer::operator!=(const Ionizer &other) const
1111 {
1112
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if(&other == this)
1113 return false;
1114
1115 6 return !operator==(other);
1116 }
1117
1118 /*!
1119 \brief Renders the ionization rule XML \a element.
1120
1121 The XML element is parsed and the data extracted from the XML data
1122 are set to this Ionizer instance.
1123
1124 The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>
1125
1126 A typical ionization rule element looks like this:
1127
1128 \code
1129 <ionizerule>
1130 <formula>+H</formula>
1131 <charge>1</charge>
1132 <level>1</level>
1133 </ionizerule>
1134 \endcode
1135
1136 The caller is reponsible for
1137 checking the validity of the IonizeRule prior use.
1138
1139 Returns true if the parsing is successful, false otherwise.
1140
1141 \sa formatXmlIonizeRuleElement(int offset, const QString &indent)
1142 */
1143 bool
1144 70 Ionizer::renderXmlIonizeRuleElement(const QDomElement &element)
1145 {
1146 // Assume the Ionizer is valid. We'll change that as we go.
1147 70 m_isValid = true;
1148
1149 70 QDomElement child;
1150
1151 // <ionizerule>
1152 // <formula>+H</formula>
1153 // <charge>1</charge>
1154 // <level>1</level>
1155 // </ionizerule>
1156
1157
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 70 times.
140 if(element.tagName() != "ionizerule")
1158 {
1159 qCritical() << "Cannot render ionization rule. Problem with the "
1160 "<ionizerule> element.";
1161
1162 m_isValid = false;
1163 return m_isValid;
1164 }
1165
1166 // <formula>
1167
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 70 times.
✗ Branch 5 not taken.
140 child = element.firstChildElement();
1168
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 70 times.
140 if(child.tagName() != "formula")
1169 {
1170 qCritical() << "Cannot render ionization rule. Problem with the "
1171 "<formula> element.";
1172
1173 m_isValid = false;
1174 return m_isValid;
1175 }
1176
1177
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 70 times.
70 if(!m_formula.renderXmlFormulaElement(child))
1178 {
1179 qCritical()
1180 << "Cannot render ionization rule. Problem with the validation"
1181 "of the Formula.";
1182
1183 m_isValid = false;
1184 }
1185 70 qDebug() << "Rendered formula:" << m_formula.getActionFormula();
1186
1187 // <charge>
1188
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 70 times.
✗ Branch 5 not taken.
140 child = child.nextSiblingElement();
1189
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 70 times.
140 if(child.tagName() != "charge")
1190 {
1191 qCritical() << "Cannot render ionization rule. Problem with the "
1192 "<charge> element.";
1193
1194 m_isValid = false;
1195 return m_isValid;
1196 }
1197
1198 70 bool ok = false;
1199
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
140 m_nominalCharge = child.text().toInt(&ok);
1200
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
70 if(!m_nominalCharge && !ok)
1201 {
1202 qCritical() << "Cannot render ionization rule. Problem with the "
1203 "nominal charge value.";
1204
1205 m_isValid = false;
1206 }
1207 70 qDebug() << "Rendered nominal charge:" << m_nominalCharge;
1208
1209 // <level>
1210
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 70 times.
✗ Branch 5 not taken.
140 child = child.nextSiblingElement();
1211
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 70 times.
140 if(child.tagName() != "level")
1212 {
1213 qCritical() << "Cannot render ionization rule. Problem with the "
1214 "<level> element.";
1215
1216 m_isValid = false;
1217 return m_isValid;
1218 }
1219
1220 70 ok = false;
1221
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
140 m_level = child.text().toInt(&ok);
1222
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
70 if(!m_level && !ok)
1223 {
1224 qCritical() << "Cannot render ionization rule. Problem with the "
1225 "level value.";
1226
1227 m_isValid = false;
1228 }
1229 70 qDebug() << "Rendered level:" << m_level;
1230
1231 70 qDebug() << "At this point validate the Ionizer.";
1232
1233 70 ErrorList error_list;
1234
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
70 m_isValid = validate(&error_list);
1235
1236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if(!m_isValid)
1237 qCritical() << "Failed to successfully render <ionizerule> with errors:"
1238 << Utils::joinErrorList(error_list, ", ");
1239
1240 70 return m_isValid;
1241 140 }
1242
1243 /*!
1244 \brief Formats and returns a string suitable to use as an XML element.
1245
1246 Formats a string suitable to be used as an XML element in a
1247 polymer chemistry definition file. The typical ionization rule
1248 element that is generated in this function looks like this:
1249
1250 The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>
1251
1252 A typical ionization rule element looks like this:
1253
1254 \code
1255
1256 <ionizerule>
1257 ~~<formula>+H</formula>
1258 ~~<charge>1</charge>
1259 ~~<level>1</level>
1260 </ionizerule>
1261
1262 \endcode
1263
1264 \a offset times the \a indent string must be used as a lead in the
1265 formatting of elements.
1266
1267 \sa renderXmlIonizeRuleElement(const QDomElement &element)
1268 */
1269 QString
1270 3 Ionizer::formatXmlIonizeRuleElement(int offset, const QString &indent)
1271 {
1272 3 int newOffset;
1273 3 int iter = 0;
1274
1275 3 QString lead("");
1276 3 QString text;
1277
1278
1279 // Prepare the lead.
1280 3 newOffset = offset;
1281
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 while(iter < newOffset)
1282 {
1283
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 lead += indent;
1284 2 ++iter;
1285 }
1286
1287 /* We are willing to create an <ionizerule> node that should look like this:
1288 *
1289 *<ionizerule>
1290 * <formula>+H</formula>
1291 * <charge>1</charge>
1292 * <level>1</level>
1293 *</ionizerule>
1294 *
1295 */
1296
1297
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 text += QString("%1<ionizerule>\n").arg(lead);
1298
1299 // Prepare the lead.
1300 3 ++newOffset;
1301 3 lead.clear();
1302 3 iter = 0;
1303
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
11 while(iter < newOffset)
1304 {
1305
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 lead += indent;
1306 5 ++iter;
1307 }
1308
1309 // Continue with indented elements.
1310
1311
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 text += QString("%1<formula>%2</formula>\n")
1312
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 .arg(lead)
1313
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 .arg(m_formula.getActionFormula(/*with_title*/ true));
1314
1315
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
3 text += QString("%1<charge>%2</charge>\n").arg(lead).arg(m_nominalCharge);
1316
1317
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
3 text += QString("%1<level>%2</level>\n").arg(lead).arg(m_level);
1318
1319 // Prepare the lead for the closing element.
1320 3 --newOffset;
1321 3 lead.clear();
1322 3 iter = 0;
1323
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
8 while(iter < newOffset)
1324 {
1325
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 lead += indent;
1326 2 ++iter;
1327 }
1328
1329
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 text += QString("%1</ionizerule>\n").arg(lead);
1330
1331 3 return text;
1332 3 }
1333
1334 /*!
1335 \brief Validates this Ionizer, setting any error message to \a error_list_p.
1336
1337 An Ionizer is valid if the following data for the ionization-to-come:
1338
1339 \list
1340
1341 \li The member IsotopicData are available
1342
1343 \li The Formula validates successfully (that is, should contain at least
1344 one atom (for a zero-mass formula, use "+H-H", for example);
1345
1346 \li The nominal charge should be != 0 because otherwise the Ionizer raison
1347 d'être is absent.
1348
1349 \endlist
1350
1351 Further, the current ionization state data are checked and are valid if:
1352
1353 \list
1354
1355 \li The member IsotopicData are available
1356
1357 \li The Formula validates successfully if it is not empty (that is, should
1358 contain at least one atom (for a zero-mass formula, use "+H-H", for example);
1359
1360 \endlist
1361
1362 Note that both the current state nominal charge and level may be 0 (which is
1363 logical if the analyte being managed by this Ionizer is not currently ionized).
1364
1365 If any of the tests above fail, the Ionizer is considered
1366 invalid and the validity status (m_isValid) value is set to false; true
1367 otherwise.
1368
1369 Returns true if validation succeeded, false otherwise.
1370
1371 \sa Formula::validate(), validateCurrentState(), isValid()
1372 */
1373 bool
1374 6305 Ionizer::validate(ErrorList *error_list_p) const
1375 {
1376 6305 Q_ASSERT(error_list_p != nullptr);
1377
1378
2/2
✓ Branch 0 taken 3584 times.
✓ Branch 1 taken 2721 times.
6305 qsizetype error_count = error_list_p->size();
1379
1380
3/4
✓ Branch 0 taken 3584 times.
✓ Branch 1 taken 2721 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3584 times.
9889 if(mcsp_isotopicData == nullptr || mcsp_isotopicData.get() == nullptr ||
1381 3584 !mcsp_isotopicData->size())
1382 {
1383
1/2
✓ Branch 2 taken 2721 times.
✗ Branch 3 not taken.
2721 qCritical() << "Cannot validate Ionizer without available IsotopicData.";
1384 5442 error_list_p->push_back(
1385 "Cannot validate Ionizer without available IsotopicData");
1386 2721 m_isValid = false;
1387 2721 return m_isValid;
1388 }
1389
1390 // qDebug() << "Going to validate the formula" <<
1391 // m_formula.getActionFormula();
1392
1393
4/6
✓ Branch 2 taken 3584 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3584 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 77 times.
✓ Branch 7 taken 3507 times.
7168 if(!m_formula.validate(mcsp_isotopicData, error_list_p))
1394 {
1395 77 QString msg =
1396 77 QString("The ionizer formula %1 failed to validate successfully")
1397
2/4
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
154 .arg(m_formula.getActionFormula());
1398
2/4
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
77 qCritical() << msg;
1399
1/2
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
154 error_list_p->push_back(msg);
1400 77 }
1401
1402 // Note that it is perfectly possible that the level of the ionization in
1403 // the ionizer be 0. Instead, having the nominal charge set to 0 means
1404 // that the ionizer will never ionize anything even by setting the level
1405 // to some non-naught value.
1406 3584 qDebug() << "Going to validate the nominal charge:" << m_nominalCharge;
1407
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 3498 times.
3584 if(!m_nominalCharge)
1408 {
1409 172 qCritical()
1410
1/2
✓ Branch 1 taken 86 times.
✗ Branch 2 not taken.
86 << "Cannot validate Ionizer that has its nominal charge equal to 0.";
1411 172 error_list_p->push_back(
1412 "Cannot validate Ionizer that has its nominal charge equal to 0");
1413 }
1414
1415 // And now validate the current state
1416
1417 // qDebug() << "Going to validate the current state data.";
1418 3584 validateCurrentState(error_list_p);
1419 // qDebug() << "Done validating the current state data with status:" << ok;
1420
1421 // If we added errors, then that means that the Ionizer was not valid.
1422 3584 m_isValid = (error_list_p->size() > error_count ? false : true);
1423
1424 3584 if(!m_isValid)
1425 3584 qDebug() << "Failed to validate this Ionizer, with errors:"
1426 << Utils::joinErrorList(*error_list_p);
1427
1428 3584 return m_isValid;
1429 }
1430
1431 /*!
1432 \brief Validates this Ionizer current state, setting any error message to \a
1433 error_list_p.
1434
1435 An Ionizer has a valid current state if:
1436
1437 \list
1438
1439 \li The member IsotopicData are available
1440
1441 \li The Formula validates successfully if it is not empty (that is, should
1442 contain at least one atom (for a zero-mass formula, use "+H-H", for example);
1443
1444 \endlist
1445
1446 Note that both the current state nominal charge and level may be 0 (which is
1447 logical if the analyte being managed by this Ionizer is not currently ionized).
1448
1449 If any of the tests above fail, the Ionizer is considered as
1450 invalid for the current ionization state and the validity status
1451 (m_isCurrentStateValid) value is set to false; true otherwise.
1452
1453 Returns true if validation succeeded, false otherwise.
1454
1455 \sa Formula::validate(), validate(), isValid()
1456 */
1457 bool
1458 3584 Ionizer::validateCurrentState(ErrorList *error_list_p) const
1459 {
1460 3584 m_isCurrentStateValid = true;
1461
1462
2/4
✓ Branch 0 taken 3584 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3584 times.
7168 if(mcsp_isotopicData == nullptr || mcsp_isotopicData.get() == nullptr ||
1463 3584 !mcsp_isotopicData->size())
1464 {
1465 qCritical() << "Cannot validate Ionizer without available IsotopicData.";
1466
1467 if(error_list_p != Q_NULLPTR)
1468 error_list_p->push_back(
1469 "Cannot validate Ionizer without available IsotopicData");
1470
1471 m_isCurrentStateValid = false;
1472
1473 // We really cannot go on with looking into this state.
1474 return m_isCurrentStateValid;
1475 }
1476
1477 // The current state formula might be empty if the analyte state
1478 // described the the current ionization state is not charged.
1479
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 3584 times.
3584 if(!m_currentStateFormula.getActionFormula().isEmpty())
1480 {
1481 qDebug() << "Going to validate the current state formula:"
1482 << m_currentStateFormula.getActionFormula();
1483
1484 if(!m_currentStateFormula.validate(mcsp_isotopicData, error_list_p))
1485 {
1486 QString msg = QString(
1487 "The ionizer current state formula %1 failed to "
1488 "validate successfully")
1489 .arg(m_currentStateFormula.getActionFormula());
1490
1491 qCritical() << msg;
1492
1493 if(error_list_p != Q_NULLPTR)
1494 error_list_p->push_back(msg);
1495
1496 m_isCurrentStateValid = false;
1497 }
1498 }
1499
1500
1501 // The current state nominal charge and the current state level might be
1502 // 0 because the user might want to set up the Ionizer in such a way
1503 // that the analyte is not charged at all.
1504
1505 3584 if(!m_isCurrentStateValid)
1506 {
1507 3584 if(error_list_p != Q_NULLPTR)
1508 3584 qDebug()
1509 << "Failed to validate this Ionizer current state, with errors:"
1510 << Utils::joinErrorList(*error_list_p);
1511 }
1512 else
1513 3584 qDebug() << "The current state formula was validated successfully.";
1514
1515 3584 return m_isCurrentStateValid;
1516 }
1517
1518 /*!
1519 \brief Returns the validity status of this instance: true if this Ionizer is
1520 valid, false otherwise.
1521
1522 \sa validateCurrentState(), validate()
1523 */
1524 bool
1525 1352 Ionizer::isValid() const
1526 {
1527 1352 return m_isValid;
1528 }
1529
1530 /*!
1531 \brief Returns the validity status of the current state of this Ionizer
1532 instance: true if the current state is valid, false otherwise.
1533 \sa validate()
1534 */
1535 bool
1536 Ionizer::isCurrentStateValid() const
1537 {
1538 return m_isCurrentStateValid;
1539 }
1540
1541 //////////////// UTILS /////////////////////
1542 /*!
1543 \brief Returns a string holding a textual representation of the member data.
1544
1545 If \a with_title is true, the member formulas (m_formula and
1546 m_currentStateFormula) are output with their title (if any).
1547 */
1548 QString
1549 2 Ionizer::toString(bool with_title) const
1550 {
1551 2 QString text;
1552
1553 2 text +=
1554 QString(
1555 "Ionizer destination state. formula: %1 - nominal charge: %2 - level: "
1556
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 "%3 - is valid: %4\n")
1557
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_formula.getActionFormula(with_title))
1558
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_nominalCharge)
1559
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_level)
1560
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 .arg(m_isValid);
1561
1562 2 text +=
1563 QString(
1564 "Ionizer current state. formula: %1 - nominal charge: %2 - level: %3 "
1565
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 "- charge: %4 - was valid: %5\n")
1566
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_currentStateFormula.getActionFormula(with_title))
1567
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_currentStateNominalCharge)
1568
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 .arg(m_currentStateLevel)
1569
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 .arg(currentStateCharge())
1570
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 .arg(m_isCurrentStateValid);
1571
1572 2 return text;
1573 }
1574
1575 /*!
1576 \brief Resets this instance to default values.
1577 */
1578 void
1579 Ionizer::clear()
1580 {
1581 mcsp_isotopicData.reset();
1582 m_formula.clear();
1583 m_currentStateFormula.clear();
1584 m_nominalCharge = 0;
1585 m_currentStateNominalCharge = 0;
1586 m_level = 0;
1587 m_currentStateLevel = 0;
1588 m_isValid = false;
1589 m_isCurrentStateValid = false;
1590 }
1591
1592 void
1593 Ionizer::registerJsConstructor(QJSEngine *engine)
1594
1595 {
1596 if(!engine)
1597 {
1598 qWarning() << "Cannot register Ionizer class: engine is null";
1599 return;
1600 }
1601
1602 // Register the meta object as a constructor
1603
1604 QJSValue jsMetaObject = engine->newQMetaObject(&Ionizer::staticMetaObject);
1605 engine->globalObject().setProperty("Ionizer", jsMetaObject);
1606 }
1607
1608
1609 } // namespace libXpertMassCore
1610 } // namespace MsXpS
1611