GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Modif.cpp
Date: 2025-11-20 01:41:33
Lines:
394/430
91.6%
Functions:
34/35
97.1%
Branches:
384/692
55.5%

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 <QString>
39 #include <QDebug>
40
41
42 /////////////////////// Local includes
43 #include "MsXpS/libXpertMassCore/globals.hpp"
44 #include "MsXpS/libXpertMassCore/Formula.hpp"
45 #include "MsXpS/libXpertMassCore/Utils.hpp"
46 #include "MsXpS/libXpertMassCore/IsotopicData.hpp"
47 #include "MsXpS/libXpertMassCore/Modif.hpp"
48 #include "MsXpS/libXpertMassCore/Monomer.hpp"
49
50
51 int modifMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Modif>(
52 "MsXpS::libXpertMassCore::Modif");
53
54 int modifPtrMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Modif *>(
55 "MsXpS::libXpertMassCore::ModifPtr");
56
57
58 namespace MsXpS
59 {
60 namespace libXpertMassCore
61 {
62
63
64 /*!
65 \class MsXpS::libXpertMassCore::Modif
66 \inmodule libXpertMassCore
67 \ingroup PolChemDefBuildingdBlocks
68 \inheaderfile Modif.hpp
69
70 \brief The Modif class provides abstractions to work with chemical
71 modifications.
72
73 The Modif class provides a chemical modification that can be set to any monomer
74 in a polymer sequence or to any one of the polymer sequence ends. In the protein
75 world, chemical modifications of proteins that occur in the living cell are
76 called post-translational modifications.This class aims at modelling, among
77 others, such modifications.
78
79 The chemical reaction described by the Modif class is encoded as an
80 action-formula (see \l{Formula}).
81 */
82
83 /*!
84 \variable MsXpS::libXpertMassCore::Modif::mcsp_polChemDef
85
86 \brief The \l PolChemDef polymer chemistry definition.
87
88 This member allows to root the Modif instance in a chemical context where
89 IsotopicData can be used to calculate masses starting from a Formula, but also
90 where the Monomer instances can be checked for existence when validating
91 the Modif targets, for example.
92 */
93
94 /*!
95 \variable MsXpS::libXpertMassCore::Modif::m_name
96
97 \brief Name of the chemical modification, like "Acetylation".
98 */
99
100 /*!
101 \variable MsXpS::libXpertMassCore::Modif::m_formula
102
103 \brief \l Formula string representing the chemical modification.
104 */
105
106 /*!
107 \variable MsXpS::libXpertMassCore::Modif::m_targets
108
109 \brief String that holds a list of all the target monomers of this
110 modification.
111
112 If there are more than one target, the targets (Monomer codes)
113 must be separated by ';' characters.
114
115 If any monomer in the polymer chemistry definition might be modified by this
116 Modif object, then, the "*" string can be used to indicate so.
117 */
118
119 /*!
120 \variable MsXpS::libXpertMassCore::Modif::m_maxCount
121
122 \brief Value indicating the maximum number of times this modification
123 can be set to a target entity (monomer or polymer).
124
125 The value cannot be less than one, otherwise the Modif is set to an invalid
126 state.
127
128 This member is designed to prohibit modifying more than chemically possible a
129 given Monomer. For example it is not possible to phosphorylate a Seryl residue
130 more than once.
131 */
132
133 /*!
134 \variable MsXpS::libXpertMassCore::Modif::m_mono
135
136 \brief Value representing the monoisotopic mass of the formula.
137 */
138
139 /*!
140 \variable MsXpS::libXpertMassCore::Modif::m_avg
141
142 \brief Value representing the average mass of the formula.
143 */
144
145 /*!
146 \variable MsXpS::libXpertMassCore::Modif::m_isValid
147
148 \brief Validity status of the Modif instance.
149 */
150
151 /*!
152 \brief Constructs a modification starting from an XML <mdf> \a element according
153 to \a version and using the reference polymer chemistry definition \a
154 pol_chem_def_csp.
155
156 The \a version indicates what version of the XML element is to be used.
157
158 This is the current format:
159 \code
160 <mdf>
161 <name>Acetylation</name>
162 <formula>C2H2O1</formula>
163 <targets>;K;</targets>
164 <maxcount>1</maxcount>
165 </mdf>
166 <mdf>
167 <name>AmidationAsp</name>
168 <formula>H1N1-O1</formula>
169 <targets>;D;</targets>
170 <maxcount>1</maxcount>
171 </mdf>
172 \endcode
173
174 The XML element is rendered using the dedicated function renderXmlMdfElement().
175
176 \sa renderXmlMdfElement()
177 */
178 1831 Modif::Modif(PolChemDefCstSPtr pol_chem_def_csp,
179 const QDomElement &element,
180 1831 [[maybe_unused]] int version)
181
3/6
✓ Branch 3 taken 1831 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1831 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 1831 times.
✗ Branch 10 not taken.
1831 : mcsp_polChemDef(pol_chem_def_csp)
182 {
183
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1830 times.
1831 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
184
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 qCritical() << "Constructing Modif with no PolChemDef.";
185
186
3/4
✓ Branch 1 taken 1831 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 1821 times.
1831 if(!renderXmlMdfElement(element, version))
187 {
188
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
20 qCritical() << "Failed to fully render or validate the Modif XML element "
189
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 "for construction of Modif instance.";
190 }
191
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1831 }
192
193 /*!
194 \brief Constructs a modification.
195
196 A Modif instance cannot be of any use if it is not associated logically to a
197 polymer chemistry definition (\a pol_chem_def_csp). The Modif instance is
198 defined by its \a modif_name and its \a formula_string as as string (that
199 defaults to empty in this constructor).
200
201 Being able to construct a Modif without a formula string is necessary when Modif
202 objects are intialized piecemeal upon reading XML elements that describe the
203 Modif.
204
205 The \a formula_string might be a simple formula ("O", for an oxidation) or an
206 action-formula, if that is desirable to best characterize the modification
207 ("-H20+CH3COOH", for example, for an acetylation). The formula string can also
208 have a title, like:
209
210 \c{"Acetylation"-H20+CH3COOH}.
211
212 The member string datum representing the allowed targets of this Modif
213 instanceis set to "all" (that is, \c{"*"}) and the maximum count that this
214 modification can bet set to a given target is set to \c{1} by default, like a
215 Seryl residue can only be phosphorylated once, for example.
216
217 After setting the member data and if the polymer chemistry definition is
218 available, \l validate() is called, the masses are calculated and if all went
219 without error the member \l m_isValid validity status is set to true, otherwise
220 it is set to false.
221 */
222 276 Modif::Modif(PolChemDefCstSPtr pol_chem_def_csp,
223 const QString &modif_name,
224 const QString &formula_string,
225 double mono,
226 276 double avg)
227 552 : mcsp_polChemDef(pol_chem_def_csp),
228
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 200 times.
276 m_name(modif_name),
229
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 200 times.
276 m_formula(formula_string),
230 276 m_mono(mono),
231
1/2
✓ Branch 3 taken 276 times.
✗ Branch 4 not taken.
552 m_avg(avg)
232 {
233 276 ErrorList error_list;
234
1/2
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
276 m_isValid = validate(&error_list);
235
236
1/2
✓ Branch 0 taken 276 times.
✗ Branch 1 not taken.
276 if(!m_isValid)
237 {
238
2/4
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 276 times.
✗ Branch 5 not taken.
552 qCritical() << "Construction of Modif with validation errors:\n"
239
3/6
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 276 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 276 times.
✗ Branch 8 not taken.
552 << Utils::joinErrorList(error_list, ", ");
240 }
241
242 // Validation tries to compute masses but does not update member data.
243 // Here we want to update the member data.
244
4/6
✓ Branch 1 taken 276 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 276 times.
✓ Branch 5 taken 204 times.
✓ Branch 6 taken 72 times.
276 if(!calculateMasses(nullptr))
245 {
246 204 m_isValid = false;
247
1/2
✓ Branch 1 taken 204 times.
✗ Branch 2 not taken.
408 qCritical() << "Construction of Modif with masses that failed to be "
248
1/2
✓ Branch 1 taken 204 times.
✗ Branch 2 not taken.
204 "calculated.";
249 }
250
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
276 }
251
252 /*!
253 \brief Constructs a Modif object as a copy of \a other.
254
255 After setting the member data and if the polymer chemistry definition is
256 available, \l validate() is called, the masses are calculated and if all went
257 without error the member \l m_isValid validity status is set to true, otherwise
258 it is set to false.
259 */
260 261 Modif::Modif(const Modif &other)
261 : PropListHolder(other),
262 522 mcsp_polChemDef(other.mcsp_polChemDef),
263
1/2
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
261 m_name(other.m_name),
264
1/2
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
261 m_formula(other.m_formula),
265
1/2
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
261 m_targets(other.m_targets),
266 261 m_maxCount(other.m_maxCount),
267 261 m_mono(other.m_mono),
268 261 m_avg(other.m_avg)
269 {
270 261 ErrorList error_list;
271
1/2
✓ Branch 1 taken 261 times.
✗ Branch 2 not taken.
261 m_isValid = validate(&error_list);
272
273
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 257 times.
261 if(!m_isValid)
274 {
275
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Construction of Modif with validation errors:\n"
276
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
8 << Utils::joinErrorList(error_list, ", ");
277 }
278
279 // Validation tries to compute masses but does not update member data.
280 // Here we want to update the member data.
281
4/6
✓ Branch 1 taken 261 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 261 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 257 times.
261 if(!calculateMasses(nullptr))
282 {
283 4 m_isValid = false;
284
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 qCritical() << "Construction of Modif with masses that failed to be "
285
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 "calculated.";
286 }
287
0/2
✗ Branch 3 not taken.
✗ Branch 4 not taken.
261 }
288
289 /*!
290 \brief Destructs this Modif.
291 */
292 1208 Modif::~Modif()
293 {
294
2/2
✓ Branch 3 taken 561 times.
✓ Branch 4 taken 5 times.
2330 }
295
296
297 /*!
298 \brief Sets the polymer chemistry definition to \a pol_chem_def_csp.
299
300 After setting the member data, \l validate() is called and the \l m_isValid
301 member is set to the result of the validation.
302 */
303 void
304 203 Modif::setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp)
305 {
306 203 mcsp_polChemDef = pol_chem_def_csp;
307
308 203 ErrorList error_list;
309
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 m_isValid = validate(&error_list);
310
311
1/2
✓ Branch 0 taken 203 times.
✗ Branch 1 not taken.
203 if(!m_isValid)
312
2/4
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 203 times.
✗ Branch 5 not taken.
406 qCritical() << "Failed to validate the Modif with errors:\n"
313
3/6
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 203 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 203 times.
✗ Branch 8 not taken.
406 << Utils::joinErrorList(error_list, ", ");
314 203 }
315
316 /*!
317 \brief Returns the polymer chemistry definition.
318 */
319 const PolChemDefCstSPtr &
320 781 Modif::getPolChemDefCstSPtr() const
321 {
322 781 return mcsp_polChemDef;
323 }
324
325 //////////////// THE NAME /////////////////////
326
327 /*!
328 \brief Sets the \a name.
329
330 After setting the member data, \l validate() is called and the \l m_isValid
331 member is set to the result of the validation.
332 */
333 void
334 10 Modif::setName(const QString &name)
335 {
336 10 m_name = name;
337
338 10 ErrorList error_list;
339
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 m_isValid = validate(&error_list);
340
341
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 if(!m_isValid)
342
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
18 qCritical() << "Failed to validate the Modif with errors:\n"
343
3/6
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 9 times.
✗ Branch 8 not taken.
18 << Utils::joinErrorList(error_list, ", ");
344 10 }
345
346 /*!
347 \brief Returns the name.
348 */
349 QString
350 5532 Modif::getName() const
351 {
352
2/2
✓ Branch 0 taken 5529 times.
✓ Branch 1 taken 3 times.
5532 return m_name;
353 }
354
355 /*!
356 \brief Sets the formula to \a formula_string.
357
358 After setting the member data and if the polymer chemistry definition is
359 available, \l validate() is called, the masses are calculated and if all went
360 without error the member \l m_isValid validity status is set to true, otherwise
361 it is set to false.
362 */
363 void
364 6 Modif::setFormula(const QString &formula_string)
365 {
366 6 m_formula = formula_string;
367
368 6 ErrorList error_list;
369
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_isValid = validate(&error_list);
370
371
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if(!m_isValid)
372 {
373
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
12 qCritical() << "Failed to validate the Modif with errors:\n"
374
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
12 << Utils::joinErrorList(error_list, ", ");
375 }
376
377 // Validation tries to compute masses but does not update member data.
378 // Here we want to update the member data.
379
4/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 5 times.
6 if(!calculateMasses(nullptr))
380 {
381 1 m_isValid = false;
382
383
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical()
384
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "Failed to calculate the masses after setting formula to Modif.";
385 }
386 6 }
387
388 /*
389 \brief Returns the formula describing this modification.
390 */
391 const QString &
392 78 Modif::getFormula() const
393 {
394 78 return m_formula;
395 }
396
397 /*
398 \brief Returns a copy of the member formula describing this modification.
399
400 If \a with_title is true, the title of the formula prepended to the formula
401 string.
402
403 For example, with title, formula would be \c{"Acetylation"-H2O+CH3COOH}.
404 */
405 QString
406 35 Modif::formula(bool with_title) const
407 {
408 35 Formula temp_formula(m_formula);
409
410
1/2
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
70 return temp_formula.getActionFormula(with_title);
411 35 }
412
413 /*!
414 \brief Sets the \a targets for this modification.
415
416 Setting the targets of a Modif instance means specifying which target (Monomer
417 code) might be modified using this modification. For example, for \c
418 Phosphorylation, in protein chemistry, one would define targets as \c Serine, \c
419 Threonine, \c Tyrosine (there are other targets, like Histidine, but very rarely
420 encountered).
421
422 Multiple targets are separated using ';'.
423
424 The \a targets are validated (\l Modif::validateTargets()) and the obtained
425 string is set to m_targets. If the validation fails, m_targets is set to
426 QString().
427
428 After setting the member data, \l validate() is called and the \l m_isValid
429 member is set to the result of the validation.
430 */
431 void
432 80 Modif::setTargets(const QString &targets)
433 {
434 80 qDebug() << "Validating targets:" << targets;
435
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 QString local_targets_string = targets;
436
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 local_targets_string = Utils::unspacify(local_targets_string);
437
438 80 qDebug() << "After unspacification" << local_targets_string;
439
440 80 bool ok = false;
441
442 // Validate without simplification (false).
443 80 local_targets_string =
444
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 validateTargets(local_targets_string, /* simplification */ false, ok);
445
446
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 75 times.
80 if(!ok)
447 {
448
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
10 qCritical() << "The validation of the Modif targets failed:"
449
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 << local_targets_string;
450 5 m_targets = QString();
451 }
452 else
453 75 m_targets = local_targets_string;
454
455 80 ErrorList error_list;
456
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 m_isValid = validate(&error_list);
457
458
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 2 times.
80 if(!m_isValid)
459
2/4
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 78 times.
✗ Branch 5 not taken.
156 qCritical() << "Failed to validate the Modif with errors:"
460
3/6
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 78 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 78 times.
✗ Branch 8 not taken.
156 << Utils::joinErrorList(error_list, ", ");
461 80 }
462
463 /*!
464 \brief Returns the tagets of this modification.
465 */
466 QString
467 12 Modif::getTargets() const
468 {
469
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 return m_targets;
470 }
471
472 /*!
473 \brief Returns true if Mnomer \a code is found among the targets of this
474 Modif instance, false otherwise.
475 */
476 bool
477 477 Modif::doesTargetMonomer(const QString &code) const
478 {
479
2/2
✓ Branch 1 taken 422 times.
✓ Branch 2 taken 55 times.
477 if(m_targets == "*")
480 return true;
481
482
2/2
✓ Branch 1 taken 419 times.
✓ Branch 2 taken 3 times.
422 if(m_targets == "!")
483 return false;
484
485
1/2
✓ Branch 2 taken 419 times.
✗ Branch 3 not taken.
419 QString delimitedCode = QString(";%1;").arg(code);
486
487 // The m_targets string is in the form ";code;code;code;".
488
489 // qDebug() << "The targets:" << m_targets;
490
491
1/2
✓ Branch 1 taken 419 times.
✗ Branch 2 not taken.
419 return m_targets.contains(delimitedCode, Qt::CaseSensitive);
492 419 }
493
494 /*!
495 \brief Sets the maximum count (times the modification event is allowed to occur
496 on a target) that this modification might be set to a target to \a value.
497
498 For Phosphorylation, for example, that would be \c{1} count exclusively for
499 modification of Seryl, Threonyl and Tyrosinyl residues.
500
501 After setting the member data, \l validate() is called and the \l m_isValid
502 member is set to the result of the validation.
503 */
504 void
505 73 Modif::setMaxCount(int value)
506 {
507 73 m_maxCount = value;
508
509 73 ErrorList error_list;
510
1/2
✓ Branch 1 taken 73 times.
✗ Branch 2 not taken.
73 m_isValid = validate(&error_list);
511
512
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 67 times.
73 if(!m_isValid)
513
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
12 qCritical() << "Failed to validate the Modif with errors:"
514
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
12 << Utils::joinErrorList(error_list, ", ");
515 73 }
516
517 /*!
518 \brief Returns the maximum count of times that this modification might be
519 set to a target.
520 */
521 int
522 146 Modif::getMaxCount() const
523 {
524 146 return m_maxCount;
525 }
526
527 //////////////// OPERATORS /////////////////////
528 /*!
529 \brief Assigns \a other to this modification.
530
531 Returns a reference to this modification.
532
533 After setting the member data, \l validate() is called and the \l m_isValid
534 member is set to the result of the validation.
535 */
536 Modif &
537 10 Modif::operator=(const Modif &other)
538 {
539
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 if(&other == this)
540 return *this;
541
542 9 mcsp_polChemDef = other.mcsp_polChemDef;
543 9 m_name = other.m_name;
544 9 m_formula = other.m_formula;
545 9 m_targets = other.m_targets;
546 9 m_maxCount = other.m_maxCount;
547 9 m_mono = other.m_mono;
548 9 m_avg = other.m_avg;
549
550 9 PropListHolder::operator=(other);
551
552 9 ErrorList error_list;
553
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 m_isValid = validate(&error_list);
554
555
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if(!m_isValid)
556 {
557 qCritical() << "Assignment of Modif with validation errors:\n"
558 << Utils::joinErrorList(error_list, ", ");
559 }
560
561 9 return *this;
562 9 }
563
564
565 /*!
566 \brief Returns true if this and the \a other modifications are identical,
567 false otherwise.
568 */
569 bool
570 59 Modif::operator==(const Modif &other) const
571 {
572
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 8 times.
59 if(&other == this)
573 return true;
574
575 // We cannot compare the PolChemDef, because that would cause
576 // an infinite loop: (each instance of this class in the PolChemDef would
577 // try to compare the PolChemDef...).
578
579
4/6
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
51 return m_name == other.m_name && m_formula == other.m_formula &&
580
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 m_targets == other.m_targets && m_maxCount == other.m_maxCount &&
581
4/6
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
100 m_mono == other.m_mono && m_avg == other.m_avg;
582 }
583
584 /*!
585 \brief Returns true if this and the \a other modifications differ,
586 false otherwise.
587
588 Returns the negated result of operator==().
589 */
590 bool
591 53 Modif::operator!=(const Modif &other) const
592 {
593
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 16 times.
53 if(&other == this)
594 return false;
595
596 37 return !operator==(other);
597 }
598
599 //////////////// VALIDATIONS /////////////////////
600 /*!
601 \brief Returns the Modif instance from the polymer chemistry definition
602 registered in this instance.
603
604 The key to search the Modif is this instance's member name.
605
606 If there is no PolChemDef available, nullptr is returned.
607
608 If no Modif instance is found by this instance's name, nullptr is returned.
609 */
610 ModifCstSPtr
611 1 Modif::getFromPolChemDefByName() const
612 {
613
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
614 return nullptr;
615
616
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(m_name.isEmpty())
617 return nullptr;
618
619 1 return mcsp_polChemDef->getModifCstSPtrByName(m_name);
620 }
621
622 /*!
623 \brief Returns the status of this Modif instance the polymer chemistry
624 definition registered in this instance.
625
626 The key to search the Modif is this instance's member name.
627
628 If there is no PolChemDef available,
629 Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE is returned.
630
631 If no Modif instance is found by this instance's name,
632 Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN is returned, otherwise
633 Enums::PolChemDefEntityStatus::ENTITY_KNOWN is returned.
634 */
635 Enums::PolChemDefEntityStatus
636 3 Modif::isKnownByNameInPolChemDef() const
637 {
638
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
639 return Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE;
640
641
4/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
3 if(mcsp_polChemDef->getModifCstSPtrByName(m_name) != nullptr)
642 1 return Enums::PolChemDefEntityStatus::ENTITY_KNOWN;
643
644 return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
645 };
646
647 /*!
648 \brief Validates the \a targets_string target and returns the processed
649 targets string.
650
651 If \a targets_string is non-empty, it is validated. Otherwise, \a ok is set
652 to false and \a targets_string is returned unchanged.
653
654 If \a simplify is true, then, whenever '*' or '!' is encountered in \a
655 targets_string, then \a ok is set to true and either '*' or '!' is returned.
656 Indeed, the target list is split using ';' as a delimiter. If '*' is found
657 and \a simplify is true, the function returns '*' immediately (as all the
658 monomers in the polymer chemistry definition might be a target of this
659 modification) and \a ok is set to true. Likewise, if '!' is found and \a
660 simplify is true, the function returns '!' immediately (as none of all the
661 monomers in the polymer chemistry definition might be a target of this
662 modification) and \a ok is set to true.
663
664 If at least one target is found, then each target is a monomer
665 code and that code is looked for in the member polymer chemistry definition
666 list of monomers. If the code is found, that code is added to a temporary
667 string. If the code is not found, \a ok is set to false and \a targets_string
668 is returned unchanged.
669
670 When the process completes, without error, \a ok is set to true and the
671 string corresponding to the processed monomer codes in the \a targets_string
672 is returned. Otherwise, \a ok is set to false and an empty string is
673 returned.
674 */
675 QString
676 5506 Modif::validateTargets(const QString &targets_string,
677 bool simplify,
678 bool &ok) const
679 {
680
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5505 times.
5506 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
681 {
682 2 qCritical()
683
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "Cannot validate the targets because no PolChemDef is defined.";
684 1 m_isValid = false;
685 1 return QString();
686 }
687
688
2/2
✓ Branch 0 taken 5201 times.
✓ Branch 1 taken 304 times.
5505 QString local_targets_string = targets_string;
689
690 // If the local_targets_string is empty, this is an error, because we cannot
691 // know what's to be done with the modification.
692
2/2
✓ Branch 0 taken 304 times.
✓ Branch 1 taken 5201 times.
5505 if(local_targets_string.isEmpty())
693 {
694 304 ok = false;
695 304 return QString();
696 }
697
698 // A targets string cannot contain both a '*' and a '!'
699 // character. We check that immediately.
700
4/4
✓ Branch 0 taken 895 times.
✓ Branch 1 taken 4306 times.
✓ Branch 2 taken 893 times.
✓ Branch 3 taken 2 times.
5201 if(local_targets_string.contains('*') && local_targets_string.contains('!'))
701 {
702
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 qWarning()
703
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 << "A modification targets string cannot contain both '*' and '!'.";
704
705 2 ok = false;
706 2 return QString();
707 }
708
709 5199 QString processed_targets_string;
710
711 // A targets string looks like "Ser ; Thr ; Tyr".
712
1/2
✓ Branch 1 taken 5199 times.
✗ Branch 2 not taken.
5199 QStringList code_string_list =
713
1/2
✓ Branch 1 taken 5199 times.
✗ Branch 2 not taken.
5199 local_targets_string.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive);
714
715
2/2
✓ Branch 0 taken 8530 times.
✓ Branch 1 taken 5191 times.
13721 for(int iter = 0; iter < code_string_list.size(); ++iter)
716 {
717
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 QString iter_code_string = code_string_list.at(iter);
718
719 // There are two character that might be encountered: '*' is the
720 // equivalent of "all the monomers in the definition"; '!' is
721 // equivalent to "none of the monomers in the definition". But
722 // it is not possible that both * and ! be present in the same
723 // targets string.
724
725
4/4
✓ Branch 1 taken 7637 times.
✓ Branch 2 taken 893 times.
✓ Branch 4 taken 7635 times.
✓ Branch 5 taken 2 times.
8530 if(iter_code_string == "*" || iter_code_string == "!")
726 {
727 // Simplification asked: if '*' is found then it can be
728 // there alone. Same for '!'. '*' means that any monomer in
729 // the definition might be modified with this modification,
730 // '!' means that none of the monomers might be modified.
731
732
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 895 times.
895 if(simplify)
733 {
734 // qDebug() << "Simplification asked for.";
735 processed_targets_string = iter_code_string;
736
737 break;
738 }
739 else
740 {
741
1/2
✓ Branch 1 taken 895 times.
✗ Branch 2 not taken.
895 processed_targets_string.append(
742
2/4
✓ Branch 1 taken 895 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 895 times.
✗ Branch 5 not taken.
1790 QString("%1;").arg(iter_code_string));
743 }
744
745 895 continue;
746 }
747
748 // At this point, we have something to check as a monomer code:
749
750
5/6
✓ Branch 1 taken 7635 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7627 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 7627 times.
✓ Branch 6 taken 8 times.
15262 if(mcsp_polChemDef->getMonomerCstSPtrByCode(iter_code_string) != nullptr)
751 {
752 // Want the string to be ;code;code;code;(notice the first
753 // and last ';'), so that we can later ask easily if ;Lys;
754 // is found in the targets string, without risking to also
755 // match ;Ly;.
756
2/2
✓ Branch 0 taken 4300 times.
✓ Branch 1 taken 3327 times.
7627 if(!processed_targets_string.size())
757
1/2
✓ Branch 1 taken 4300 times.
✗ Branch 2 not taken.
4300 processed_targets_string.append(
758
2/4
✓ Branch 1 taken 4300 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4300 times.
✗ Branch 5 not taken.
8600 QString(";%1;").arg(iter_code_string));
759 else
760
1/2
✓ Branch 1 taken 3327 times.
✗ Branch 2 not taken.
3327 processed_targets_string.append(
761
2/4
✓ Branch 1 taken 3327 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3327 times.
✗ Branch 5 not taken.
6654 QString("%1;").arg(iter_code_string));
762 }
763 else
764 {
765 8 qDebug() << "Monomer code is not known:" << iter_code_string;
766 8 ok = false;
767 8 return QString();
768 7627 ;
769 }
770 8530 }
771 // End of
772 // for(int iter = 0; iter < code_string_list.size(); ++iter)
773
774 // qDebug() << "processed_targets_string:" << processed_targets_string;
775
776
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5191 times.
5191 if(processed_targets_string.isEmpty())
777 {
778 qDebug()
779 << "After the validation process, the targets string results empty.";
780 ok = false;
781 return QString();
782 5191 ;
783 }
784
785 // Most evident fix to the loop output above
786
2/2
✓ Branch 1 taken 893 times.
✓ Branch 2 taken 4298 times.
5191 if(processed_targets_string == "*;")
787
1/2
✓ Branch 1 taken 893 times.
✗ Branch 2 not taken.
893 processed_targets_string = "*";
788
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 5189 times.
5191 if(processed_targets_string == "!;")
789
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 processed_targets_string = "!";
790
791 5191 ok = true;
792 5191 return processed_targets_string;
793 10704 }
794
795 /*!
796 \brief Validates the member targets and returns the processed
797 targets string.
798
799 If \a simplify is true, then, whenever '*' or '!' is encountered in the member
800 targets string, then \a ok is set to true and either '*' or '!' is returned.
801 Indeed, the target list is split using ';' as a delimiter. If '*' is found
802 and \a simplify is true, the function returns '*' immediately (as all the
803 monomers in the polymer chemistry definition might be a target of this
804 modification) and \a ok is set to true. Likewise, if '!' is found and \a
805 simplify is true, the function returns '!' immediately (as none of all the
806 monomers in the polymer chemistry definition might be a target of this
807 modification) and \a ok is set to true.
808
809 If at least one target is found, then each target is a monomer
810 code and that code is looked for in the member polymer chemistry definition
811 list of monomers. If the code is found, that code is added to a temporary
812 string. If the code is not found, \a ok is set to false and the member targets
813 string is returned unchanged.
814
815 When the process completes, without error, \a ok is set to true and the
816 string corresponding to the processed monomer codes in the member targets string
817 is returned. Otherwise, \a ok is set to false and an empty string is
818 returned.
819 */
820 QString
821 2 Modif::validateTargets(bool simplify, bool &ok)
822 {
823 2 return validateTargets(m_targets, simplify, ok);
824 }
825
826 /*!
827 \brief Validates this modification, sets the validity status accordingly and
828 returns it.
829
830 Any potential error is reported with a message added to \a error_list_p (that is
831 not cleared).
832
833 The modification validates successfully if:
834
835 \list
836 \li The member polymer chemistry definition is available and the isotopic data
837 inside it also
838
839 \li The name is not empty
840
841 \li The formula validates successfully
842
843 \li The masses can be calculated (on local mass variables, given constness of
844 this function)
845
846 \li The targets validate successfully (that is, they reference Monomer codes
847 known to the polymer chemistry definition)
848
849 \li The m_maxCount member is greater than 0
850 \endlist
851
852 If the validation is successful, the validity status (m_isValide) is set to
853 true, to false otherwise, and returned.
854 */
855 bool
856 3710 Modif::validate(ErrorList *error_list_p) const
857 {
858 3710 Q_ASSERT(error_list_p != nullptr);
859
860
2/2
✓ Branch 0 taken 3498 times.
✓ Branch 1 taken 212 times.
3710 qsizetype error_count = error_list_p->size();
861
862 3710 m_isValid = false;
863
864
3/4
✓ Branch 0 taken 3498 times.
✓ Branch 1 taken 212 times.
✓ Branch 2 taken 3498 times.
✗ Branch 3 not taken.
7208 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr ||
865
5/8
✓ Branch 1 taken 3498 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3498 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3498 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 212 times.
✓ Branch 8 taken 3498 times.
10706 mcsp_polChemDef->getIsotopicDataCstSPtr() == nullptr ||
866
8/14
✓ Branch 0 taken 3498 times.
✓ Branch 1 taken 212 times.
✓ Branch 3 taken 3498 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3498 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 3498 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3498 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3498 times.
✓ Branch 13 taken 212 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
14416 mcsp_polChemDef->getIsotopicDataCstSPtr().get() == nullptr ||
867
4/8
✓ Branch 1 taken 3498 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3498 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3498 times.
✓ Branch 7 taken 212 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
7208 !mcsp_polChemDef->getIsotopicDataCstSPtr()->size())
868 {
869 424 error_list_p->push_back(
870 "The PolChemDef or the IsotopicData are not available");
871 424 qCritical()
872 << "The polymer chemistry definition member datum is nullptr or "
873 "its isotopic data are be either nullptr or empty. Modif validation "
874
1/2
✓ Branch 1 taken 212 times.
✗ Branch 2 not taken.
212 "failed.";
875
876 212 return false;
877 }
878
879
2/2
✓ Branch 0 taken 202 times.
✓ Branch 1 taken 3296 times.
3498 if(m_name.isEmpty())
880 {
881
1/2
✓ Branch 2 taken 202 times.
✗ Branch 3 not taken.
202 qCritical() << "The Modif name is empty.";
882 404 error_list_p->push_back("The Modif name is empty");
883 }
884
885 3498 Formula temp_formula(m_formula);
886
5/8
✓ Branch 1 taken 3498 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3498 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3498 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 229 times.
✓ Branch 9 taken 3269 times.
6996 if(!temp_formula.validate(mcsp_polChemDef->getIsotopicDataCstSPtr(),
887 error_list_p))
888 {
889
2/4
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 229 times.
✗ Branch 5 not taken.
229 qCritical() << "The Modif formula failed to validate.";
890
1/2
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
458 error_list_p->push_back("The Modif formula failed to validate");
891 }
892
893 3498 double mono = 0.0;
894 3498 double avg = 0.0;
895
896
5/8
✓ Branch 1 taken 3498 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3498 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3498 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 229 times.
✓ Branch 9 taken 3269 times.
6996 if(!calculateMasses(mcsp_polChemDef->getIsotopicDataCstSPtr(), mono, avg))
897 {
898
2/4
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 229 times.
✗ Branch 5 not taken.
229 qCritical() << "Failed to calculate the Modif masses.";
899
1/2
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
458 error_list_p->push_back("Failed to calculate the Modif masses");
900 }
901
902 3498 bool ok = false;
903
904 // Validate without simplification (false).
905 3498 QString targets_after_validation =
906
1/2
✓ Branch 1 taken 3498 times.
✗ Branch 2 not taken.
3498 validateTargets(m_targets, /* simplification */ false, ok);
907
908
2/2
✓ Branch 0 taken 305 times.
✓ Branch 1 taken 3193 times.
3498 if(!ok)
909 {
910
2/4
✓ Branch 1 taken 305 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 305 times.
✗ Branch 5 not taken.
305 qCritical() << "The Modif targets failed to validate.";
911
1/2
✓ Branch 1 taken 305 times.
✗ Branch 2 not taken.
610 error_list_p->push_back("The Modif targets failed to validate");
912 }
913
914
2/2
✓ Branch 0 taken 380 times.
✓ Branch 1 taken 3118 times.
3498 if(m_maxCount <= 0)
915 {
916
2/4
✓ Branch 1 taken 380 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 380 times.
✗ Branch 5 not taken.
380 qCritical() << "The maximum modification count failed to validate.";
917
1/2
✓ Branch 1 taken 380 times.
✗ Branch 2 not taken.
760 error_list_p->push_back("The maximum modification count failed to validate");
918 }
919
920 3498 m_isValid = (error_list_p->size() > error_count ? false : true);
921
922 3498 return m_isValid;
923 3498 }
924
925 /*!
926 \brief Returns the validity status of this Modif instance.
927 */
928 bool
929 1890 Modif::isValid() const
930 {
931 1890 return m_isValid;
932 }
933
934
935 //////////////// MASS CALCULATIONS /////////////////////
936 /*!
937 \brief Calculates the net masses of this modification and sets the results in
938 \a mono and \a avg.
939
940 The masses of the modification are the masses (monoisotopic and average)
941 that are added to the target as a result of that target being modified with
942 this modification.
943
944 The mass calculations are performed using reference data in \a
945 isotopic_data_csp. If \a isotopic_data_csp is nullptr, then the reference data
946 are searched in the member polymer chemistry definition.
947
948 Returns true if the mass calculations were successful, false otherwise.
949
950 If the calculations failed, m_isValid is set to false.
951
952 \sa Formula::accountMasses()
953 */
954 bool
955 3504 Modif::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp,
956 double &mono,
957 double &avg) const
958 {
959 3504 IsotopicDataCstSPtr local_isotopic_data_csp = isotopic_data_csp;
960
961
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3504 times.
3504 if(local_isotopic_data_csp == nullptr ||
962 local_isotopic_data_csp.get() == nullptr)
963 {
964 if(mcsp_polChemDef != nullptr)
965 local_isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr();
966
967 if(local_isotopic_data_csp == nullptr ||
968 local_isotopic_data_csp.get() == nullptr)
969 {
970 qCritical() << "Failed to find usable isotopic data.";
971 m_isValid = false;
972 return false;
973 }
974 }
975
976 // qDebug() << "Calculating masses for" << m_name;
977
978 3504 mono = 0;
979 3504 avg = 0;
980
981 3504 bool ok;
982
983 // Formula temp_formula(m_formula);
984
2/4
✓ Branch 1 taken 3504 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3504 times.
✗ Branch 5 not taken.
10512 Formula(m_formula).accountMasses(ok, local_isotopic_data_csp, mono, avg);
985
986
2/2
✓ Branch 0 taken 229 times.
✓ Branch 1 taken 3275 times.
3504 if(!ok)
987 {
988
3/8
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 229 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 229 times.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
458 qCritical() << "Failed accounting masses for Modif:" << m_name
989
2/4
✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 229 times.
✗ Branch 5 not taken.
229 << "and formula:" << m_formula;
990 229 m_isValid = false;
991 }
992
993 3504 return ok;
994 3504 }
995
996 /*!
997 \brief Calculates the net masses of this modification.
998
999 The masses of the modification are the masses (monoisotopic and average)
1000 that are added to the target as a result of that target being modified with
1001 this modification.
1002
1003 The calculated masses are set to the m_mono and m_avg members.
1004
1005 The mass calculations are performed using reference data in \a
1006 isotopic_data_csp. If \a isotopic_data_csp is nullptr, then the reference data
1007 are searched in the member polymer chemistry definition.
1008
1009 Returns true if the mass calculations were successful, false otherwise.
1010
1011 If the calculations failed, m_isValid is set to false.
1012
1013 \sa Formula::accountMasses()
1014 */
1015 bool
1016 2531 Modif::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp)
1017 {
1018 2531 IsotopicDataCstSPtr local_isotopic_data_csp = isotopic_data_csp;
1019
1020
2/2
✓ Branch 0 taken 2479 times.
✓ Branch 1 taken 52 times.
2531 if(local_isotopic_data_csp == nullptr ||
1021 local_isotopic_data_csp.get() == nullptr)
1022 {
1023
2/2
✓ Branch 0 taken 2279 times.
✓ Branch 1 taken 200 times.
2479 if(mcsp_polChemDef != nullptr)
1024
2/4
✓ Branch 1 taken 2279 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2279 times.
4558 local_isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr();
1025
1026
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 2279 times.
2479 if(local_isotopic_data_csp == nullptr ||
1027 local_isotopic_data_csp.get() == nullptr)
1028 {
1029
2/4
✓ Branch 1 taken 200 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 200 times.
✗ Branch 5 not taken.
200 qCritical() << "Failed to find usable isotopic data.";
1030 200 return false;
1031 }
1032 }
1033
1034 // qDebug() << "Calculating masses for" << m_name;
1035
1036 2331 m_mono = 0;
1037 2331 m_avg = 0;
1038
1039 2331 bool ok;
1040
1041 // Formula temp_formula(m_formula);
1042
2/4
✓ Branch 1 taken 2331 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 2331 times.
✗ Branch 6 not taken.
4662 Formula(m_formula).accountMasses(ok, local_isotopic_data_csp, m_mono, m_avg);
1043
1044
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2318 times.
2331 if(!ok)
1045 {
1046
3/8
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 13 times.
✗ Branch 8 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
26 qCritical() << "Failed accounting masses for Modif:" << m_name
1047
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 << "and formula:" << m_formula;
1048 13 m_isValid = false;
1049 }
1050
1051 2331 return ok;
1052 2531 }
1053
1054 /*!
1055 \brief Calculates the net masses of this modification.
1056
1057 The masses of the modification are the masses (monoisotopic and average)
1058 that are added to the target as a result of that target being modified with
1059 this modification.
1060
1061 The calculated masses are set to the m_mono and m_avg members.
1062
1063 The mass calculations are performed using reference data in \a
1064 isotopic_data_csp. If \a isotopic_data_csp is nullptr, then the reference data
1065 are searched in the member polymer chemistry definition.
1066
1067 Returns a reference to this Modif instance.
1068
1069 If the calculations failed, m_isValid is set to false.
1070
1071 \sa Formula::accountMasses()
1072 */
1073 Modif &
1074 3 Modif::calculateMasses(bool &ok, const IsotopicDataCstSPtr &isotopic_data_csp)
1075 {
1076 3 IsotopicDataCstSPtr local_isotopic_data_csp = isotopic_data_csp;
1077
1078
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if(local_isotopic_data_csp == nullptr ||
1079 local_isotopic_data_csp.get() == nullptr)
1080 {
1081
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(mcsp_polChemDef != nullptr)
1082
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
2 local_isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr();
1083
1084
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(local_isotopic_data_csp == nullptr ||
1085 local_isotopic_data_csp.get() == nullptr)
1086 {
1087 qCritical() << "Failed to find usable isotopic data.";
1088 ok = false;
1089 return *this;
1090 }
1091 }
1092
1093 // qDebug() << "Calculating masses for" << m_name;
1094
1095 3 m_mono = 0;
1096 3 m_avg = 0;
1097
1098 // Formula temp_formula(m_formula);
1099
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
6 Formula(m_formula).accountMasses(ok, local_isotopic_data_csp, m_mono, m_avg);
1100
1101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(!ok)
1102 {
1103 qCritical() << "Failed accounting masses for Modif:" << m_name
1104 << "and formula:" << m_formula;
1105 m_isValid = false;
1106 }
1107
1108 return *this;
1109 3 }
1110
1111 /*!
1112 \brief Adds to \a mono_p and \a avg_p the corresponding mass of this
1113 modification.
1114
1115 The m_mono and m_avg masses are added to the arguments. The masses are
1116 compounded by factor \a times before the addition.
1117
1118 Returns this object.
1119 */
1120 const Modif &
1121 31 Modif::accountMasses(double *mono_p, double *avg_p, int times) const
1122 {
1123
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1 times.
31 if(mono_p != nullptr)
1124 30 *mono_p += m_mono * times;
1125
1126
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1 times.
31 if(avg_p != nullptr)
1127 30 *avg_p += m_avg * times;
1128
1129 31 return *this;
1130 }
1131
1132 /*!
1133 \brief Adds to \a mono and \a avg the corresponding mass of this
1134 modification.
1135
1136 The m_mono and m_avg masses are added to the arguments. The masses are
1137 compounded by factor \a times before the addition.
1138
1139 Returns this object.
1140 */
1141 const Modif &
1142 1177 Modif::accountMasses(double &mono, double &avg, int times) const
1143 {
1144 1177 mono += m_mono * times;
1145 1177 avg += m_avg * times;
1146
1147 1177 return *this;
1148 }
1149
1150
1151 /*!
1152 \brief Returns the mass of the type defined by \a mass_type.
1153 */
1154 double
1155 12 Modif::getMass(Enums::MassType mass_type)
1156 {
1157
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 if(mass_type == Enums::MassType::MONO)
1158 6 return m_mono;
1159
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 else if(mass_type == Enums::MassType::AVG)
1160 6 return m_avg;
1161 else
1162 qFatalStream() << "Not possible to ask a mass that is not MONO nor AVG";
1163
1164 return -1;
1165 }
1166
1167 /*!
1168 \brief Parses the modification XML \a element specifically for \a version.
1169
1170 Parses the modif \c mdf XML element passed as argument and for each
1171 encountered data will set the data to this modif (this is
1172 called XML rendering).The parsing is delegated to a function that is
1173 specific for \a version of the polymer chemistry definition.
1174
1175 The \c mdf XML element is found in the polymer chemistry definition and has
1176 the following form:
1177
1178
1179 \code
1180 <mdf>
1181 <name>Acetylation</name>
1182 <formula>C2H2O1</formula>
1183 <targets>;K;</targets>
1184 <maxcount>1</maxcount>
1185 </mdf>
1186 <mdf>
1187 <name>AmidationAsp</name>
1188 <formula>H1N1-O1</formula>
1189 <targets>;D;</targets>
1190 <maxcount>1</maxcount>
1191 </mdf>
1192 \endcode
1193
1194 After setting all the data, this modification calculates it masses and
1195 validates itself. If any of these steps fails, the error is reported
1196 by returning false.
1197
1198 Returns true if parsing was successful, false otherwise.
1199 */
1200 bool
1201 1929 Modif::renderXmlMdfElement(const QDomElement &element,
1202 [[maybe_unused]] int version)
1203 {
1204 // Assume this, we later negate this as we go.
1205 1929 m_isValid = true;
1206
1207 // QDomDocument doc;
1208 // QDomElement root = doc.importNode(element, true).toElement();
1209 // doc.appendChild(root);
1210 // qDebug() << "The element:" << doc.toString();
1211
1212
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1928 times.
3858 if(element.tagName() != "mdf")
1213 {
1214
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 qCritical() << "The element tag name is not 'mdf'";
1215 1 m_isValid = false;
1216 1 return m_isValid;
1217 }
1218
1219 1928 QDomElement child;
1220
1221
3/6
✓ Branch 1 taken 1928 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1928 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1928 times.
✗ Branch 8 not taken.
3856 child = element.firstChildElement("name");
1222
1223
7/10
✓ Branch 1 taken 1928 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1927 times.
✓ Branch 4 taken 1 times.
✓ Branch 6 taken 1927 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1927 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1927 times.
3855 if(child.isNull() || child.text().isEmpty())
1224 {
1225
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: problem with the "
1226
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "<name> element.";
1227 1 m_isValid = false;
1228 1 return m_isValid;
1229 }
1230
1/2
✓ Branch 1 taken 1927 times.
✗ Branch 2 not taken.
1927 m_name = child.text();
1231 // qDebug() << "The name:" << m_name;
1232
1233
3/6
✓ Branch 1 taken 1927 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1927 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1927 times.
✗ Branch 8 not taken.
3854 child = child.nextSiblingElement("formula");
1234
1235
3/4
✓ Branch 1 taken 1927 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1926 times.
1927 if(child.isNull())
1236 {
1237
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: problem with the "
1238
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "<formula> element.";
1239 1 m_isValid = false;
1240 1 return m_isValid;
1241 }
1242
1243
1/2
✓ Branch 1 taken 1926 times.
✗ Branch 2 not taken.
1926 Formula temp_formula(nullptr);
1244
1245
3/4
✓ Branch 1 taken 1926 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1925 times.
1926 if(!temp_formula.renderXmlFormulaElement(child))
1246 {
1247
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: the formula did not "
1248
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "render correctly.";
1249 1 m_isValid = false;
1250 1 return m_isValid;
1251 }
1252
1/2
✓ Branch 1 taken 1925 times.
✗ Branch 2 not taken.
1925 m_formula = temp_formula.getActionFormula(/*with_title*/ true);
1253 // qDebug() << "The formula:" << m_formula;
1254
1255
3/6
✓ Branch 1 taken 1925 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1925 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1925 times.
✗ Branch 8 not taken.
3850 child = child.nextSiblingElement("targets");
1256
1257
7/10
✓ Branch 1 taken 1925 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1924 times.
✓ Branch 4 taken 1 times.
✓ Branch 6 taken 1924 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1924 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1924 times.
3849 if(child.isNull() || child.text().isEmpty())
1258 {
1259
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: problem with the "
1260
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "<targets> element.";
1261 1 m_isValid = false;
1262 1 return m_isValid;
1263 }
1264
1/2
✓ Branch 1 taken 1924 times.
✗ Branch 2 not taken.
1924 m_targets = child.text();
1265
1266 1924 bool ok = false;
1267
1268 1924 QString targets_after_validation =
1269
1/2
✓ Branch 1 taken 1924 times.
✗ Branch 2 not taken.
1924 validateTargets(m_targets, /* simplify */ false, ok);
1270
1271 // qDebug() << "The targets after validation:" << targets_after_validation;
1272
1273
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1921 times.
1924 if(!ok)
1274 {
1275
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 qCritical() << "The Modif did not render correctly: the targets could "
1276
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 "not validate successfully.";
1277 3 m_isValid = false;
1278 3 return m_isValid;
1279 }
1280 1921 m_targets = targets_after_validation;
1281
1282
3/6
✓ Branch 1 taken 1921 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1921 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1921 times.
✗ Branch 8 not taken.
3842 child = child.nextSiblingElement("maxcount");
1283
1284
7/10
✓ Branch 1 taken 1921 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1920 times.
✓ Branch 4 taken 1 times.
✓ Branch 6 taken 1920 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1920 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1920 times.
3841 if(child.isNull() || child.text().isEmpty())
1285 {
1286
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: problem with the "
1287
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "<maxcount> element.";
1288 1 m_isValid = false;
1289 1 return m_isValid;
1290 }
1291
1/2
✓ Branch 1 taken 1920 times.
✗ Branch 2 not taken.
1920 QString max_count_string = child.text();
1292
1293 1920 ok = false;
1294
1295
1/2
✓ Branch 1 taken 1920 times.
✗ Branch 2 not taken.
1920 int max_count_int = max_count_string.toInt(&ok);
1296
1297
3/4
✓ Branch 0 taken 1920 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1919 times.
1920 if(!ok || max_count_int <= 0)
1298 {
1299
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qCritical() << "The Modif did not render correctly: the maxcount value "
1300
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "is less than 0.";
1301 1 m_isValid = false;
1302 1 return m_isValid;
1303 }
1304 1919 m_maxCount = max_count_int;
1305
1306 1919 ErrorList error_list;
1307
1/2
✓ Branch 1 taken 1919 times.
✗ Branch 2 not taken.
1919 m_isValid = validate(&error_list);
1308
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1919 times.
1919 if(!m_isValid)
1309 {
1310 qCritical() << "The Modif did not validate successfully after "
1311 "rendering, with errors:";
1312 Utils::joinErrorList(error_list, ", ");
1313 }
1314 else
1315 {
1316 // At this point, because we are creating a Modif from scratch,
1317 // and not by copying or by assignment, we calculate the masses
1318 // explicitely (validate() does that but not on member m_mono/m_avg
1319 // because the method is const.).
1320
1321
3/6
✓ Branch 1 taken 1919 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1919 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1919 times.
1919 if(!calculateMasses(nullptr))
1322 {
1323 qCritical() << "The Modif's masses could not be calculated.";
1324 }
1325 }
1326
1327 1919 qDebug() << "Correctly rendered Modif" << m_name;
1328
1329 1919 return m_isValid;
1330 5773 }
1331
1332
1333 /*!
1334 \brief Formats this modification's data as a string suitable to be used as a
1335 \c mdf XML element in the polymer chemistry definition or a polymer sequence
1336 file.
1337
1338 The typical modification element that is generated in this function looks
1339 like this:
1340
1341 \code
1342 <mdf>
1343 <name>Acetylation</name>
1344 <formula>C2H2O1</formula>
1345 <targets>;K;</targets>
1346 <maxcount>1</maxcount>
1347 </mdf>
1348 <mdf>
1349 <name>AmidationAsp</name>
1350 <formula>H1N1-O1</formula>
1351 <targets>;D;</targets>
1352 <maxcount>1</maxcount>
1353 </mdf>
1354 \endcode
1355
1356 The formatting of the XML element takes into account \a offset and \a
1357 indent by prepending the string with \a offset * \a indent character
1358 substring.
1359
1360 \a indent defaults to two spaces.
1361
1362 Returns a string.
1363 */
1364 QString
1365 28 Modif::formatXmlMdfElement(int offset, const QString &indent) const
1366 {
1367 28 int newOffset;
1368 28 int iter = 0;
1369
1370 28 QString lead("");
1371 28 QString text;
1372
1373 // Prepare the lead.
1374 28 newOffset = offset;
1375
2/2
✓ Branch 0 taken 79 times.
✓ Branch 1 taken 28 times.
107 while(iter < newOffset)
1376 {
1377
1/2
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
79 lead += indent;
1378 79 ++iter;
1379 }
1380
1381 /* We are willing to create an <modif> node that should look like this:
1382
1383 <mdf>
1384 <name>Phosphorylation</name>
1385 <formula>-H+H2PO3</formula>
1386 <targets>S;T;Y</targets>
1387 <maxcount>1</maxcount>
1388 </mdf>
1389
1390 */
1391
1392
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
56 text += QString("%1<mdf>\n").arg(lead);
1393
1394 // Prepare the lead.
1395 28 ++newOffset;
1396 28 lead.clear();
1397 28 iter = 0;
1398
2/2
✓ Branch 1 taken 107 times.
✓ Branch 2 taken 28 times.
163 while(iter < newOffset)
1399 {
1400
1/2
✓ Branch 1 taken 107 times.
✗ Branch 2 not taken.
107 lead += indent;
1401 107 ++iter;
1402 }
1403
1404 // Continue with indented elements.
1405
1406
3/6
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
56 text += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);
1407
1408
3/6
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
56 text += QString("%1<formula>%2</formula>\n").arg(lead).arg(m_formula);
1409
1410
3/6
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
56 text += QString("%1<targets>%2</targets>\n").arg(lead).arg(m_targets);
1411
1412
4/8
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 28 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 28 times.
✗ Branch 11 not taken.
28 text += QString("%1<maxcount>%2</maxcount>\n").arg(lead).arg(m_maxCount);
1413
1414 // Prepare the lead for the closing element.
1415 28 --newOffset;
1416 28 lead.clear();
1417 28 iter = 0;
1418
2/2
✓ Branch 1 taken 79 times.
✓ Branch 2 taken 28 times.
135 while(iter < newOffset)
1419 {
1420
1/2
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
79 lead += indent;
1421 79 ++iter;
1422 }
1423
1424
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
56 text += QString("%1</mdf>\n").arg(lead);
1425
1426 // QString debug_message =
1427 // QString("%1\n%2\n").arg("Returning string:").arg(text);
1428 // qCritical().noquote() << debug_message;
1429
1430 28 return text;
1431 28 }
1432
1433 /*!
1434 \brief Resets this modification to an empty object.
1435 */
1436 void
1437 Modif::clear()
1438 {
1439 m_name.clear();
1440 m_formula.clear();
1441 m_targets.clear();
1442 m_maxCount = -1;
1443
1444 m_mono = 0;
1445 m_avg = 0;
1446
1447 m_isValid = false;
1448 }
1449
1450 /*!
1451 \brief Returns a string representing this Modif instance.
1452 */
1453 QString
1454 1 Modif::toString() const
1455 {
1456 1 return QString("%1, %2, %3, %4")
1457
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 .arg(m_name)
1458
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 .arg(m_formula)
1459
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 .arg(m_targets)
1460
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 .arg(m_maxCount);
1461 }
1462
1463 } // namespace libXpertMassCore
1464 } // namespace MsXpS
1465