GCC Code Coverage Report


./
File: src/XpertMass/IonizeRule.cpp
Date: 2024-08-24 11:26:06
Lines:
104/131
79.4%
Functions:
16/17
94.1%
Branches:
74/162
45.7%

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 /////////////////////// Local includes
35 #include "IonizeRule.hpp"
36
37
38 namespace MsXpS
39 {
40
41 namespace libXpertMass
42 {
43
44
45 /*!
46 \class MsXpS::libXpertMass::IonizeRule
47 \inmodule libXpertMass
48 \ingroup PolChemDefBuildingdBlocks
49 \inheaderfile IonizeRule.hpp
50
51 \brief The IonizeRule class provides an ionization rule.
52
53 Ionizations are chemical reactions that bring a charge (or more
54 charges) to an analyte. The chemical reaction is described by the
55 inherited \l Formula class. The electric charge that is brought by
56 this reaction is described by a member of the class, m_charge. It
57 might happen that for polymers, ionization reactions might occur
58 more than once. This is described by one member of the class,
59 m_level.
60
61 An IonizeRule like the following, if used to ionize a molecule of Mr 1000
62
63 \list
64 \li Formula +H
65 \li Charge 1
66 \li Level 1
67 \endlist
68
69 would lead to an ion of mass 1001 and of m/z 1001.
70
71 In protein chemistry, the ionization reaction is mainly a
72 protonation reaction, which brings a single charge. Thus, the Formula would be
73 "+H" and the charge 1. In MALDI, we would have a single protonation, thus
74 level would be set to 1 by default. In electrospray ionization, more than one
75 ionization reaction occurs, and we could have a protein that is 25+, thus
76 having an ionization level of 25, for example.
77
78 An IonizeRule like the following, if used to ionize a molecule of Mr 1000
79
80 \list
81 \li Formula +H
82 \li Charge 1
83 \li Level 4
84 \endlist
85
86 would lead to an ion of mass 1004 and of m/z 251 (1004 / 4).
87
88 An IonizeRule like
89 the following, if used to ionize a molecule of Mr 1000
90
91 \list
92 \li Formula +Mg (in fact Mg2+)
93 \li Charge 2
94 \li Level 4
95 \endlist
96
97 would lead to an ion of mass 1000 + (24 * 4) and of m/z 137 = (1096 / 8).
98
99 \note Both the charge and level should have positive values, since
100 the real nature of the ionization is beared by the Formula (with, for example
101 in protein chemistry, "+H" for protonation and, in nucleic acids chemistry,
102 "-H" for deprotonation).
103
104 Thus, an IonizeRule is valid if it generates a m/z ratio after
105 ionization of the analyte that is different than the previous (M)
106 and if its Formula validates. This means that the following
107 should be true:
108
109 \list
110 \li The Formula should be valid (that is, should contain at least
111 one symbol (which might have a very small mass, like when the ionization is
112 done by electron gain or loss);
113 \li The charge is > 0 (the ionization event should bring one
114 charge, otherwise there is no ionization). To reset the ionization
115 to 0 (that is to deionize the analyte, set the level to 0);
116 \li The level is >= 0(if the level is 0, then the analyte is
117 considered not ionized);
118 \endlist
119
120 \note We do not consider compulsory that the Formula brings
121 a mass difference whatsoever, because some ionizations might not involve heavy
122 mass transfers, like electron gain or electron loss. However, the Formula must
123 validate successfully and that means thit it cannot be empty. In that case, use
124 the Nul chemical element that has not weight from the polymer chemistry
125 definitions shipped with the software package.
126 */
127
128 /*!
129 \variable int MsXpS::libXpertMass::IonizeRule::m_charge
130
131 \brief The charge that is brought to the molecule when it is ionized by the
132 IonizeRule with an ionization level (m_level) of 1.
133 */
134
135 /*!
136 \variable int MsXpS::libXpertMass::IonizeRule::m_level
137
138 \brief The number of times this IonizRule is used to ionize a molecule.
139
140 For example, applying this IonizeRule to a molecule
141
142 \list
143 \li Formula +H
144 \li Charge 1
145 \li Level 4
146 \endlist
147
148 would protonate it 4 times, with a charge of 4 and an increment in mass of the
149 mass of 4 protons.
150 */
151
152 /*!
153 \variable int MsXpS::libXpertMass::IonizeRule::m_isValid
154
155 \brief Tells if this IonizeRule is valid,
156
157 \sa isValid
158 */
159
160 /*!
161 \brief Constructs an IonizeRule initialized as an empty object.
162 */
163
1/2
✓ Branch 2 taken 597 times.
✗ Branch 3 not taken.
597 IonizeRule::IonizeRule() : Formula(), m_charge(0), m_level(0)
164 {
165 597 m_isValid = false;
166 597 }
167
168 /*!
169 \brief Constructs an IonizeRule initialized with \a formula, \a charge, \a level.
170 */
171 982 IonizeRule::IonizeRule(const Formula &formula, int charge, int level)
172 982 : Formula(formula), m_charge(charge), m_level(level)
173 {
174 982 }
175
176 /*!
177 \brief Constructs an IonizeRule as a copy of \a other.
178 */
179 580 IonizeRule::IonizeRule(const IonizeRule &other)
180 : Formula(other),
181 580 m_charge(other.m_charge),
182 580 m_level(other.m_level),
183 580 m_isValid(other.m_isValid)
184 {
185 580 }
186
187 /*!
188 \brief Assigns \a other to this IonizeRule.
189 Returns a reference to this ionization rule.
190 */
191 IonizeRule &
192 20 IonizeRule::operator=(const IonizeRule &other)
193 {
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if(&other == this)
195 return *this;
196
197 20 Formula::operator=(other);
198
199 20 m_charge = other.m_charge;
200 20 m_level = other.m_level;
201
202 20 m_isValid = other.m_isValid;
203
204 20 return *this;
205 }
206
207 /*!
208 \brief Returns true if this IonizeRule is identical to \a other, false
209 otherwise.
210 */
211 bool
212 68 IonizeRule::operator==(const IonizeRule &other) const
213 {
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if(&other == this)
215 return true;
216
217
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if(Formula::operator!=(other))
218 return false;
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if(m_charge != other.m_charge)
220 return false;
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if(m_level != other.m_level)
222 return false;
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if(m_isValid != other.m_isValid)
224 return false;
225
226 68 return true;
227 }
228
229 /*!
230 \brief Returns true if this IonizeRule is different than \a other, false
231 otherwise.
232
233 Returns the negated result of operator==().
234 */
235 bool
236 20 IonizeRule::operator!=(const IonizeRule &other) const
237 {
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if(&other == this)
239 return false;
240
241 20 return !operator==(other);
242 }
243
244 /*!
245 \brief Sets the charge to \a value.
246
247 An IonizeRule with a(charge <= 0) is invalid by definition. If so, the
248 m_isValid member is set to false.
249 */
250 void
251 44 IonizeRule::setCharge(int value)
252 {
253 44 m_charge = value;
254
255
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if(m_charge <= 0)
256 m_isValid = false;
257 44 }
258
259 /*!
260 \brief Returns the charge.
261 */
262 int
263 148 IonizeRule::charge() const
264 {
265 148 return m_charge;
266 }
267
268
269 /*!
270 \brief Sets the ionzation level to \a value.
271
272 An IonizeRule might have an ionization level == 0 but not level
273 < 0, as this means that the analyte is not ionized at all. If
274 (level < 0), then the m_isValid member is set to false.
275 */
276 void
277 48 IonizeRule::setLevel(int value)
278 {
279 48 m_level = value;
280
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if(m_level < 0)
282 m_isValid = false;
283 48 }
284
285
286 /*!
287 \brief Returns the ionization level.
288 */
289 int
290 232 IonizeRule::level() const
291 {
292 232 return m_level;
293 }
294
295 /*!
296 \brief Returns the formula.
297 */
298 QString
299 84 IonizeRule::formula() const
300 {
301 84 return Formula::toString();
302 }
303 /*!
304 \brief Validates this IonizeRule against the \a isotopic_data_csp.
305
306 An IonizeRule is valid if it generates a new m/z ratio after
307 ionization (or deionization if (\c m_level == 0) of the analyte
308 that is different than the previous one and if its \l Formula
309 validates successfully. This means that the following should be true:
310
311 \list
312 \li The Formula should be valid (that is, should contain at least
313 one atom (use a 0-weighing atom if necessary, like Nul from the
314 polymer chemistry definitions shipped with the software);
315
316 \li The charge should > 0;
317
318 \li The level should >= 0;
319 \endlist
320
321 If these three tests do not fail, the IonizeRule is considered
322 valid and the m_isValid boolean value is set to true; false
323 otherwise.
324
325 Returns true if validation succeeds, false otherwise.
326
327 \sa Formula::validate()
328 */
329 bool
330 440 IonizeRule::validate(IsotopicDataCstSPtr isotopic_data_csp)
331 {
332
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 440 times.
440 if(isotopic_data_csp == nullptr)
333 qFatal("Programming error. The pointer cannot be nullptr.");
334
335 440 int tests = 0;
336
337
1/2
✓ Branch 2 taken 440 times.
✗ Branch 3 not taken.
440 tests += Formula::validate(isotopic_data_csp);
338 440 tests += (m_charge > 0);
339 440 tests += (m_level >= 0);
340
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 440 times.
440 if(tests < 3)
342 {
343 m_isValid = false;
344
345 return false;
346 }
347
348 440 m_isValid = true;
349
350 440 return true;
351 }
352
353
354 /*!
355 \brief Returns true if this IonizeRule is valid, false otherwise.
356 \sa validate()
357 */
358 bool
359 160 IonizeRule::isValid() const
360 {
361 160 return m_isValid;
362 }
363
364
365 /*!
366 \brief Renders the ionization rule XML \a element.
367
368 The XML element is parsed and the data extracted from the XML data
369 are set to this IonizeRule instance.
370
371 The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>
372
373 A typical ionization rule element looks like this:
374
375 \code
376 <ionizerule>
377 <formula>+H</formula>
378 <charge>1</charge>
379 <level>1</level>
380 </ionizerule>
381 \endcode
382
383 Note that this IonizeRule is not valid, as it has not been
384 validated by calling validate(). The caller is reponsible for
385 checking the validity of the IonizeRule prior use.
386
387 Returns true if the parsing is successful, false otherwise.
388
389 \sa formatXmlIonizeRuleElement(int offset, const QString &indent)
390 */
391 bool
392 392 IonizeRule::renderXmlIonizeRuleElement(const QDomElement &element)
393 {
394
1/2
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
392 QDomElement child;
395
396 // <ionizerule>
397 // <formula>+H</formula>
398 // <charge>1</charge>
399 // <level>1</level>
400 // </ionizerule>
401
402
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 392 times.
392 if(element.tagName() != "ionizerule")
403 return false;
404
405 // <formula>
406
2/4
✓ Branch 3 taken 392 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 392 times.
✗ Branch 7 not taken.
392 child = element.firstChildElement();
407
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 392 times.
392 if(child.tagName() != "formula")
408 return false;
409
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 392 times.
392 if(!renderXmlFormulaElement(child))
410 return false;
411
412 // <charge>
413
2/4
✓ Branch 3 taken 392 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 392 times.
✗ Branch 7 not taken.
392 child = child.nextSiblingElement();
414
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 392 times.
392 if(child.tagName() != "charge")
415 return false;
416 392 bool ok = false;
417
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 392 times.
✗ Branch 5 not taken.
392 m_charge = child.text().toInt(&ok);
418
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
392 if(!m_charge && !ok)
419 return false;
420
421 // <level>
422
2/4
✓ Branch 3 taken 392 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 392 times.
✗ Branch 7 not taken.
392 child = child.nextSiblingElement();
423
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 392 times.
392 if(child.tagName() != "level")
424 return false;
425 392 ok = false;
426
2/4
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 392 times.
✗ Branch 5 not taken.
392 m_level = child.text().toInt(&ok);
427
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
392 if(!m_level && !ok)
428 return false;
429
430 // We have not validated this IonizeRule, as we should have the
431 // reference list of atoms to do that. The caller is responsible
432 // for the validate() call.
433 392 m_isValid = false;
434
435 392 return true;
436 392 }
437
438
439 /*!
440 \brief Formats a string suitable to use as an XML element.
441
442 Formats a string suitable to be used as an XML element in a
443 polymer chemistry definition file. The typical ionization rule
444 element that is generated in this function looks like this:
445
446 The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>
447
448 A typical ionization rule element looks like this:
449
450 \code
451
452 <ionizerule>
453 ~~<formula>+H</formula>
454 ~~<charge>1</charge>
455 ~~<level>1</level>
456 </ionizerule>
457
458 \endcode
459
460 \a offset times the \a indent string must be used as a lead in the
461 formatting of elements.
462
463 Returns a dynamically allocated string that needs to be freed after use.
464
465 \sa renderXmlIonizeRuleElement(const QDomElement &element)
466 */
467 QString *
468 8 IonizeRule::formatXmlIonizeRuleElement(int offset, const QString &indent)
469 {
470 int newOffset;
471 8 int iter = 0;
472
473
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 QString lead("");
474
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 QString *string = new QString();
475
476
477 // Prepare the lead.
478 8 newOffset = offset;
479
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 while(iter < newOffset)
480 {
481
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 lead += indent;
482 8 ++iter;
483 }
484
485 /* We are willing to create an <ionizerule> node that should look like this:
486 *
487 *<ionizerule>
488 * <formula>+H</formula>
489 * <charge>1</charge>
490 * <level>1</level>
491 *</ionizerule>
492 *
493 */
494
495
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
16 *string += QString("%1<ionizerule>\n").arg(lead);
496
497 // Prepare the lead.
498 8 ++newOffset;
499 8 lead.clear();
500 8 iter = 0;
501
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
24 while(iter < newOffset)
502 {
503
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 lead += indent;
504 16 ++iter;
505 }
506
507 // Continue with indented elements.
508
509
5/10
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 8 times.
✗ Branch 14 not taken.
24 *string += QString("%1<formula>%2</formula>\n").arg(lead).arg(formula());
510
511
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
24 *string += QString("%1<charge>%2</charge>\n").arg(lead).arg(m_charge);
512
513
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
24 *string += QString("%1<level>%2</level>\n").arg(lead).arg(m_level);
514
515 // Prepare the lead for the closing element.
516 8 --newOffset;
517 8 lead.clear();
518 8 iter = 0;
519
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 while(iter < newOffset)
520 {
521
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 lead += indent;
522 8 ++iter;
523 }
524
525
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
16 *string += QString("%1</ionizerule>\n").arg(lead);
526
527 8 return string;
528 8 }
529
530 /*!
531 \brief Returns a string holding a textual representation of the member data.
532 */
533 QString
534 8 IonizeRule::toString() const
535 {
536 8 QString text;
537
538
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
16 text += QString("Formula: %1").arg(Formula::toString());
539
4/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
24 text += QString(" - charge: %1 -- level: %2").arg(m_charge).arg(m_level);
540
541 8 return text;
542 }
543
544 /*
545 \brief Outputs a string holding a textual representation of the member data
546 using
547 qDebug().
548 */
549 void
550 IonizeRule::debugPutStdErr()
551 {
552 qDebug() << __FILE__ << __LINE__
553 << QString("Ionizerule: charge=%1; level=%2; formula=%3")
554 .arg(m_charge)
555 .arg(m_level)
556 .arg(m_formula);
557 }
558
559 } // namespace libXpertMass
560
561 } // namespace MsXpS
562