GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Polymer.cpp
Date: 2025-11-20 01:41:33
Lines:
490/1134
43.2%
Functions:
56/109
51.4%
Branches:
482/1994
24.2%

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 /////////////////////// stdlib includes
34
35
36 /////////////////////// Qt includes
37 #include <QDateTime>
38 #include <QFile>
39 #include <QIODevice>
40
41 /////////////////////// Local includes
42 #include "MsXpS/libXpertMassCore/Polymer.hpp"
43 #include "MsXpS/libXpertMassCore/Modif.hpp"
44 #include "MsXpS/libXpertMassCore/PolChemDef.hpp"
45 #include "MsXpS/libXpertMassCore/CrossLink.hpp"
46
47
48 int polymerMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Polymer>(
49 "MsXpS::libXpertMassCore::Polymer");
50
51 int polymerSPtrMetaTypeId =
52 qRegisterMetaType<MsXpS::libXpertMassCore::PolymerSPtr>(
53 "MsXpS::libXpertMassCore::PolymerSPtr");
54
55 int PolymerCstQSPtrMetaTypeId =
56 qRegisterMetaType<MsXpS::libXpertMassCore::PolymerCstQSPtr>(
57 "MsXpS::libXpertMassCore::PolymerCstQSPtr");
58
59 namespace MsXpS
60 {
61 namespace libXpertMassCore
62 {
63
64
65 /*!
66 \class MsXpS::libXpertMassCore::Polymer
67 \inmodule libXpertMassCore
68 \ingroup PolChemDefBuildingdBlocks
69 \inheaderfile Polymer.hpp
70
71 \brief The Polymer class provides abstractions to work with
72 a polymer molecule (protein or saccharide , for example).
73
74 The Polymer class provides a polymer sequence. In the
75 protein world, a polymer sequence is a protein. From a computing standpoint,
76 that sequence is first created by chaining amino acid residues (the residue
77 chain). In a second step, the entity is set to a finished polymer state by
78 adding the N-terminal proton and the C-terminal hydroxyl residue. It might
79 happen also that the ends of a polymer be chemically modified (acetylation of
80 the N-terminal residue, for example).
81
82 The Polymer class allows modelling a polymer sequence in its finest details.
83
84 \note As a design decision, Polymer instances can only be created as shared
85 pointer instances. Hence, the constructor is private and is called by
86 \l{createSPtr()}.
87
88 \note The Polymer class derives from std::enable_shared_from_this<Polymer>
89 because in determinate situations it is necessary to access the shared pointer
90 from the raw pointer (in the particular case of CrossLink instances that need to
91 reference the Polymer in which they occur).
92 */
93
94 /*!
95 \variable MsXpS::libXpertMassCore::POL_SEQ_FILE_FORMAT_VERSION
96
97 \brief Version number of the polymer sequence file format.
98 */
99
100 /*!
101 \variable MsXpS::libXpertMassCore::Polymer::mcsp_polChemDef
102
103 \brief The \l PolChemDef polymer chemistry definition that is the context in
104 which the Polymer exists.
105 */
106
107 /*!
108 \variable MsXpS::libXpertMassCore::Polymer::m_name
109
110 \brief The name of the polymer, for example, "Apomyoglobin".
111 */
112
113 /*!
114 \variable MsXpS::libXpertMassCore::Polymer::m_code
115
116 \brief The code of the polymer, for example, the accession number in
117 SwissProt.
118 */
119
120 /*!
121 \variable MsXpS::libXpertMassCore::Polymer::m_author
122
123 \brief The author or creator of the file.
124 */
125
126 /*!
127 \variable MsXpS::libXpertMassCore::Polymer::m_filePath
128
129 \brief The file path to the Polymer sequence file.
130 */
131
132 /*!
133 \variable MsXpS::libXpertMassCore::Polymer::m_dateTime
134
135 \brief The date and time of last modification.
136 */
137
138 /*!
139 \variable MsXpS::libXpertMassCore::Polymer::m_sequence
140
141 \brief The Sequence instance that enshrines the actual Polymer sequence of
142 Monomer instances.
143 */
144
145 /*!
146 \variable MsXpS::libXpertMassCore::Polymer::m_leftEndModif
147
148 \brief The left end modification.
149 */
150
151 /*!
152 \variable MsXpS::libXpertMassCore::Polymer::m_rightEndModif
153
154 \brief The right end modification.
155 */
156
157 /*!
158 \variable MsXpS::libXpertMassCore::Polymer::mp_calcOptions
159
160 \brief The CalcOptions instance that configure the way masses and formulas are
161 computed.
162 */
163
164 /*!
165 \variable MsXpS::libXpertMassCore::Polymer::mp_ionizer
166
167 \brief The Ionizer instance that configure the ionization of this Polymer
168 instance.
169 */
170
171 /*!
172 \variable MsXpS::libXpertMassCore::Polymer::m_crossLinks
173
174 \brief The container of \l UuidCrossLinkWPtrPair instances occurring in this
175 Polymer sequence.
176 */
177
178 /*!
179 \variable MsXpS::libXpertMassCore::Polymer::m_mono
180
181 \brief The monoisotopic mass computed for this Polymer instance.
182 */
183
184 /*!
185 \variable MsXpS::libXpertMassCore::Polymer::m_avg
186
187 \brief The average mass computed for this Polymer instance.
188 */
189
190 /*!
191 \variable MsXpS::libXpertMassCore::Polymer::m_isValid
192
193 \brief The validity status of this Polymer instance.
194 */
195
196 /*!
197 \typedef MsXpS::libXpertMassCore::PolymerSPtr
198 \relates Polymer
199
200 Synonym for std::shared_ptr<Polymer>.
201 */
202
203 /*!
204 \typedef MsXpS::libXpertMassCore::PolymerCstQSPtr
205 \relates Polymer
206
207 Synonym for std::shared_ptr<const Polymer>.
208 */
209
210
211 /*!
212 \brief Constructs a polymer with a number of arguments.
213
214 \list
215 \li \a pol_chem_def_csp The polymer chemistry definition context in which this
216 Polymer instance exists
217
218 \li \a name The name of the Polymer instance (could be the name of the protein
219 from the SwissProt or the GenBank database, for example)
220
221 \li \a code The code of Polymer sequence (could be an accession number from the
222 SwissProt or the GenBank database, for example)
223
224 \li \a author The author creating the Polymer sequence
225
226 \li \a sequence_string The monomer codes that make the Polymer sequence.
227
228 \endlist
229
230 This function call the actual private constructor.
231 */
232 PolymerQSPtr
233 93 Polymer::createSPtr(PolChemDefCstSPtr pol_chem_def_csp,
234 const QString &name,
235 const QString &code,
236 const QString &author,
237 const QString &sequence_string)
238 {
239 93 Polymer *polymer_p =
240
3/4
✓ Branch 3 taken 93 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 88 times.
✓ Branch 6 taken 5 times.
93 new Polymer(pol_chem_def_csp, name, code, author, sequence_string);
241 93 return QSharedPointer<Polymer>(polymer_p);
242 }
243
244 // This constructor is private on purpose, so that a Polymer can only be
245 // constructed as a QSharedPointer using the public createSPtr() above.
246 // See how mp_calcOptions and mp_ionizer can never be nullptr at construction
247 // time.
248 93 Polymer::Polymer(PolChemDefCstSPtr pol_chem_def_csp,
249 const QString &name,
250 const QString &code,
251 const QString &author,
252 93 const QString &sequence_string)
253 93 : mcsp_polChemDef(pol_chem_def_csp),
254
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 85 times.
93 m_name(name),
255
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 85 times.
93 m_code(code),
256
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 85 times.
93 m_author(author),
257
1/2
✓ Branch 2 taken 93 times.
✗ Branch 3 not taken.
93 m_sequence(Sequence(pol_chem_def_csp, sequence_string)),
258
1/2
✓ Branch 1 taken 93 times.
✗ Branch 2 not taken.
93 mp_calcOptions(new CalcOptions(this)),
259
5/10
✓ Branch 4 taken 93 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 93 times.
✗ Branch 10 not taken.
✓ Branch 14 taken 93 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 93 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 93 times.
✗ Branch 21 not taken.
558 mp_ionizer(new Ionizer(this))
260 {
261
1/2
✓ Branch 2 taken 93 times.
✗ Branch 3 not taken.
93 m_leftEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
262
1/2
✓ Branch 2 taken 93 times.
✗ Branch 3 not taken.
93 m_rightEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
263
0/2
✗ Branch 10 not taken.
✗ Branch 11 not taken.
93 }
264
265 /*!
266 \brief Destructs the polymer.
267 */
268 304 Polymer::~Polymer()
269 {
270 152 m_crossLinks.clear();
271
2/2
✓ Branch 10 taken 75 times.
✓ Branch 11 taken 1 times.
454 }
272
273 //////////////// THE SHARED POINTER /////////////////////
274 /*!
275 \brief Returns a const-cast of the PolymerSPtr (std::shared_ptr<Polymer>) that
276 manages this Polymer *.
277
278 This function should never fail because a Polymer can
279 only be allocated as a PolymerSPtr (private Polymer::Polymer() constructor
280 called by a public createSPtr() function).
281
282 \sa CrossLink::CrossLink(), renderXmlCrossLinksElement()
283 */
284 PolymerCstQSPtr
285 103 Polymer::getCstSharedPtr()
286 {
287 103 PolymerCstQSPtr polymer_cqsp = nullptr;
288 103 try
289 {
290 103 polymer_cqsp = sharedFromThis();
291 }
292 catch(const std::exception &e)
293 {
294 QString message =
295 QString("Exception caught: %1\n%2.")
296 .arg(e.what())
297 .arg(
298 "Programming error. The pointer cannot be nullptr. If that "
299 "pointer was gotten from Polymer::getCstSharedPtr(), ensure that "
300 "the used raw pointer is indeed managed by a "
301 "QSharedPointer<Polymer>.");
302 qFatalStream().noquote() << message;
303 }
304
305 103 return polymer_cqsp;
306 }
307
308 /*!
309 \brief Returns the PolymerSPtr (std::shared_ptr<Polymer>) that
310 manages this Polymer *.
311
312 This function should never fail because a Polymer can
313 only be allocated as a PolymerSPtr (private Polymer::Polymer() constructor
314 called by a public createSPtr() function).
315
316 \sa CrossLink::CrossLink(), renderXmlCrossLinksElement()
317 */
318 PolymerQSPtr
319 Polymer::getSharedPtr()
320 {
321 PolymerQSPtr polymer_qsp = nullptr;
322 try
323 {
324 polymer_qsp = sharedFromThis();
325 }
326 catch(const std::exception &e)
327 {
328 QString message =
329 QString("Exception caught: %1\n%2.")
330 .arg(e.what())
331 .arg(
332 "Programming error. The pointer cannot be nullptr. If that "
333 "pointer was gotten from Polymer::getCstSharedPtr(), ensure that "
334 "the used raw pointer is indeed managed by a "
335 "QSharedPointer<Polymer>.");
336 qFatalStream().noquote() << message;
337 }
338
339 return polymer_qsp;
340 }
341
342 //////////////// THE POLCHEMDEF /////////////////////
343 /*!
344 \brief Sets the polymer chemistry definition to \a pol_chem_def_csp.
345 */
346 void
347 4 Polymer::setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp)
348 {
349 4 mcsp_polChemDef = pol_chem_def_csp;
350
351 4 ErrorList error_list;
352
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_isValid = validate(&error_list);
353
354
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 m_leftEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
355
1/2
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 m_rightEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
356
357
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(!m_isValid)
358
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
359
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, ", ");
360 4 }
361
362 /*!
363 \brief Returns the polymer chemistry definition.
364 */
365 const PolChemDefCstSPtr &
366 1987 Polymer::getPolChemDefCstSPtr() const
367 {
368 1987 return mcsp_polChemDef;
369 }
370
371 //////////////// THE NAME /////////////////////
372
373 /*!
374 \brief Sets the \a name.
375 */
376 void
377 4 Polymer::setName(const QString &name)
378 {
379 4 m_name = name;
380
381 4 ErrorList error_list;
382
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_isValid = validate(&error_list);
383
384
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(!m_isValid)
385
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
386
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, ", ");
387 4 }
388
389 /*!
390 \brief Returns the name.
391 */
392 QString
393 3 Polymer::getName() const
394 {
395
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 return m_name;
396 }
397
398 /*!
399 \brief Sets the \a code.
400
401 The code might identify the polymer in a database, like the Swissprot code, for
402 example.
403 */
404 void
405 4 Polymer::setCode(const QString &code)
406 {
407 4 m_code = code;
408
409 4 ErrorList error_list;
410
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_isValid = validate(&error_list);
411
412
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(!m_isValid)
413
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
414
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, ", ");
415 4 }
416
417 /*!
418 \brief Returns the code.
419 */
420 QString
421 3 Polymer::getCode() const
422 {
423
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 return m_code;
424 }
425
426 /*!
427 \brief Sets the \a author.
428 */
429 void
430 4 Polymer::setAuthor(const QString &author)
431 {
432 4 m_author = author;
433
434 4 ErrorList error_list;
435
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_isValid = validate(&error_list);
436
437
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(!m_isValid)
438
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
439
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, ", ");
440 4 }
441
442 /*!
443 \brief Returns the author.
444 */
445 QString
446 3 Polymer::getAuthor() const
447 {
448
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 return m_author;
449 }
450
451 /*!
452 \brief Sets the polymer's \l{Sequence} file path to \a file_path.
453 */
454 void
455 89 Polymer::setFilePath(const QString &file_path)
456 {
457 89 m_filePath = file_path;
458
459 89 ErrorList error_list;
460
1/2
✓ Branch 1 taken 89 times.
✗ Branch 2 not taken.
89 m_isValid = validate(&error_list);
461
462
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 85 times.
89 if(!m_isValid)
463
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
464
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, ", ");
465 89 }
466
467 /*!
468 \brief Returns the polymer sequence file path.
469 */
470 QString
471 14 Polymer::getFilePath() const
472 {
473
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 times.
14 return m_filePath;
474 }
475
476 /*!
477 \brief Sets the date and time to \a date_time.
478 */
479 void
480 9 Polymer::setDateTime(const QString &date_time)
481 {
482 18 m_dateTime = QDateTime::fromString(date_time, "yyyy-MM-dd:mm:ss");
483
484 9 ErrorList error_list;
485
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 m_isValid = validate(&error_list);
486
487
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if(!m_isValid)
488
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
8 qCritical() << "Failed to validate the Polymer with errors:\n"
489
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, ", ");
490 9 }
491
492 /*!
493 \brief Sets the date and time to now.
494 */
495 void
496 Polymer::setDateTime()
497 {
498 m_dateTime = QDateTime::currentDateTime();
499
500 ErrorList error_list;
501 m_isValid = validate(&error_list);
502
503 if(!m_isValid)
504 qCritical() << "Failed to validate the Polymer with errors:\n"
505 << Utils::joinErrorList(error_list, ", ");
506 }
507
508 /*!
509 \brief Returns the date and time.
510 */
511 QString
512 3 Polymer::getDateTime() const
513 {
514
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 return m_dateTime.toString("yyyy-MM-dd:mm:ss");
515 }
516
517 //////////////// THE SEQUENCE /////////////////////
518
519 /*!
520 \brief Sets the \l{Sequence} as created using text \a sequence_string.
521 */
522 void
523 6 Polymer::setSequence(const QString &sequence_string)
524 {
525
3/6
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
6 m_sequence = Sequence(mcsp_polChemDef, sequence_string);
526
527 6 ErrorList error_list;
528
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 m_isValid = validate(&error_list);
529
530
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(!m_isValid)
531 qCritical() << "Failed to validate the Polymer with errors:\n"
532 << Utils::joinErrorList(error_list, ", ");
533 6 }
534
535 /*!
536 \brief Sets the \l{Sequence} as \a sequence.
537 */
538 void
539 2 Polymer::setSequence(const Sequence &sequence)
540 {
541 2 m_sequence = sequence;
542
543 2 ErrorList error_list;
544
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 m_isValid = validate(&error_list);
545
546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(!m_isValid)
547 qCritical() << "Failed to validate the Polymer with errors:\n"
548 << Utils::joinErrorList(error_list, ", ");
549 2 }
550
551 /*!
552 \brief Returns a const reference to the member Sequence instance.
553 */
554 const Sequence &
555 30528 Polymer::getSequenceCstRef() const
556 {
557 30528 return m_sequence;
558 }
559
560 /*!
561 \brief Returns a reference to the member Sequence instance.
562 */
563 Sequence &
564 2 Polymer::getSequenceRef()
565 {
566 2 return m_sequence;
567 }
568
569 /*!
570 \brief Returns this Polymer instance's \l{Sequence}'s sequence as a text string.
571 */
572 QString
573 Polymer::getSequenceText() const
574 {
575 return m_sequence.getSequence();
576 }
577
578 /*!
579 \brief Returns the size of this Polymer instance as the count of Monomer
580 instances contained in the Monomer container belonging to the member
581 Sequence instance.
582 */
583 std::size_t
584 1427 Polymer::size() const
585 {
586 1427 return m_sequence.size();
587 }
588
589 // MONOMER MODIFICATIONS
590 /////////////////////////////
591 /*!
592 \brief Returns the count of modified Monomers occurring in the IndexRange
593 instances contained in \a index_range_collection.
594 */
595 std::size_t
596 717 Polymer::modifiedMonomerCount(
597 const IndexRangeCollection &index_range_collection) const
598 {
599 717 qsizetype count = 0;
600
601
2/2
✓ Branch 2 taken 713 times.
✓ Branch 3 taken 717 times.
1430 foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
602 {
603
2/2
✓ Branch 0 taken 10173 times.
✓ Branch 1 taken 713 times.
10886 for(qsizetype iter = item->m_start; iter < item->m_stop + 1; ++iter)
604 {
605
5/8
✓ Branch 1 taken 10173 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10173 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10173 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 109 times.
✓ Branch 9 taken 10064 times.
20346 if(m_sequence.getMonomerCstSPtrAt(iter)->isModified())
606 109 ++count;
607 }
608 717 }
609
610 717 return count;
611 }
612
613 /*!
614 \brief Returns true if the modification of Monomer instance at \a index using \a
615 modif_name was succesful, false otherwise.
616
617 If \a override is true, the Monomer at \a index is modified even if it is
618 current modified the maximum count for \a modif_name.
619
620 \sa Modif::m_maxCount
621 */
622 bool
623 2 Polymer::modifyMonomer(std::size_t index,
624 const QString modif_name,
625 bool override)
626 {
627 // qDebug() << "The modification name:" << modif_name;
628
629
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if(index >= size())
630 qFatalStream() << "Programming error. Index is out of bounds.";
631
632
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
4 return m_sequence.modifyMonomer(index, modif_name, override);
633 }
634
635 /*!
636 \brief Return true if this Polymer's \l{Sequence} contains at least one modified
637 \l{Monomer} in the sequence range delimited by \a left_index and \a right_index,
638 false otherwise.
639 */
640 bool
641 Polymer::hasModifiedMonomer(std::size_t left_index,
642 std::size_t right_index) const
643 {
644 return m_sequence.hasModifiedMonomer(left_index, right_index);
645 }
646
647 // POLYMER MODIFICATIONS
648 /////////////////////////////
649
650 // ANY END
651
652 /*!
653 \brief Removes this Polymer instance's modification on the end specified in \a
654 polymer_end.
655 */
656 void
657 Polymer::unmodify(Enums::PolymerEnd polymer_end)
658 {
659 if(static_cast<int>(polymer_end) & static_cast<int>(Enums::PolymerEnd::LEFT))
660 setLeftEndModifByName("");
661
662 if(static_cast<int>(polymer_end) & static_cast<int>(Enums::PolymerEnd::RIGHT))
663 setRightEndModifByName("");
664 }
665
666 // LEFT END
667
668 /*!
669 \brief Sets this polymer's left end modification to \a name.
670
671 If \a name is empty, the corresponding Modif instance of this Polymer object is
672 removed (ie, the polymer's end is unmodifed.)
673
674 Returns true if a modification by \a name was found in the member polymer
675 chemistry definition's list of modifications and if that modification could be
676 set and its masses could be calculated successfully, otherwise returns false.
677
678 After setting the member data, the instance is validated and the result is set
679 to m_isValid.
680 */
681 bool
682 4 Polymer::setLeftEndModifByName(const QString &name)
683 {
684
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if(name.isNull() || name.isEmpty())
685 {
686 // Reset the modif to nothing.
687 m_leftEndModif.clear();
688 }
689
690
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
691 {
692 qCritical()
693 << "Cannot search for Modif name if no PolChemDef is available.";
694 return false;
695 }
696
697 4 ModifCstSPtr modif_csp = mcsp_polChemDef->getModifCstSPtrByName(name);
698
699
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(modif_csp == nullptr)
700 {
701 qCritical() << "Failed to find a Modif by name " << name
702 << "in the polChemDef.";
703 return false;
704 }
705
706
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_leftEndModif = *modif_csp.get();
707
708
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if(m_leftEndModif.getPolChemDefCstSPtr() != mcsp_polChemDef)
709 qFatalStream()
710 << "Programming error. The pointers to PolChemDef cannot differ.";
711
712 4 ErrorList error_list;
713
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_isValid = validate(&error_list);
714
715
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if(!m_isValid)
716 qCritical() << "Failed to validate the Polymer with errors:\n"
717 << Utils::joinErrorList(error_list, ", ");
718
719 4 return true;
720 8 }
721
722 /*!
723 \brief Sets this polymer's left end modification to \a modif.
724
725 Returns true if \a modif could validate successfully and if that modification
726 could be set and its masses could be calculated successfully, otherwise returns
727 false.
728
729 After setting the member data, the instance is validated and the result is set
730 to m_isValid.
731 */
732 bool
733 Polymer::setLeftEndModif(const Modif &modif)
734 {
735 ErrorList error_list;
736
737 m_leftEndModif = modif;
738 m_leftEndModif.setPolChemDefCstSPtr(mcsp_polChemDef);
739
740 if(!m_leftEndModif.validate(&error_list))
741 {
742 qCritical() << "The Modif is not valid, with errors:"
743 << Utils::joinErrorList(error_list, ", ");
744 return false;
745 }
746
747 m_isValid = validate(&error_list);
748
749 if(!m_isValid)
750 qCritical() << "Failed to validate the Polymer with errors:\n"
751 << Utils::joinErrorList(error_list, ", ");
752
753 return true;
754 }
755
756 /*!
757 \brief Returns a const reference to this Polymer's left end modif.
758 */
759 const Modif &
760 145 Polymer::getLeftEndModifCstRef() const
761 {
762 145 return m_leftEndModif;
763 }
764
765 /*!
766 \brief Returns a reference to this Polymer's left end modif.
767 */
768 Modif &
769 Polymer::getLeftEndModifRef()
770 {
771 return m_leftEndModif;
772 }
773
774 /*!
775 \brief Returns true if this Polymer is modified at its left end.
776 */
777 bool
778 36 Polymer::isLeftEndModified() const
779 {
780 36 return m_leftEndModif.isValid();
781 }
782
783 // RIGHT END
784 /*!
785 \brief Sets this polymer's right end modification to \a name.
786
787 Returns true if a modification by \a name was found in the member polymer
788 chemistry definition's list of modifications and if that modification could be
789 set and its masses could be calculated successfully, otherwise returns false.
790
791 After setting the member data, the instance is validated and the result is set
792 to m_isValid.
793 */
794 bool
795 2 Polymer::setRightEndModifByName(const QString &name)
796 {
797
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if(name.isNull() || name.isEmpty())
798 {
799 // Reset the modif to nothing.
800 m_rightEndModif.clear();
801 }
802
803
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
804 {
805 qCritical()
806 << "Cannot search for Modif name if no PolChemDef is available.";
807 return false;
808 }
809
810 2 ModifCstSPtr modif_csp = mcsp_polChemDef->getModifCstSPtrByName(name);
811
812
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(modif_csp == nullptr)
813 {
814 qCritical() << "Failed to find a Modif by name " << name
815 << "in the polChemDef.";
816 return false;
817 }
818
819
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 m_rightEndModif = *modif_csp.get();
820
821
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if(m_rightEndModif.getPolChemDefCstSPtr() != mcsp_polChemDef)
822 qFatalStream()
823 << "Programming error. The pointers to PolChemDef cannot differ.";
824
825 2 ErrorList error_list;
826
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 m_isValid = validate(&error_list);
827
828
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(!m_isValid)
829 qCritical() << "Failed to validate the Polymer with errors:\n"
830 << Utils::joinErrorList(error_list, ", ");
831
832 2 return true;
833 4 }
834
835 /*!
836 \brief Sets this polymer's right end modification to \a modif.
837
838 Returns true if \a modif could validate successfully and if that modification
839 could be set and its masses could be calculated successfully, otherwise returns
840 false.
841
842 After setting the member data, the instance is validated and the result is set
843 to m_isValid.
844 */
845 bool
846 Polymer::setRightEndModif(const Modif &modif)
847 {
848 ErrorList error_list;
849
850 m_rightEndModif = modif;
851 m_rightEndModif.setPolChemDefCstSPtr(mcsp_polChemDef);
852
853 if(!m_rightEndModif.validate(&error_list))
854 {
855 qCritical() << "The Modif is not valid, with errors:"
856 << Utils::joinErrorList(error_list, ", ");
857 return false;
858 }
859
860 m_isValid = validate(&error_list);
861
862 if(!m_isValid)
863 qCritical() << "Failed to validate the Polymer with errors:\n"
864 << Utils::joinErrorList(error_list, ", ");
865
866 return true;
867 }
868
869 /*!
870 \brief Returns a const reference to this Polymer's right end modif.
871 */
872 const Modif &
873 79 Polymer::getRightEndModifCstRef() const
874 {
875 79 return m_rightEndModif;
876 }
877
878 /*!
879 \brief Returns a reference to this Polymer's right end modif.
880 */
881 Modif &
882 Polymer::getRightEndModifRef()
883 {
884 return m_rightEndModif;
885 }
886
887 /*!
888 \brief Returns true if this Polymer is modified at its right end.
889 */
890 bool
891 36 Polymer::isRightEndModified() const
892 {
893 36 return m_rightEndModif.isValid();
894 }
895
896 //////////////// THE CALCULATION OPTIONS /////////////////////
897
898 /*!
899 \brief Sets \a calc_options to the member datum.
900 */
901 void
902 Polymer::setCalcOptions(const CalcOptions &calc_options)
903 {
904 mp_calcOptions->initialize(calc_options);
905 }
906
907 /*!
908 \brief Sets \a calc_options to the member datum.
909 */
910 void
911 Polymer::setCalcOptions(const CalcOptions *calc_options)
912 {
913 mp_calcOptions->initialize(*calc_options);
914 }
915
916 /*!
917 \brief Returns a const reference to the member CalcOptions.
918 */
919 const CalcOptions &
920 Polymer::getCalcOptionsCstRef() const
921 {
922 return *mp_calcOptions;
923 }
924
925 /*!
926 \brief Returns a reference to the member CalcOptions.
927 */
928 const CalcOptions &
929 Polymer::getCalcOptionsRef()
930 {
931 return *mp_calcOptions;
932 }
933
934 /*!
935 \brief Returns a reference to the member CalcOptions.
936 */
937 CalcOptions *
938 Polymer::getCalcOptions()
939 {
940 // qDebug() << "Returning calculation options:" << mp_calcOptions->toString();
941 return mp_calcOptions;
942 }
943
944 // IONIZATION
945 /////////////////////////////
946
947 /*!
948 \brief Sets the member Ionizer instance to \a ionizer_p.
949
950 Calls the Polymer::setIonizer(const Ionizer &ionizer) by dereferencing
951 the pointer.
952 */
953 void
954 Polymer::setIonizer(const Ionizer *ionizer_p)
955 {
956 // qDebug() << "Setting ionizer:" << ionizer_p->toString();
957 setIonizer(*ionizer_p);
958 }
959
960 /*!
961 \brief Sets the member Ionizer instance to \a ionizer.
962
963 If presently ionized, this Polymer is first deionized. Then this Polymer
964 undergoes ionization according to the current ionization status of \a ionizer.
965 This is required so that we put ourselves in line with the contents of \a
966 ionizer.
967 */
968 void
969 8 Polymer::setIonizer(const Ionizer &ionizer)
970 {
971 // qDebug() << "Setting ionizer:" << ionizer.toString() << "The current masses are:"
972 // << m_mono << "-" << m_avg;
973
974 // Check if ionizer is valid.
975
976 8 ErrorList error_list;
977
978
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if(!ionizer.validate(&error_list))
979 qFatalStream()
980 << "Programming error. Trying to set a new Ionizer that does not "
981 "validate successfully, with errors:"
982 << Utils::joinErrorList(error_list);
983
984
3/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 7 times.
8 if(mp_ionizer->isIonized())
985 {
986 // qDebug() << "Before setting a new ionizer, undergo deionization.";
987
988
2/5
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(deionize() == Enums::IonizationOutcome::FAILED)
989 qFatalStream()
990 << "Programming error. Failed to deionize an ionized analyte.";
991 }
992
993 // qDebug() << "Now setting the ionizer to the member mp_ionizer.";
994
995
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 mp_ionizer->initialize(ionizer);
996
997 // We now have to put this Polymer in conformity with the contents of the
998 // new Ionizer. That is, we should ionize this Polymer according to the
999 // current state of mp_ionizer. Only do that if the current state is ionized,
1000 // otherwise the ionizer validation below will fail miserably.
1001
1002
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
8 if(mp_ionizer->currentStateCharge())
1003 {
1004 // qDebug()
1005 // << "The newly set mp_ionizer has a charge, which means it is ionized."
1006 // "put this Polymer in the right ionization state, matching the "
1007 // "current state of the mp_ionizer.";
1008
1009 Ionizer current_state_ionizer =
1010 mp_ionizer->makeIonizerWithCurrentStateData();
1011
1012 // Make sure the created ionizer is valid
1013 if(!current_state_ionizer.isValid())
1014 qFatal() << "The created ionizer is invalid.";
1015
1016 // qDebug() << "The created ionizer:" << current_state_ionizer.toString();
1017
1018 // Now use that current_state_ionizer to ionize this Polymer without
1019 // changing its internal Ionizer instance.
1020
1021 if(ionize(current_state_ionizer) == Enums::IonizationOutcome::FAILED)
1022 qFatalStream() << "Programming error. Failed to ionize the Polymer.";
1023 }
1024
1025 // qDebug() << "At this point the member masses:" << m_mono << "-" << m_avg;
1026
1027 // qDebug() << "Now returning.";
1028 // No use to validate because validation does not care of
1029 // optional Ionizer.
1030 8 }
1031
1032 /*!
1033 \brief Returns a const reference to the member Ionizer instance.
1034 */
1035 const Ionizer &
1036 11 Polymer::getIonizerCstRef() const
1037 {
1038 11 return *mp_ionizer;
1039 }
1040
1041 /*!
1042 \brief Returns a reference to the member Ionizer instance.
1043 */
1044 Ionizer &
1045 Polymer::getIonizerRef()
1046 {
1047 return *mp_ionizer;
1048 }
1049
1050 /*!
1051 \brief Returns a reference to the member Ionizer instance.
1052 */
1053 Ionizer *
1054 Polymer::getIonizer()
1055 {
1056 return mp_ionizer;
1057 }
1058
1059 /*!
1060 \brief Returns the result of the ionization process.
1061
1062 The ionization is performed by the member mp_ionizer and the masses
1063 that are updated are the member masses of this Polymer instance.
1064 */
1065 Enums::IonizationOutcome
1066 7 Polymer::ionize()
1067 {
1068 7 qDebug() << "The masses before ionization:" << m_mono << "-" << m_avg;
1069 7 Enums::IonizationOutcome ionization_outcome = mp_ionizer->ionize(m_mono, m_avg);
1070 7 qDebug() << "The masses after ionization:" << m_mono << "-" << m_avg;
1071 7 return ionization_outcome;
1072 }
1073
1074 /*!
1075 \brief Returns the result of the ionization process using \a ionizer.
1076
1077 The \a ionizer instance is used to perform the ionization and the masses
1078 that are updated are the member masses of this Polymer instance.
1079
1080 \sa Ionizer::ionize()
1081 */
1082 Enums::IonizationOutcome
1083 Polymer::ionize(const Ionizer &ionizer)
1084 {
1085 qDebug() << "The ionizer:" << ionizer.toString();
1086
1087 return ionizer.ionize(m_mono, m_avg);
1088 }
1089
1090 /*!
1091 \brief Returns the result of the deionization process.
1092
1093 The new masses, if a change occurred, are updated in the member monoisotopic and
1094 average masses.
1095 */
1096 Enums::IonizationOutcome
1097 4 Polymer::deionize()
1098 {
1099
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 return mp_ionizer->deionize(m_mono, m_avg);
1100 }
1101
1102 /*!
1103 \brief Returns the result of the deionization process using \a ionizer.
1104
1105 The new masses, if a change occurred, are updated in the member monoisotopic and
1106 average masses.
1107 */
1108 Enums::IonizationOutcome
1109 Polymer::deionize(const Ionizer &ionizer)
1110 {
1111 return ionizer.deionize(m_mono, m_avg);
1112 }
1113
1114 /*!
1115 \brief Sets \a mono and \a avg to the masses of an unionized Polymer analyte.
1116
1117 If the member Ionizer instance reports that this Polymer analyte is ionized,
1118 that Ionizer instance is first used to deionize this Polymer instance into
1119 copied monoisotopic and average mass values. Then these values are returned as
1120 the molecular masses of this Polymer instance.
1121
1122 \note Since no member data were touched, that this function is marked const.
1123
1124 Returns the process outcome.
1125 */
1126 Enums::IonizationOutcome
1127 1 Polymer::molecularMasses(double &mono, double &avg) const
1128 {
1129 1 double temp_mono = m_mono;
1130 1 double temp_avg = m_avg;
1131
1132 // If not ionized, returns Enums::IonizationOutcome::UNCHANGED which is not an
1133 // error.
1134 1 Enums::IonizationOutcome ionization_outcome =
1135 1 mp_ionizer->deionize(temp_mono, temp_avg);
1136
1137
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(ionization_outcome == Enums::IonizationOutcome::FAILED)
1138 {
1139 qCritical() << "Failed to deionize the analyte.";
1140 return ionization_outcome;
1141 }
1142
1143 // The masses of the deionized analyte are its Mr molecular mass.
1144 1 mono = temp_mono;
1145 1 avg = temp_avg;
1146
1147 1 return ionization_outcome;
1148 }
1149
1150 // CROSS-LINKS
1151 /////////////////////////////
1152
1153
1154 /*!
1155 \brief Returns the number of CrossLink instances in the member container.
1156 */
1157 std::size_t
1158 Polymer::crossLinkCount() const
1159 {
1160 return m_crossLinks.size();
1161 }
1162
1163 /*!
1164 \brief Returns a const reference to the container of CrossLink instances.
1165 */
1166 const std::vector<CrossLinkSPtr> &
1167 36 Polymer::getCrossLinksCstRef() const
1168 {
1169 36 return m_crossLinks;
1170 }
1171
1172 /*!
1173 \brief Returns a reference to the container of CrossLink instances.
1174 */
1175 std::vector<CrossLinkSPtr> &
1176 Polymer::getCrossLinksRef()
1177 {
1178 return m_crossLinks;
1179 }
1180
1181 /*!
1182 \brief Fills-in the \a indices container with indices of Monomer instances
1183 involved in cross-links found to be entirely contained in the \a start_index --
1184 \a stop_index sequence range.
1185 f
1186 This function iterates in all of this polymer's cross-links and checks, for
1187 each cross-link if:
1188
1189 \list
1190
1191 \li It is fully contained in the indices range [\a start_index, \a
1192 stop_index] (the indices of the monomer in between the two cross-linked monomers
1193 are added to \a indices)
1194
1195 \li It is only partially contained in the range (the \a partials counter is then
1196 incremented)
1197
1198 \li Not contained at all in the range (nothing is done).
1199
1200 \endlist
1201
1202 Returns true if at least one cross-link was found to be fully encompassed by
1203 the range, false otherwise.
1204
1205 \sa crossLinksInRange()
1206 */
1207 bool
1208 12 Polymer::crossLinkedMonomersIndicesInRange(std::size_t start_index,
1209 std::size_t stop_index,
1210 std::vector<std::size_t> &indices,
1211 std::size_t &partials) const
1212 {
1213 // qDebug() << "Now determining all the Monomer indices of Monomers involved
1214 // in "
1215 // "cross-links in the Sequence index range"
1216 // << start_index << "-" << stop_index;
1217
1218 // We are asked to fill-in a list of the indices of all the
1219 // monomers involved in cross-links that link any monomer in the
1220 // region delimited by start_index and stop_index.
1221
1222 12 bool one_fully_encompassed_was_found = false;
1223
1224 // Iterate in the list of cross-links set to *this polymer. For
1225 // each iterated cross-link, check if it is fully encompassed by
1226 // the region delimited by [startIndex--endIndex]. If so, get the
1227 // indices of the monomers involved in that cross-link and append
1228 // these (no duplicates) to the indexList passed as param to the
1229 // function.
1230
1231 // qDebug() << "Iterating in all the CrossLink instances of the Polymer.";
1232
1233
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 for(const CrossLinkCstSPtr cross_link_csp : m_crossLinks)
1234 {
1235 if(cross_link_csp == nullptr)
1236 qFatalStream() << "Programming error. The CrossLink is not more alive.";
1237
1238 // qDebug() << "Iterating in cross-link:" << cross_link_csp->toString();
1239
1240 std::size_t in_cross_link;
1241 std::size_t out_cross_link;
1242
1243 Enums::CrossLinkEncompassed result =
1244 cross_link_csp->isEncompassedByIndexRange(
1245 start_index, stop_index, in_cross_link, out_cross_link);
1246
1247 if(result == Enums::CrossLinkEncompassed::FULLY)
1248 {
1249 // qDebug() << "Cross-link:" << cross_link_csp->getCrossLinkerName()
1250 // << "is fully encompassed by range:" << start_index << "-"
1251 // << stop_index;
1252
1253 std::vector<std::size_t> temp_indices =
1254 cross_link_csp->continuumOfLocationsOfInclusiveSequenceMonomers(
1255 Enums::LocationType::INDEX);
1256
1257 QString indices_as_text;
1258 for(std::size_t index : temp_indices)
1259 indices_as_text += QString("%1;").arg(index);
1260
1261 // qDebug() << "Found" << temp_indices.size()
1262 // << "indices of all the Monomers inclusively contained in "
1263 // "the current CrossLink:"
1264 // << indices_as_text;
1265
1266 // Avoid duplicating indices in the target index
1267 // list. This will have the excellent side effect of
1268 // condensating into one single region a number of
1269 // contained cross-linked regions. For example, from the
1270 // Kunitz inhibitor, there are the following cross-links:
1271
1272 // 90 -- 187
1273 // 230 -- 280
1274 // 239 -- 263
1275 // 255 -- 276
1276 // 286 -- 336
1277 // 295 -- 319
1278 // 311 -- 332
1279
1280 // Thanks to our strategy below, the cross-links
1281 // 230 -- 280
1282 // 239 -- 263
1283 // 255 -- 276
1284 // become contained in one single region:
1285 // 230--276.
1286
1287 // Same for the cross-links
1288 // 286 -- 336
1289 // 295 -- 319
1290 // 311 -- 332
1291 // Which become contained in 286--336
1292
1293 for(const int index : temp_indices)
1294 {
1295 if(std::find(indices.begin(), indices.end(), index) ==
1296 indices.end())
1297 indices.push_back(index);
1298 }
1299
1300 one_fully_encompassed_was_found = true;
1301 }
1302 else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
1303 {
1304 ++partials;
1305 }
1306 }
1307 // End of
1308 // for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
1309
1310 12 return one_fully_encompassed_was_found;
1311 }
1312
1313 /*!
1314 \brief Fills-in the \a cross_links CrossLink container with cross-links found
1315 to be entirely contained in the \a start_index -- \a stop_index sequence range.
1316
1317 This function iterates in all of this polymer's cross-links and checks, for
1318 each cross-link if:
1319 \list
1320
1321 \li It is fully contained in the indices range [\a start_index, \a
1322 stop_index] (it is then added to \a cross_links)
1323
1324 \li It is only partially
1325 contained in the range (the \a partials counter is then incremented)
1326
1327 \li Not
1328 contained at all in the range (nothing is done).
1329
1330 \endlist
1331
1332 Returns true if at least one cross-link was found to be fully encompassed by
1333 the range, false otherwise.
1334
1335 \sa crossLinkedMonomersIndicesInRange()
1336 */
1337 bool
1338 Polymer::crossLinksInRange(std::size_t start_index,
1339 std::size_t stop_index,
1340 std::vector<CrossLinkSPtr> &cross_links,
1341 std::size_t &partials) const
1342 {
1343 // We are asked to return a list of all the cross-links that are
1344 // fully encompassed by the [start_index--stop_index] region.
1345
1346 bool one_fully_encompassed_was_found = false;
1347
1348 // Iterate in the list of cross-links set to *this polymer. For
1349 // each iterated cross-link, check if it is fully encompassed by
1350 // the [start_index--stop_index] range. If so simply add it to the
1351 // list of cross-links.
1352
1353 for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
1354 {
1355 if(cross_link_sp == nullptr)
1356 qFatalStream() << "Programming error. The CrossLink is not more alive.";
1357
1358 // qDebug() << "Cross-link:" << cross_link_csp;
1359 std::size_t in_cross_link;
1360 std::size_t out_cross_link;
1361
1362 Enums::CrossLinkEncompassed result =
1363 cross_link_sp->isEncompassedByIndexRange(
1364 start_index, stop_index, in_cross_link, out_cross_link);
1365
1366 if(result == Enums::CrossLinkEncompassed::FULLY)
1367 {
1368 // qDebug() << __FILE__ << __LINE__
1369 // << "Cross-link:" << cross_link_csp
1370 // << "is encompassed by region:"
1371 // << startIndex << "-" << endIndex;
1372
1373 cross_links.push_back(cross_link_sp);
1374
1375 one_fully_encompassed_was_found = true;
1376 }
1377 else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
1378 {
1379 ++partials;
1380 }
1381 }
1382
1383 return one_fully_encompassed_was_found;
1384 }
1385
1386 /*!
1387 \brief Searches for all the CrossLink instances that involve \a monomer_crp and
1388 returns a container with the index of each one of them in the member container
1389 of CrossLink instances.
1390 */
1391 std::vector<std::size_t>
1392 411 Polymer::crossLinkIndicesInvolvingMonomer(MonomerCstRPtr monomer_crp) const
1393 {
1394 411 std::vector<std::size_t> cross_link_indices;
1395
1396 // We will need iter, so use this loop.
1397
2/2
✓ Branch 0 taken 2877 times.
✓ Branch 1 taken 411 times.
3288 for(std::size_t iter = 0; iter < m_crossLinks.size(); ++iter)
1398 {
1399 2877 CrossLinkCstSPtr cross_link_csp = m_crossLinks.at(iter);
1400
1401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2877 times.
2877 if(cross_link_csp == nullptr)
1402 qFatalStream() << "Programming error. The CrossLink is not more alive.";
1403
1404
1/2
✓ Branch 1 taken 2877 times.
✗ Branch 2 not taken.
2877 for(const MonomerCstSPtr &monomer_csp :
1405
3/4
✓ Branch 1 taken 2877 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5754 times.
✓ Branch 4 taken 2877 times.
8631 cross_link_csp->getMonomersCstRef())
1406 {
1407
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 5710 times.
5754 if(monomer_csp.get() == monomer_crp)
1408 {
1409
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
5798 cross_link_indices.push_back(iter);
1410 }
1411 }
1412 2877 }
1413
1414 411 return cross_link_indices;
1415 }
1416
1417 /*!
1418 \brief Searches for all the CrossLink instances that involve \a monomer_sp and
1419 returns a container with all of them.
1420 */
1421 std::vector<std::size_t>
1422 Polymer::crossLinkIndicesInvolvingMonomer(MonomerSPtr monomer_sp) const
1423 {
1424 return crossLinkIndicesInvolvingMonomer(monomer_sp.get());
1425 }
1426
1427 /*!
1428 \brief Performs the actual cross-linking as described in \a cross_link_sp.
1429
1430 The chemical representation of the cross-link must have been previously defined
1431 in \a cross_link_sp.
1432
1433 Upon performing the actual cross-link, a Uuid string is generated to identify
1434 that cross-link unambiguously. That string is returned. If the cross-link fails,
1435 the empty string is returned.
1436
1437 Returns true upon success or false if the CrossLink did not not validate
1438 successfully.
1439
1440 \note Emits the \c{crossLinkChangedSignal(this)} signal to let interested
1441 parties know that the Polymer has seen its cross-linking state changed.
1442 */
1443 QString
1444 101 Polymer::crossLink(CrossLinkSPtr cross_link_sp)
1445 {
1446
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
1447 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
1448
1449 // This function must be called once all the members taking part
1450 // into the crossLink have been set.
1451
1452 101 ErrorList error_list;
1453
1454
2/4
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 101 times.
101 if(!cross_link_sp->validate(&error_list))
1455 {
1456 qCritical() << "Requesting uncross-linking with invalid cross-link.";
1457 return QString();
1458 }
1459
1460 // OK, from the perspective of the chemical modification of the
1461 // monomers involved in the crosslink, everything is fine.
1462
1463 // Now is the moment that we actually perform the crossLink : this
1464 // is done simply by adding *this crossLink to the list of
1465 // crossLinks that belongs to the polymer.
1466
1/2
✓ Branch 2 taken 101 times.
✗ Branch 3 not taken.
101 QString uuid = storeCrossLink(cross_link_sp);
1467
1468
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if(uuid.isEmpty())
1469 qFatalStream() << "Programming error. Failed to cross-link monomers.";
1470
1471 // If the crossLink dialog is open, inform it that it can refresh
1472 // the data.
1473
1/2
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
101 emit(crossLinkChangedSignal(this));
1474
1475 101 return uuid;
1476 202 }
1477
1478 /*!
1479 \brief Undoes the cross-link \a cross_link_sp.
1480
1481 Returns true upon success or false if the CrossLink does not validate
1482 successfully.
1483
1484 \note Emits the \c{crossLinkChangedSignal(this)} signal to let interested
1485 parties know that the Polymer has seen its cross-linking state changed.
1486 */
1487 bool
1488 Polymer::uncrossLink(CrossLinkSPtr cross_link_sp)
1489 {
1490 if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
1491 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
1492
1493 ErrorList error_list;
1494
1495 if(!cross_link_sp->validate(&error_list))
1496 qFatalStream() << "Requesting uncross-linking with invalid cross-link.";
1497
1498 // qDebug() << "The CrossLink validated succesfully. Now removing it.";
1499
1500 if(!removeCrossLink(cross_link_sp))
1501 qFatalStream() << "Programming error. Failed to remove CrossLink.";
1502
1503 // If the crossLink dialog is open, inform it that it can refresh
1504 // the data.
1505 emit(crossLinkChangedSignal(this));
1506
1507 return true;
1508 }
1509
1510 // MONOMER REMOVAL
1511 /////////////////////////////
1512 /*!
1513 \brief Prepares for the removal of \a monomer_csp from this polymer's sequence.
1514
1515 The challenge is that monomers might be involved in cross-links. In that
1516 case, removing a Monomer instance that was involved in a cross-link needs
1517 preparation: it needs first to be uncross-linked.
1518
1519 Returns the count of uncross-links that were performed.
1520
1521 \sa uncrossLink()
1522 */
1523 std::size_t
1524 Polymer::prepareMonomerRemoval(MonomerSPtr monomer_csp)
1525 {
1526 if(monomer_csp == nullptr)
1527 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
1528
1529 if(!m_crossLinks.size())
1530 {
1531 // qDebug() << "There are no cross-links, just return 0.";
1532 return 0;
1533 }
1534
1535 // We are asked to destroy **all** the crossLinks that involve the
1536 // 'monomer'.
1537 bool ok = false;
1538
1539 // std::size_t monomer_index = m_sequence.monomerIndex(monomer_csp, ok);
1540 m_sequence.monomerIndex(monomer_csp, ok);
1541 if(!ok)
1542 qFatalStream() << "Programming error.";
1543
1544 // qDebug() << "Preparing Monomer removal of"
1545 // << monomer_csp->getCode()
1546 // << "at polymer Sequence index:" << monomer_index;
1547
1548 // Now process all the CrossLinks in the member container and for each one
1549 // that involves the Monomer being removed, just uncrosslink.
1550
1551 // We cannot use the simple for(CrossLinkSPtr cross_link_sp : m_crossLinks)
1552 // range-based iteration because when we remove one CrossLink, we
1553 // invalidate all the iterators used internally to make the loop work.
1554
1555 // One strategy is to first store in a vector all the CrossLinks that
1556 // are found to involve the Monomer we are removing.
1557
1558 std::vector<CrossLinkSPtr> cross_links_to_un_cross_link;
1559
1560 // Iterate in the list of crossLinks, and for each cross-link check
1561 // if it involves 'monomer_csp'. If so, remove the cross-link from
1562 // the container.
1563
1564 for(CrossLinkSPtr cross_link_sp : m_crossLinks)
1565 {
1566 // qDebug()
1567 // << "Iterating in the cross-link container in Polymer:"
1568 // << cross_link_sp->getCrossLinkerName()
1569 // << ". Now check if that "
1570 // "cross-link contains the Monomer we are preparing the removal
1571 // of.";
1572
1573 bool ok = false;
1574 cross_link_sp->monomerIndex(monomer_csp, ok);
1575
1576 if(ok)
1577 {
1578 // qDebug() << "Yes, the iterated cross-link does contain the Monomer
1579 // "
1580 // "we are handling. Adding it to the list of cross-links
1581 // " "to un-cross-link.";
1582
1583 cross_links_to_un_cross_link.push_back(cross_link_sp);
1584 }
1585 }
1586
1587 std::size_t count_of_cross_links_to_remove =
1588 cross_links_to_un_cross_link.size();
1589
1590 // qDebug() << "There are" << count_of_cross_links_to_remove
1591 // << "cross-links to uncrosslink because they involved the Monomer
1592 // we "
1593 // "are preparing the removal of.";
1594
1595 // We can now safely iterate in the new container of cross-links to
1596 // be un-cross-linked.
1597
1598 std::size_t count_of_actually_un_crossed_links = 0;
1599
1600 for(CrossLinkSPtr cross_link_sp : cross_links_to_un_cross_link)
1601 {
1602 // Now un-cross-linking that cross-link
1603 uncrossLink(cross_link_sp);
1604
1605 // qDebug() << "Done un-cross-linking cross-link.";
1606
1607 ++count_of_actually_un_crossed_links;
1608 }
1609
1610 // qDebug() << "Finished iterating the the vector of cross-links and"
1611 // << count_of_actually_un_crossed_links
1612 // << "cross-links were un-cross-linked.";
1613
1614 // Sanity check
1615 if(count_of_cross_links_to_remove != count_of_actually_un_crossed_links)
1616 qFatalStream() << "Programming error.";
1617
1618 return count_of_actually_un_crossed_links;
1619 }
1620
1621 /*!
1622 \brief Removes the Monomer instance at \a index.
1623
1624 An index that is out of bounds is fatal.
1625
1626 The monomer is first uncross-linked (if it was).
1627
1628 Returns true if the removal was succesful, false otherwise.
1629 */
1630 bool
1631 Polymer::removeMonomerAt(std::size_t index)
1632 {
1633 // qDebug() << "Asking to remove Monomer at index" << index;
1634
1635 if(index >= size())
1636 qFatalStream() << "Programming error. Index is out of bounds.";
1637
1638 MonomerSPtr monomer_csp = m_sequence.getMonomerCstSPtrAt(index);
1639
1640 // qDebug() << "The Monomer being removed at index:" << index
1641 // << "is:" << monomer_csp->getName()
1642 // << "with modification status:" << monomer_csp->isModified();
1643
1644 prepareMonomerRemoval(monomer_csp);
1645
1646 // qDebug() << "Done removing the Monomer.";
1647
1648 // qDebug() << "Now asking the Sequence to remove the Monomer.";
1649
1650 return m_sequence.removeMonomerAt(index);
1651 }
1652
1653 // MASS CALCULATION FUNCTIONS
1654
1655 /*!
1656 \brief Calculates the masses of \a polymer_p.
1657
1658 The calculated masses are set to \a mono and \a avg.
1659
1660 If \a reset is true, \a mono and \a avg are reset to 0.0, otherwise they are
1661 left unchanged and incremented with the masses obtained from the calculation.
1662
1663 The calculation of the masses (monoisotopic and average) is configured in \a
1664 calc_options.
1665
1666 The ionization of the polymer is not accounted for in this function.
1667
1668 Returns true if all the calculation steps were performed succesfully, false
1669 otherwise.
1670
1671 \sa calculateMasses()
1672 */
1673 bool
1674 551 Polymer::calculateMasses(const Polymer *polymer_p,
1675 const CalcOptions &calc_options,
1676 double &mono,
1677 double &avg,
1678 bool reset)
1679 {
1680
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 551 times.
551 if(polymer_p == nullptr)
1681 qFatalStream() << "Programming error. Pointer cannot be nullptr.";
1682
1683
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 523 times.
551 if(reset)
1684 {
1685 // Reset the masses to 0.
1686 28 mono = 0;
1687 28 avg = 0;
1688 }
1689
1690 // The calc_options parameter holds a SequenceRanges instance
1691 // listing all the sequence_range of the different(if any) region
1692 // selections of the polymer sequence. This SequenceRanges is
1693 // never empty, as it should at least contain the pseudo-selection
1694 // of the sequence, that is [start of sequence, cursor index] or
1695 // the [-1, -1] values for whole sequence mass
1696 // calculation. Iterate in this SequenceRanges and for each item
1697 // call this function.
1698
1699 551 const IndexRangeCollection &index_range_collection =
1700 551 calc_options.getIndexRangeCollectionCstRef();
1701
1702
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 545 times.
551 if(!index_range_collection.size())
1703 {
1704
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 qCritical() << "Calculating masses for a Polymer with no SequenceRange.";
1705 6 return false;
1706 }
1707
1708 // For each SequenceRange item in the
1709 // calc_options.getIndexRangeCollectionCstRef() list of such items, perform
1710 // the mass calculation.
1711
1712
2/2
✓ Branch 2 taken 565 times.
✓ Branch 3 taken 545 times.
1110 foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
1713 {
1714
1/2
✓ Branch 1 taken 565 times.
✗ Branch 2 not taken.
565 IndexRange local_index_range;
1715
1/2
✓ Branch 1 taken 565 times.
✗ Branch 2 not taken.
565 local_index_range.initialize(*item);
1716
1717 // If the end value is greater than the polymer size, set it
1718 // to the polymer size.
1719
3/4
✓ Branch 1 taken 565 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 559 times.
565 if(local_index_range.m_stop >= (qsizetype)polymer_p->size())
1720
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 local_index_range.m_stop = polymer_p->size() - 1;
1721
1722 // qDebug() << "Iterating in SequenceRange:"
1723 // << QString("[%1-%2]")
1724 // .arg(local_index_range.start)
1725 // .arg(local_index_range.stop);
1726
1727 // First account for the residual chain masses.
1728
1729 // qDebug() << "calculateMasses: accounting for residual chain indices "
1730 // << "[" << item->m_start << "--" << item->m_stop << "]";
1731
1732 565 for(qsizetype iter = local_index_range.m_start;
1733
2/2
✓ Branch 0 taken 18768 times.
✓ Branch 1 taken 565 times.
19333 iter <= local_index_range.m_stop;
1734 ++iter)
1735 {
1736 // qDebug() << "Iterating in range index:" << iter;
1737
1738 18768 MonomerSPtr monomer_sp = std::const_pointer_cast<Monomer>(
1739
3/7
✓ Branch 1 taken 18768 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 18768 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 18768 times.
✗ Branch 7 not taken.
37536 polymer_p->getSequenceCstRef().getMonomerCstSPtrAt(iter));
1740
1741
3/4
✓ Branch 1 taken 18768 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2444 times.
✓ Branch 4 taken 16324 times.
18768 if(calc_options.isDeepCalculation())
1742 {
1743 // qDebug() << "Deep calculation or Enums::ChemicalEntity::MODIF
1744 // requested.";
1745
1746
2/6
✓ Branch 1 taken 2444 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2444 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4888 monomer_sp->calculateMasses(nullptr,
1747 calc_options.getMonomerEntities());
1748 }
1749
1750 // qDebug() << "Iterating in monomer:" << monomer_sp->getCode()
1751 // << "with masses:" << monomer_sp->getMass(Enums::MassType::MONO)
1752 // << "-" << monomer_sp->getMass(Enums::MassType::MONO);
1753
1754
1/2
✓ Branch 1 taken 18768 times.
✗ Branch 2 not taken.
18768 monomer_sp->accountMasses(mono, avg);
1755 18768 }
1756 565 }
1757
1758 // qDebug() << "After having accounted the masses of all the monomers:" <<
1759 // mono
1760 // << "-" << avg;
1761
1762 // Even if we are not in the residual chain loop, we have to account
1763 // for the crossLinks, if so requires it. The crossLinks are a
1764 // monomer chemical entity, but because it is unpractical to
1765 // calculate their ponderable contribution in the loop above, we
1766 // deal with them here. This is difficult stuff. In fact, the
1767 // crossLinks, which in reality belong to at least two monomers
1768 //(monomers can be engaged in more than a crossLink), are not
1769 // stored as properties in the monomers(contrary to monomer
1770 // modifications, for example). The crossLinks are stored in a list
1771 // of such instances in the polymer(m_crossLinkList of CrossLink
1772 // pointers). Now, the point is: if one of the monomers of a
1773 // crossLink is selected but not the other partners, then what
1774 // should be do about that crossLink accounting ?
1775
1776
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 538 times.
545 if(static_cast<int>(calc_options.getMonomerEntities()) &
1777 static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
1778 {
1779 // qDebug() << "Cross-links are to be accounted for.";
1780
1781 // We have to take into account the crossLinks. Hmmm... hard
1782 // task. The calculation is to be performed for the sequence
1783 // stretch from localStart to localEnd. We can iterate in the
1784 // crossLink list and for each crossLink check if it involves
1785 // monomers that *all* are contained in the sequence stretch
1786 //(or sequence stretches, that is a number of SequenceRange
1787 // items in the calc_options.coordinateList()) we're
1788 // calculating the mass of. If at least one monomer of any
1789 // crossLink is not contained in the [localStart--localEnd]
1790 // sequence stretch, than increment a count variable and do
1791 // not account the mass.
1792
1793 7 int partial_cross_links_count = 0;
1794
1795
2/2
✓ Branch 1 taken 43 times.
✓ Branch 2 taken 7 times.
50 for(CrossLinkSPtr cross_link_sp : polymer_p->m_crossLinks)
1796 {
1797
1798 43 std::size_t in_count;
1799 43 std::size_t out_count;
1800
1801
1/2
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
43 Enums::CrossLinkEncompassed result =
1802
2/4
✓ Branch 1 taken 43 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 43 times.
✗ Branch 5 not taken.
43 cross_link_sp->isEncompassedByIndexRangeCollection(
1803 calc_options.getIndexRangeCollectionCstRef(),
1804 in_count,
1805 out_count);
1806
1807
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 17 times.
43 if(result == Enums::CrossLinkEncompassed::FULLY)
1808 {
1809 // qDebug() << "CrossLink:"
1810 // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
1811 // << "is fully encompassed: accounting its masses.";
1812
1813 // The cross_link_sp is fully encompassed by our monomer
1814 // stretch, so we should take it into account.
1815
1816
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 cross_link_sp->accountMasses(mono, avg);
1817 }
1818
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2 times.
17 else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
1819 {
1820 // qDebug() << "CrossLink:"
1821 // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
1822 // << "is only partially encompassed: not accounting its"
1823 // "masses.";
1824
1825 2 ++partial_cross_links_count;
1826 }
1827 else
1828 {
1829 // qDebug()
1830 // << "CrossLink:"
1831 // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
1832 // << "is not encompassed at all: not accounting its masses.";
1833 }
1834 43 }
1835
1836 7 emit(polymer_p->crossLinksPartiallyEncompassedSignal(
1837 partial_cross_links_count));
1838 }
1839
1840 // qDebug() << "After having accounted the masses of all the cross-links:"
1841 // << mono << "-" << avg;
1842
1843 // We now have to account for the left/right cappings. However,
1844 // when there are multiple region selections(that is multiple
1845 // Coordinate elements in the calc_options.coordinateList()) it is
1846 // necessary to know if the user wants each of these SequenceRange
1847 // to be considered real oligomers(each one with its left/right
1848 // caps) or as residual chains. Thus there are two cases:
1849
1850 // 1. Each SequenceRange item should be considered an oligomer
1851 //(Enums::SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
1852 // the left and right caps should be accounted for. This is
1853 // typically the case when the user selects multiple regions to
1854 // compute the mass of cross-linked oligomers.
1855
1856 // 2. Each SequenceRange item should be considered a residual chain
1857 //(Enums::SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
1858 // one item should see its left and right caps accounted for. This
1859 // is typically the case when the user selects multiple regions
1860 // like it would select repeated sequence elements in a polymer
1861 // sequence: all the regions selected are treated as a single
1862 // oligomer.
1863
1864 // Holds the number of times the chemical entities are to be
1865 // accounted for.
1866 545 int times = 0;
1867
1868
1/2
✓ Branch 1 taken 545 times.
✗ Branch 2 not taken.
545 if(calc_options.getSelectionType() == Enums::SelectionType::RESIDUAL_CHAINS)
1869 {
1870 // qDebug() << __FILE__ << __LINE__
1871 // << "SELECTION_TYPE_RESIDUAL_CHAINS";
1872
1873 times = 1;
1874 }
1875 else
1876 {
1877 // qDebug() << __FILE__ << __LINE__
1878 // << "SELECTION_TYPE_OLIGOMERS";
1879
1880 545 times = calc_options.getIndexRangeCollectionCstRef().size();
1881 }
1882
1883 // Account for the left and right cap masses, if so required.
1884
2/2
✓ Branch 1 taken 470 times.
✓ Branch 2 taken 75 times.
545 if(static_cast<int>(calc_options.getCapType()) &
1885 static_cast<int>(Enums::CapType::LEFT))
1886 {
1887 470 bool result = Polymer::accountCappingMasses(
1888 polymer_p, Enums::CapType::LEFT, mono, avg, times);
1889 470 Q_ASSERT(result);
1890 }
1891
1892
2/2
✓ Branch 1 taken 470 times.
✓ Branch 2 taken 75 times.
545 if(static_cast<int>(calc_options.getCapType()) &
1893 static_cast<int>(Enums::CapType::RIGHT))
1894 {
1895 470 bool result = Polymer::accountCappingMasses(
1896 polymer_p, Enums::CapType::RIGHT, mono, avg, times);
1897
1898 470 Q_ASSERT(result);
1899 }
1900
1901 // Account for the left and right modification masses, if so
1902 // required and the region(s) require(s) it: we have to make it
1903 // clear if the selection encompasses indices 0(left end) and/or
1904 // polymerSize-1(right end).
1905
1906 // Note that if we are forced to take into account either or both
1907 // the left/right end modif, then even if the selected region does
1908 // not encompass the end(s), their modif(s) must be taken into
1909 // account.
1910
1911
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 541 times.
545 if(static_cast<int>(calc_options.getPolymerEntities()) &
1912 static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
1913 {
1914
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if(calc_options.getIndexRangeCollectionCstRef().encompassIndex(0) ||
1915 static_cast<int>(calc_options.getPolymerEntities()) &
1916 static_cast<int>(Enums::ChemicalEntity::FORCE_LEFT_END_MODIF))
1917 {
1918 // qDebug() << "Before accounting for LEFT_END_MODIF, masses:" << mono
1919 // << "-" << avg;
1920 4 Polymer::accountEndModifMasses(
1921 polymer_p, Enums::ChemicalEntity::LEFT_END_MODIF, mono, avg);
1922 // qDebug() << "After accounting for LEFT_END_MODIF, masses:" << mono
1923 // << "-" << avg;
1924 }
1925 }
1926
1927
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 543 times.
545 if(static_cast<int>(calc_options.getPolymerEntities()) &
1928 static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
1929 {
1930 2 if(calc_options.getIndexRangeCollectionCstRef().encompassIndex(
1931
1/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 polymer_p->size() - 1) ||
1932 static_cast<int>(calc_options.getPolymerEntities()) &
1933 static_cast<int>(Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF))
1934 {
1935 // qDebug() << "Before accounting for RIGHT_END_MODIF, masses:" <<
1936 // mono
1937 // << "-" << avg;
1938 2 Polymer::accountEndModifMasses(
1939 polymer_p, Enums::ChemicalEntity::RIGHT_END_MODIF, mono, avg);
1940 // qDebug() << "After accounting for RIGHT_END_MODIF, masses:" << mono
1941 // << "-" << avg;
1942 }
1943 }
1944
1945 // qDebug() << "CalculateMasses Mono:" << mono << "Avg:" << avg;
1946
1947 return true;
1948 }
1949
1950 /*!
1951 \overload calculateMasses()
1952
1953 \brief Calculates the masses of \a polymer_p.
1954
1955 The calculated masses are set to \a mono and \a avg. Prior to setting the
1956 masses to \a mono and \a avg, these masses are reset to 0.0.
1957
1958 The calculation of the masses (monoisotopic and average) is configured in \a
1959 calc_options.
1960
1961 After the masses are computed, the ionization described in \a ionizer is
1962 accounted for.
1963
1964 Returns true if all steps could be performed successfully, false otherwise.
1965
1966 \sa calculateMasses(), CalcOptions, Ionizer
1967 */
1968 bool
1969 Polymer::calculateMasses(const Polymer *polymer_p,
1970 const CalcOptions &calc_options,
1971 const Ionizer &ionizer,
1972 double &mono,
1973 double &avg)
1974 {
1975 if(!calculateMasses(polymer_p, calc_options, mono, avg, true))
1976 return false;
1977
1978 Ionizer local_ionizer_copy(ionizer);
1979
1980 // If the ionizer is valid use it.
1981 if(local_ionizer_copy.isValid())
1982 {
1983 if(local_ionizer_copy.ionize(mono, avg) ==
1984 Enums::IonizationOutcome::FAILED)
1985 {
1986 qCritical() << "Failed to ionize the Oligomer.";
1987 return false;
1988 }
1989 }
1990
1991 return true;
1992 }
1993
1994 /*!
1995 \overload calculateMasses()
1996
1997 \brief Calculates the masses of this polymer.
1998
1999 The calculated masses are set to this polymer's m_mono and m_avg members. If
2000 \a reset is true, these masses are first reset to 0.0, otherwise this
2001 polymer's masses are left unchanged and incremented with those obtained from
2002 the calculation.
2003
2004 The calculation of the masses (monoisotopic and average) is configured in \a
2005 calc_options.
2006
2007 No ionization is accounted for in this function.
2008
2009 Returns true.
2010
2011 \sa calculateMasses(), CalcOptions
2012 */
2013 bool
2014 28 Polymer::calculateMasses(const CalcOptions &calc_options, bool reset)
2015 {
2016 28 return calculateMasses(this, calc_options, m_mono, m_avg, reset);
2017 }
2018
2019 /*!
2020 \overload calculateMasses()
2021
2022 \brief Calculates the masses of this polymer.
2023
2024 The calculated masses are set to this polymer's m_mono and m_avg members.
2025 These masses are first reset to 0.0.
2026
2027 The calculation of the masses (monoisotopic and average) is configured in \a
2028 calc_options.
2029
2030 After the masses are computed, the ionization described in \a ionizer is
2031 accounted for.
2032
2033 Returns true if all steps could be performed successfully, false otherwise.
2034
2035 \sa calculateMasses(), CalcOptions, Ionizer
2036 */
2037 bool
2038 Polymer::calculateMasses(const CalcOptions &calc_options,
2039 const Ionizer &ionizer)
2040 {
2041 return calculateMasses(this, calc_options, ionizer, m_mono, m_avg);
2042 }
2043
2044 /*!
2045 \overload calculateMasses()
2046
2047 \brief Calculates the masses of this polymer.
2048
2049 The calculated masses are set to this polymer's m_mono and m_avg members.
2050 These masses are first reset to 0.0.
2051
2052 The calculation of the masses (monoisotopic and average) is configured in \a
2053 calc_options_p.
2054
2055 After the masses are computed, the ionization described in \a ionizer_p is
2056 accounted for.
2057
2058 Returns true if all steps could be performed successfully, false otherwise.
2059
2060 \sa calculateMasses(), CalcOptions, Ionizer
2061 */
2062 bool
2063 Polymer::calculateMasses(const CalcOptions *calc_options_p,
2064 const Ionizer *ionizer_p)
2065 {
2066 bool result =
2067 calculateMasses(this, *calc_options_p, *ionizer_p, m_mono, m_avg);
2068 // qDebug() << "After computation of the masses:" << m_mono << "-"
2069 // << m_avg;
2070 return result;
2071 }
2072
2073 /*!
2074 \overload calculateMasses()
2075
2076 \brief Calculates the masses of this polymer.
2077
2078 The calculated masses are set to this polymer's m_mono and m_avg members.
2079 These masses are first reset to 0.0.
2080
2081 The calculation of the masses (monoisotopic and average) is configured in the
2082 member CalcOptions object.
2083
2084 After the masses are computed, the ionization described in the member Ionizer
2085 object is accounted for.
2086
2087 Returns true if all steps could be performed successfully, false otherwise.
2088
2089 \sa calculateMasses(), CalcOptions, Ionizer
2090 */
2091 bool
2092 Polymer::calculateMasses()
2093 {
2094 return calculateMasses(*mp_calcOptions, *mp_ionizer);
2095 }
2096
2097 /*!
2098 \brief Accounts the masses of this polymer.
2099
2100 Accounting masses means calculating masses and adding the results to
2101 objects. Here the masses of this polymer are calculated and added to those of
2102 this polymer.
2103
2104 The calculation of the masses (monoisotopic and average) is configured in \a
2105 calc_options.
2106
2107 Returns true upon success, false otherwise.
2108
2109 \sa CalcOptions
2110 */
2111 bool
2112 Polymer::accountMasses(const CalcOptions &calc_options)
2113 {
2114 // We do not want to reset masses prior to calculating the masses
2115 // because we are accounting them in the polymer itself.
2116 return calculateMasses(calc_options, false);
2117 }
2118
2119 /*!
2120 \overload accountMasses()
2121
2122 \brief Accounts the masses of \a polymer_p.
2123
2124 \a polymer_p cannot be nullptr.
2125
2126 Accounting masses means calculating masses and adding the results to
2127 objects. Here the masses of this polymer are calculated and set to \a mono and
2128 \a avg.
2129
2130 The calculation of the masses (monoisotopic and average) is configured in \a
2131 calc_options.
2132
2133 \sa CalcOptions
2134 */
2135 bool
2136 517 Polymer::accountMasses(const Polymer *polymer_p,
2137 const CalcOptions &calc_options,
2138 double &mono,
2139 double &avg)
2140 {
2141 517 qDebug().noquote() << "Going to account masses for calculation options:"
2142 << calc_options.toString();
2143
2144 // We do not want to reset masses prior to calculating the masses
2145 // because we are not accounting them in the polymer itself.
2146 517 return calculateMasses(polymer_p, calc_options, mono, avg, /*reset*/ false);
2147 }
2148
2149 /*!
2150 \brief Accounts for the mass of the end caps.
2151
2152 The polymer sequence is actually a chain of monomers (that is, residues). In
2153 order to compute the mass of the polymer in its finished state, it is
2154 necessary to add the masses of its end caps (typically, a proton and a
2155 hydroxyl group in protein chemistry, respectively capping the N-terminus and
2156 the C-terminus).
2157
2158 The mass of the the left end cap is added to the monoisotopic and average
2159 masses of this polymer if (\a cap_type & Enums::CapType::LEFT). The mass of the
2160 the right end cap is added to the monoisotopic and average masses of this
2161 polymer if (\a cap_type & Enums::CapType::RIGHT).
2162
2163 The masses of the caps are multiplied by \a times before accounting them to
2164 this polymer's masses.
2165
2166 Returns true.
2167 */
2168 bool
2169 Polymer::accountCappingMasses(Enums::CapType cap_type, int times)
2170 {
2171 return accountCappingMasses(this, cap_type, m_mono, m_avg, times);
2172 }
2173
2174 /*!
2175 \brief Accounts for the \a{polymer_p}'s end caps masses to \a mono and
2176 \a avg.
2177
2178 \a polymer_p cannot be nullptr.
2179
2180 The polymer sequence is actually a chain of monomers (that is, residues). In
2181 order to compute the mass of the polymer in its finished state, it is
2182 necessary to add the masses of its end caps (typically, a proton and a
2183 hydroxyl group in protein chemistry, respectively capping the N-terminus and
2184 the C-terminus).
2185
2186 The mass of the the left end cap is added to the \a mono and \a avg masses if
2187 (\a cap_type & Enums::CapType::LEFT). The mass of the the right end cap is added
2188 to the \a mono and \a avg masses if (\a cap_type & Enums::CapType::RIGHT).
2189
2190 The masses of the caps are multiplied by \a times before accounting them to
2191 \a mono and \a avg.
2192 Returns true.
2193 */
2194 bool
2195 940 Polymer::accountCappingMasses(const Polymer *polymer_p,
2196 Enums::CapType cap_type,
2197 double &mono,
2198 double &avg,
2199 int times)
2200 {
2201
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 940 times.
940 if(polymer_p == nullptr)
2202 qFatalStream() << "Programming error. Pointer cannot be nullptr.";
2203
2204 940 IsotopicDataCstSPtr isotopic_data_csp =
2205 940 polymer_p->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr();
2206
2207
1/2
✓ Branch 1 taken 940 times.
✗ Branch 2 not taken.
940 Formula formula;
2208
2209
2/2
✓ Branch 0 taken 470 times.
✓ Branch 1 taken 470 times.
940 if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::LEFT))
2210 {
2211
3/6
✓ Branch 1 taken 470 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 470 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 470 times.
✗ Branch 8 not taken.
470 formula = polymer_p->getPolChemDefCstSPtr()->getLeftCap();
2212 }
2213
1/2
✓ Branch 0 taken 470 times.
✗ Branch 1 not taken.
470 else if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::RIGHT))
2214 {
2215
3/6
✓ Branch 1 taken 470 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 470 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 470 times.
✗ Branch 8 not taken.
470 formula = polymer_p->getPolChemDefCstSPtr()->getRightCap();
2216 }
2217 else if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::NONE))
2218 return true;
2219 else
2220 qFatalStream() << "Programming error";
2221
2222 940 bool ok = false;
2223
1/2
✓ Branch 2 taken 940 times.
✗ Branch 3 not taken.
940 formula.accountMasses(ok, isotopic_data_csp, mono, avg, times);
2224 940 if(!ok)
2225 return false;
2226
2227 return true;
2228
1/2
✓ Branch 1 taken 940 times.
✗ Branch 2 not taken.
1880 }
2229
2230 /*!
2231 \brief Accounts for this Polymer instance's end modifications masses as
2232 defined by \a polymer_chem_entities.
2233
2234 \sa accountEndModifMasses()
2235 */
2236 void
2237 Polymer::accountEndModifMasses(Enums::ChemicalEntity polymer_chem_entities)
2238 {
2239 return accountEndModifMasses(this, polymer_chem_entities, m_mono, m_avg);
2240 }
2241
2242 /*!
2243 \brief Accounts for the \a{polymer_p}'s masses as defined by
2244 \a polymer_chem_entities.
2245
2246 The end modifications masses are accounted for into the \a mono and \a avg
2247 masses without prior resetting of them.
2248
2249 If \c{(polymer_chem_entities & Enums::ChemicalEntity::LEFT_END_MODIF)}, the left
2250 end modification masses are accounted for; if \c{(polymer_chem_entities &
2251 Enums::ChemicalEntity::RIGHT_END_MODIF)} the right end modification masses are
2252 accounted for.
2253 */
2254 void
2255 70 Polymer::accountEndModifMasses(const Polymer *polymer_p,
2256 Enums::ChemicalEntity polymer_chem_entities,
2257 double &mono,
2258 double &avg)
2259 {
2260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if(polymer_p == nullptr)
2261 qFatalStream() << "Programming error. Pointer cannot be nullptr.";
2262
2263 // Make a safe copy of the polymer's left/right end modif and use it
2264 // for doing the calculation INTO the 'mono' and 'avg' variables.
2265
2266
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 18 times.
70 if(static_cast<int>(polymer_chem_entities) &
2267 static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
2268 {
2269 52 Modif modif(polymer_p->getLeftEndModifCstRef());
2270
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 modif.accountMasses(mono, avg);
2271 52 }
2272
2273
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 52 times.
70 if(static_cast<int>(polymer_chem_entities) &
2274 static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
2275 {
2276 18 Modif modif(polymer_p->getRightEndModifCstRef());
2277
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 modif.accountMasses(mono, avg);
2278 18 }
2279 70 }
2280
2281 /*!
2282 \brief Returns the Polymer's mass of the type defined by \a mass_type.
2283 */
2284 double
2285 44 Polymer::getMass(Enums::MassType mass_type) const
2286 {
2287
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if(mass_type != Enums::MassType::MONO && mass_type != Enums::MassType::AVG)
2288 qFatalStream() << "The mass_type needs to be either MONO or AVG.";
2289
2290
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 22 times.
44 if(mass_type == Enums::MassType::MONO)
2291 22 return m_mono;
2292
2293 22 return m_avg;
2294 }
2295
2296 /*!
2297 \brief Returns a reference to the Polymer's mass of the type defined by \a
2298 mass_type.
2299 */
2300 double &
2301 Polymer::getMassRef(Enums::MassType mass_type)
2302 {
2303 if(mass_type != Enums::MassType::MONO && mass_type != Enums::MassType::AVG)
2304 qFatalStream() << "The mass_type needs to be either MONO or AVG.";
2305
2306 if(mass_type == Enums::MassType::MONO)
2307 return m_mono;
2308
2309 return m_avg;
2310 }
2311
2312 // ELEMENTAL COMPOSITION
2313 ////////////////////////////
2314
2315
2316 /*!
2317 \brief Determines the elemental composition of this polymer.
2318
2319 The elemental composition is computed by looking into the core chemical
2320 entities of the polymer, like the monomers, the modifications, but also by
2321 accounting for the Ionizer \a ionizer, and the CalcOptions \a
2322 calc_options. The sequence elements that are accounted for are described in \l
2323 IndexRange instances stored in \a index_range_collection.
2324
2325 \a ionizer is checked for validity and if valid, it is accounted for.
2326
2327 Returns the elemental composition.
2328
2329 \sa elementalComposition(), IndexRange, Ionizer, CalcOptions
2330 */
2331 QString
2332 428 Polymer::elementalComposition(
2333 const IndexRangeCollection &index_range_collection,
2334 const CalcOptions &calc_options,
2335 const Ionizer &ionizer) const
2336 {
2337 428 IsotopicDataCstSPtr isotopic_data_csp =
2338 428 mcsp_polChemDef->getIsotopicDataCstSPtr();
2339
2340 // Iterate in all the oligomers that are encompassed in the
2341 // IndexRangeCollection.
2342
2343 428 qDebug() << "The index range collection has" << index_range_collection.size()
2344 << "IndexRange objects";
2345
2346
1/2
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
428 Formula formula;
2347
2348
3/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 428 times.
✓ Branch 5 taken 428 times.
856 foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
2349 {
2350 428 qDebug() << "Iterating into IndexRange (indices):"
2351 << item->indicesAsText();
2352
2353
2/2
✓ Branch 0 taken 7354 times.
✓ Branch 1 taken 428 times.
7782 for(qsizetype iter = item->m_start; iter <= item->m_stop; ++iter)
2354 {
2355
1/2
✓ Branch 1 taken 7354 times.
✗ Branch 2 not taken.
7354 const MonomerSPtr monomer_csp = m_sequence.getMonomerCstSPtrAt(iter);
2356
2357
2/4
✓ Branch 1 taken 7354 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7354 times.
✗ Branch 5 not taken.
7354 formula.setActionFormula(monomer_csp->getFormula());
2358
2359
3/6
✓ Branch 2 taken 7354 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7354 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7354 times.
14708 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2360 qFatalStream()
2361 << "Programming error. Accounting symbols should not fail "
2362 "at this point.";
2363
2364
3/4
✓ Branch 1 taken 7354 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 535 times.
✓ Branch 4 taken 6819 times.
7354 if(static_cast<int>(calc_options.getMonomerEntities()) &
2365 static_cast<int>(Enums::ChemicalEntity::MODIF))
2366 {
2367
3/4
✓ Branch 1 taken 535 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 535 times.
542 for(const ModifSPtr &modif_sp : monomer_csp->getModifsCstRef())
2368 {
2369 7 qDebug() << "Before accounting for Monomer Modif"
2370 << modif_sp->getName() << "elemental composition:"
2371 << formula.elementalComposition();
2372
2373
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 formula.setActionFormula(
2374
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 modif_sp->formula(/*with_title*/ false));
2375
2376 // Incrementally account for the new formula in the same
2377 // atomcount list in the formula.
2378
3/6
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
14 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2379 qFatalStream()
2380 << "Programming error. Accounting symbols should "
2381 "not fail at this point.";
2382
2383 7 qDebug() << "After accounting for Monomer Modif"
2384 << modif_sp->getName() << "elemental composition:"
2385 << formula.elementalComposition();
2386 }
2387
2388 qDebug() << "After having accounted for monomer:"
2389 << monomer_csp->getCode() << "the formula has become:"
2390 << formula.elementalComposition();
2391 }
2392 7354 }
2393 }
2394
2395 428 qDebug() << "Formula after accounting for all the residual chains:"
2396 << formula.elementalComposition();
2397
2398 // We now have to account for the left/right cappings. However,
2399 // when there are multiple region selections(that is multiple
2400 // Coordinate elements in the calc_options.coordinateList()) it is
2401 // necessary to know if the user wants each of these SequenceRange
2402 // to be considered real oligomers(each one with its left/right
2403 // caps) or as residual chains. Thus there are two cases:
2404
2405 // 1. Each SequenceRange item should be considered an oligomer
2406 //(Enums::SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
2407 // the left and right caps should be accounted for. This is
2408 // typically the case when the user selects multiple regions to
2409 // compute the mass of cross-linked oligomers.
2410
2411 // 2. Each SequenceRange item should be considered a residual chain
2412 //(Enums::SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
2413 // one item should see its left and right caps accounted for. This
2414 // is typically the case when the user selects multiple regions
2415 // like it would select repeated sequence elements in a polymer
2416 // sequence: all the regions selected are treated as a single
2417 // oligomer.
2418
2419 // Holds the number of times the chemical entities are to be
2420 // accounted for.
2421 428 int times = 0;
2422
2423
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 428 times.
✗ Branch 4 not taken.
428 if(calc_options.getSelectionType() == Enums::SelectionType::RESIDUAL_CHAINS)
2424 {
2425 times = 1;
2426 // qDebug() << "Enums::SelectionType::RESIDUAL_CHAINS ; times:" << times;
2427 }
2428
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 428 times.
✗ Branch 4 not taken.
428 else if(calc_options.getSelectionType() == Enums::SelectionType::OLIGOMERS)
2429 {
2430
1/2
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
428 times = index_range_collection.size();
2431
2432 // qDebug() << "Enums::SelectionType::OLIGOMERS ; times:" << times;
2433 }
2434 else
2435 qFatalStream()
2436 << "Programming error. Cannot be that this point is reached.";
2437
2438 // Account for the left and right cap masses, if so required.
2439
2440
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 428 times.
✗ Branch 4 not taken.
428 if(static_cast<int>(calc_options.getCapType()) &
2441 static_cast<int>(Enums::CapType::LEFT))
2442 {
2443
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 428 times.
✗ Branch 5 not taken.
428 formula.setActionFormula(mcsp_polChemDef->getLeftCap());
2444
2445 // Incrementally account for the new formula in the same
2446 // atomcount list in the formula.
2447
3/6
✓ Branch 2 taken 428 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 428 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 428 times.
856 if(!formula.accountSymbolCounts(isotopic_data_csp, times))
2448 qFatalStream() << "Programming error.";
2449
2450 qDebug() << "Formula after accounting left cap:"
2451 << formula.elementalComposition();
2452 }
2453
2454
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 428 times.
✗ Branch 4 not taken.
428 if(static_cast<int>(calc_options.getCapType()) &
2455 static_cast<int>(Enums::CapType::RIGHT))
2456 {
2457
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 428 times.
✗ Branch 5 not taken.
428 formula.setActionFormula(mcsp_polChemDef->getRightCap());
2458
2459 // Incrementally account for the new formula in the same
2460 // atomcount list in the formula.
2461
3/6
✓ Branch 2 taken 428 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 428 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 428 times.
856 if(!formula.accountSymbolCounts(isotopic_data_csp, times))
2462 qFatalStream() << "Programming error.";
2463
2464 qDebug() << "Formula after accounting right cap:"
2465 << formula.elementalComposition();
2466 }
2467
2468 // Account for the left and right modification masses, if so
2469 // required and the region(s) require(s) it: we have to make it
2470 // clear if the selection encompasses indices 0(left end) and/or
2471 // polymerSize-1(right end).
2472
2473
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 428 times.
428 if(static_cast<int>(calc_options.getPolymerEntities()) &
2474 static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
2475 {
2476 if(index_range_collection.encompassIndex(0) ||
2477 static_cast<int>(calc_options.getPolymerEntities()) &
2478 static_cast<int>(Enums::ChemicalEntity::FORCE_LEFT_END_MODIF))
2479 {
2480 formula.setActionFormula(
2481 m_leftEndModif.formula(/*with_title*/ false));
2482
2483 // qDebug() << "Accounting for left end modif:"
2484 // << m_leftEndModif.getName()
2485 // << "Formula before accounting it:"
2486 // << formula.elementalComposition();
2487
2488 // Incrementally account for the new formula in the same
2489 // atomcount list in the formula.
2490 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2491 qFatalStream() << "Programming error.";
2492
2493 // qDebug() << "Formula after accounting left end modif:"
2494 // << formula.elementalComposition();
2495 }
2496 }
2497
2498
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 428 times.
428 if(static_cast<int>(calc_options.getPolymerEntities()) &
2499 static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
2500 {
2501 if(index_range_collection.encompassIndex(size() - 1) ||
2502 static_cast<int>(calc_options.getPolymerEntities()) &
2503 static_cast<int>(Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF))
2504 {
2505 formula.setActionFormula(
2506 m_rightEndModif.formula(/*with_title*/ false));
2507
2508 // qDebug() << "Accounting for right end modif:"
2509 // << m_rightEndModif.getName();
2510
2511 // Incrementally account for the new formula in the same
2512 // atomcount list in the formula.
2513 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2514 qFatalStream() << "Programming error.";
2515
2516 // qDebug() << "Formula after accounting left end modif:"
2517 // << formula.elementalComposition();
2518 }
2519 }
2520
2521 // At this point we should not forget if the user asks to take into
2522 // account the cross-links... However, BE CAREFUL that cross-links
2523 // can only be taken into account if all the partners of a given
2524 // cross-link are actually encompassed into the selection.
2525
2526
3/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 250 times.
✓ Branch 4 taken 178 times.
428 if(static_cast<int>(calc_options.getMonomerEntities()) &
2527 static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
2528 {
2529
2/2
✓ Branch 0 taken 1750 times.
✓ Branch 1 taken 250 times.
2000 for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
2530 {
2531 1750 std::size_t in_count;
2532 1750 std::size_t out_count;
2533
2534
3/4
✓ Branch 1 taken 1750 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 1742 times.
1750 if(cross_link_sp->isEncompassedByIndexRangeCollection(
2535 index_range_collection, in_count, out_count) ==
2536 Enums::CrossLinkEncompassed::FULLY)
2537 {
2538 // The crossLink is fully encompassed by our monomer
2539 // stretch, so we should take it into account.
2540
2541 8 qDebug() << "Accounting for fully encompassed cross-link:"
2542 << cross_link_sp->getCrossLinkerCstSPtr()->getName();
2543
2544
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
16 if(!cross_link_sp->getCrossLinkerCstSPtr()
2545
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
8 ->getFormula()
2546
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 .isEmpty())
2547 {
2548 formula.setActionFormula(
2549 cross_link_sp->getCrossLinkerCstSPtr()->getFormula());
2550
2551 qDebug()
2552 << "Cross-linker formula:"
2553 << cross_link_sp->getCrossLinkerCstSPtr()->getFormula();
2554
2555 // Incrementally account for the new formula in the same
2556 // atomcount list in the formula.
2557 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2558 qFatalStream() << "Programming error.";
2559 }
2560
2561 // And now each modification that belongs to the
2562 // crosslinker.
2563
2564
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
32 for(const ModifCstSPtr &modif_csp :
2565
5/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 16 times.
✓ Branch 9 taken 8 times.
32 cross_link_sp->getCrossLinkerCstSPtr()->getModifsCstRef())
2566 {
2567 16 qDebug() << "Cross-linker's modif formula:"
2568 << modif_csp->formula(/*with_title*/ false);
2569
2570
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 formula.setActionFormula(
2571
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 modif_csp->formula(/*with_title*/ false));
2572
2573 // Incrementally account for the new formula in the same
2574 // atomcount list in the formula.
2575
3/6
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
32 if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
2576 qFatalStream() << "Programming error.";
2577 }
2578 }
2579 // End of
2580 // if(cross_link_sp->encompassedBy(sequence_ranges) ==
2581 // Enums::CrossLinkEncompassed::FULLY)
2582 }
2583 // End of
2584 // for (const CrossLinkSPtr &cross_link_sp : m_crossLinks)
2585 }
2586 // End of
2587 // if(static_cast<int>(calc_options.getMonomerEntities()) &
2588 // static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
2589
2590 // Attention, only account for the Ionizer if it is valid !!!
2591
2/4
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 428 times.
428 if(ionizer.isValid())
2592 {
2593 // The ionizer passed as param. Do not forget to take into account the
2594 // level and not the compound (nominalCharge * level)!
2595 formula.setActionFormula(ionizer.getFormulaCstRef().getActionFormula());
2596
2597 // Incrementally account for the new formula in the same
2598 // atomcount list in the formula.
2599 if(!formula.accountSymbolCounts(isotopic_data_csp, ionizer.getLevel()))
2600 qFatalStream() << "Programming error.";
2601
2602 qDebug() << "Formula after accounting ionization: "
2603 << formula.elementalComposition();
2604 }
2605
2606 #if 0
2607
2608 // As of 20250925, we remove this block because this introduced unexpected ionization
2609 // if the user of this function has called it on purpose with an invalid ionizer.
2610 else if(mp_ionizer->isValid())
2611 {
2612 // The member ionizer. Do not forget to take into account the
2613 // level and not the compound (nominalCharge * level)!
2614 formula.setActionFormula(
2615 mp_ionizer->getFormulaCstRef().getActionFormula());
2616
2617 // Incrementally account for the new formula in the same
2618 // atomcount list in the formula.
2619 if(!formula.accountSymbolCounts(isotopic_data_csp,
2620 mp_ionizer->getLevel()))
2621 qFatalStream() << "Programming error.";
2622
2623 qDebug() << "Formula after accounting ionization: "
2624 << formula.elementalComposition();
2625 }
2626 #endif
2627
2628 428 qDebug() << "Returning Formula: " << formula.elementalComposition();
2629
2630
1/2
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
428 return formula.elementalComposition();
2631
1/2
✓ Branch 1 taken 428 times.
✗ Branch 2 not taken.
856 }
2632
2633 /*!
2634 \ *brief Determines the elemental composition of this polymer.
2635
2636 The elemental composition is computed by looking into the core chemical
2637 entities of the polymer, like the monomers, the modifications, and the
2638 CalcOptions \a calc_options. The sequence elements that are accounted for are
2639 described in \l IndexRange instances stored in \a index_range_collection.
2640
2641 The member ionizer is checked for validity and if valid is taken into account.
2642
2643 Returns the elemental composition.
2644
2645 \sa elementalComposition(), IndexRange, Ionizer, CalcOptions
2646 */
2647 QString
2648 Polymer::elementalComposition(
2649 const IndexRangeCollection &index_range_collection,
2650 const CalcOptions &calc_options) const
2651 {
2652 return elementalComposition(
2653 index_range_collection, calc_options, *mp_ionizer);
2654 }
2655
2656 /*!
2657 \overload
2658 \brief Determines the elemental composition of this polymer.
2659
2660 The elemental composition is computed by looking into the core chemical
2661 entities of the polymer, like the monomers, the modifications, but also by
2662 accounting for the \l Ionizer \a ionizer, and the \l CalcOptions \a
2663 calc_options.
2664
2665 The IndexRange instances of the Polymer for which the elemental composition is
2666 to be computed are found in the IndexRangeCollection instance of \a
2667 calc_options.
2668
2669 Returns the elemental composition.
2670
2671 \sa elementalComposition()
2672 */
2673 QString
2674 428 Polymer::elementalComposition(const CalcOptions &calc_options,
2675 const Ionizer &ionizer) const
2676 {
2677 428 return elementalComposition(
2678 428 calc_options.getIndexRangeCollectionCstRef(), calc_options, ionizer);
2679 }
2680
2681 /*!
2682 \overload
2683 \brief Determines the elemental composition of this polymer.
2684
2685 The elemental composition is computed by looking into the core chemical
2686 entities of the polymer, like the monomers, the modifications, but also by
2687 accounting for the \l CalcOptions \a calc_options.
2688
2689 The IndexRange instances of the Polymer for which the elemental composition
2690 is to be computed are found in the IndexRangeCollection instance of \a
2691 calc_options.
2692
2693 If the member ionizer is valid, it is accounted for.
2694
2695 Returns the elemental composition.
2696
2697 \sa elementalComposition()
2698 */
2699 QString
2700 Polymer::elementalComposition(const CalcOptions &calc_options) const
2701 {
2702 return elementalComposition(
2703 calc_options.getIndexRangeCollectionCstRef(), calc_options, *mp_ionizer);
2704 }
2705
2706 /*!
2707 \brief Parses the XML \a element representing a sequence of monomer codes.
2708
2709 We are getting this: \c{<codes>MEFEEGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>}
2710 about which we need to create \l{Monomer}s and add them to this polymer's
2711 \l{Sequence}.
2712
2713 Returns true if parsing and conversion of the text to a monomer list
2714 were successful, false otherwise.
2715
2716 \sa
2717 \sa elementalComposition(), Sequence::makeMonomers()
2718 */
2719 bool
2720 129 Polymer::renderXmlCodesElement(const QDomElement &element)
2721 {
2722 129 QString sequence;
2723
2724 // We are getting this:
2725 // <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
2726 // We have to make monomers and add them to the list of monomers.
2727
2728
2/4
✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 129 times.
258 if(element.tagName() != "codes")
2729 {
2730 qCritical() << "Expected <codes> element not found.";
2731 return false;
2732 }
2733
2734
1/2
✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
129 std::vector<std::size_t> failing_indices;
2735
2736 129 qDebug() << "Now appending the <codes> sequence:" << element.text();
2737
2738
3/6
✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 129 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 129 times.
129 if(m_sequence.appendSequence(element.text(), failing_indices) == -1)
2739 {
2740 qCritical() << "The obtained sequence could not translate into full "
2741 "Monomer instances:"
2742 << element.text();
2743 return false;
2744 }
2745
2746 return true;
2747 258 }
2748
2749 /*!
2750 \brief Extracts the name of the polymer chemistry definition from the \a
2751 file_path polymer sequence file.
2752
2753 Returns the polymer chemistry definition name.
2754 */
2755 QString
2756 Polymer::xmlPolymerFileGetPolChemDefName(const QString &file_path)
2757 {
2758 QDomDocument doc("polSeqData");
2759 QDomElement element;
2760 QDomElement child;
2761 QDomElement indentedChild;
2762
2763 QFile file(file_path);
2764
2765 /*
2766 <polseqdata>
2767 <polchemdef_name>protein</polchemdef_name>
2768 ...
2769 */
2770
2771 if(!file.open(QIODevice::ReadOnly))
2772 return QString("");
2773
2774 if(!doc.setContent(&file))
2775 {
2776 file.close();
2777 return QString("");
2778 }
2779
2780 file.close();
2781
2782 element = doc.documentElement();
2783
2784 if(element.tagName() != "polseqdata")
2785 {
2786 qDebug() << "Polymer sequence file is erroneous\n";
2787 return QString("");
2788 }
2789
2790 // <polchemdef_name>
2791 child = element.firstChildElement();
2792 if(child.tagName() != "polchemdef_name")
2793 return QString("");
2794
2795 return child.text();
2796 }
2797
2798 /*!
2799 \brief Extracts from \a element, using the proper function (\a version), the
2800 polymer end modification.
2801
2802 The \a element tag is found in the polymer sequence XML file.
2803
2804 If the \a element tag name is \c{le_modif}, the modification name is set to
2805 the left end modification of this polymer sequence; if the tag name is
2806 \c{re_modif}, the right end of this polymer is modifified. The modifications
2807 are then rendered in place.
2808
2809 Returns true if no error was encountered, false otherwise.
2810
2811 \sa Modif::renderXmlMdfElement()
2812 */
2813 bool
2814 160 Polymer::renderXmlPolymerModifElement(const QDomElement &element,
2815 [[maybe_unused]] int version)
2816 {
2817
6/12
✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✓ Branch 5 taken 80 times.
✓ Branch 7 taken 80 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 80 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 160 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
240 if(element.tagName() != "le_modif" && element.tagName() != "re_modif")
2818 {
2819 qCritical()
2820 << "The element has not the expected 'le_modif' or 're_modif' tag.";
2821 return false;
2822 }
2823
2824 // Go down to the <mdf> element.
2825
1/2
✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
320 QDomElement child = element.firstChildElement();
2826
2827
3/4
✓ Branch 1 taken 160 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 62 times.
✓ Branch 4 taken 98 times.
160 if(child.isNull())
2828 return true;
2829
2830 98 bool result = false;
2831
3/4
✓ Branch 1 taken 98 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 49 times.
✓ Branch 5 taken 49 times.
196 if(element.tagName() == "le_modif")
2832
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 result = m_leftEndModif.renderXmlMdfElement(child, version);
2833
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
98 else if(element.tagName() == "re_modif")
2834
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 result = m_rightEndModif.renderXmlMdfElement(child, version);
2835
2836
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 if(!result)
2837 qCritical() << "Failed to render one end modifications.";
2838
2839 return result;
2840 160 }
2841
2842 /*!
2843 \brief Extracts from \a element, using the proper function (\a version),
2844 all the \l{CrossLink}s contained in it.
2845
2846 Each cross-link is rendered apart and applied to this polymer.
2847
2848 Returns true if no error was encountered, false otherwise.
2849
2850 \sa crossLink()
2851 */
2852 bool
2853 80 Polymer::renderXmlCrossLinksElement(const QDomElement &element,
2854 [[maybe_unused]] int version)
2855 {
2856 80 QDomElement child;
2857
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 QDomElement indentedChild;
2858
2859 // element is <crosslinks>
2860
2861 // <crosslinks>
2862 // <crosslink>
2863 // <name>DisulfideBond</name>
2864 // <targets>;2;6;</targets>
2865 // </crosslink>
2866 // </crosslinks>
2867
2868
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(element.tagName() != "crosslinks")
2869 return false;
2870
2871
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = element.firstChildElement();
2872
2873 // There can be any number of <crosslink> elements.
2874
3/4
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
✓ Branch 4 taken 80 times.
122 while(!child.isNull())
2875 {
2876
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 if(child.tagName() != "crosslink")
2877 return false;
2878
2879
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 indentedChild = child.firstChildElement();
2880
2881
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 if(indentedChild.tagName() != "name")
2882 return false;
2883
2884 // We actually do have a <crosslink> element, so we can allocate
2885 // one now.
2886
2887 // qDebug() << "Rendering a polymer sequence CrossLink by name:"
2888 //<< indentedChild.text();
2889
2890 42 CrossLinkerCstSPtr cross_linker_csp =
2891
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
42 mcsp_polChemDef->getCrossLinkerCstSPtrByName(indentedChild.text());
2892
2893
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if(cross_linker_csp == nullptr)
2894 return false;
2895
2896 42 CrossLinkSPtr cross_link_sp =
2897
1/6
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
84 std::make_shared<CrossLink>(cross_linker_csp, getCstSharedPtr());
2898
2899
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 indentedChild = indentedChild.nextSiblingElement();
2900
2901
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 42 times.
84 if(indentedChild.tagName() != "targets")
2902 {
2903 cross_link_sp.reset();
2904 return false;
2905 }
2906
2907 42 bool ok = false;
2908
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
42 cross_link_sp->fillInMonomers(indentedChild.text(), ok);
2909
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if(!ok)
2910 {
2911 cross_link_sp.reset();
2912 return false;
2913 }
2914
2915
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 indentedChild = indentedChild.nextSiblingElement();
2916
2917
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
✗ Branch 4 not taken.
42 if(!indentedChild.isNull())
2918 {
2919
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 42 times.
84 if(indentedChild.tagName() != "comment")
2920 {
2921 cross_link_sp.reset();
2922 return false;
2923 }
2924
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
42 if(!indentedChild.text().isEmpty())
2925
2/4
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
84 cross_link_sp->setComment(indentedChild.text());
2926 }
2927
2928 // At this point the crossLink element is finished rendering,
2929 // all we have to do is perform the crossLink proper.
2930
2931
3/6
✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 42 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 42 times.
84 if(crossLink(cross_link_sp).isEmpty())
2932 {
2933 cross_link_sp.reset();
2934 return false;
2935 }
2936
2937
3/8
✓ Branch 1 taken 42 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 42 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 42 times.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
84 child = child.nextSiblingElement();
2938
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
84 }
2939
2940 return true;
2941 80 }
2942
2943 /*!
2944 \brief Parses the \a file_path polymer sequence file.
2945
2946 During parsing, the encountered data are set to this polymer. This parsing
2947 is called "rendering".
2948
2949 Returns true if parsing succeeded, false otherwise.
2950 */
2951 bool
2952 80 Polymer::renderXmlPolymerFile(const QString &file_path)
2953 {
2954 80 qDebug() << "Starting the rendering of the XmlPolymerfile:" << file_path;
2955
2956 80 QString localFilePath;
2957
2958
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
80 QDomDocument doc("polSeqData");
2959
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 QDomElement element;
2960
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 QDomElement child;
2961
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 QDomElement indentedChild;
2962
2963 /*
2964 <polseqdata>
2965 <polchemdef_name>protein</polchemdef_name>
2966 <name>Sample</name>
2967 <code>SP2003</code>
2968 <author>rusconi</author>
2969 <datetime>1967-09-224:09:23</datetime>
2970 */
2971
2972
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if(file_path.isEmpty())
2973 localFilePath = m_filePath;
2974 else
2975 80 localFilePath = file_path;
2976
2977
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 QFile file(localFilePath);
2978
2979
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
80 if(!file.open(QIODevice::ReadOnly))
2980 {
2981 qDebug() << "Could not open file.";
2982 return false;
2983 }
2984
2985
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
80 if(!doc.setContent(&file))
2986 {
2987 qDebug() << "Failed to set file contents to doc object.";
2988 file.close();
2989 return false;
2990 }
2991
2992
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 file.close();
2993
2994
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
80 element = doc.documentElement();
2995
2996
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(element.tagName() != "polseqdata")
2997 {
2998 qDebug() << "Polymer sequence file is erroneous\n";
2999 return false;
3000 }
3001
3002 ///////////////////////////////////////////////
3003 // Check the version of the document.
3004
3005 80 QString text;
3006
3007
3/6
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 80 times.
80 if(!element.hasAttribute("version"))
3008 text = "1";
3009 else
3010
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
80 text = element.attribute("version");
3011
3012 80 bool ok = false;
3013
3014
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 int version = text.toInt(&ok, 10);
3015
3016
2/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 80 times.
80 if(version < 1 || !ok)
3017 {
3018 qDebug() << "Polymer sequence file has bad version number: " << version;
3019
3020 return false;
3021 }
3022
3023 80 qDebug() << "The version of the Polymer file:" << version;
3024
3025 // <polchemdef_name>
3026
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = element.firstChildElement();
3027
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "polchemdef_name")
3028 return false;
3029 // mcsp_polChemDef->setName(child.text());
3030
3031 // <name>
3032
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3033
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "name")
3034 return false;
3035
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 m_name = child.text();
3036
3037 // <code>
3038
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3039
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "code")
3040 return false;
3041
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 m_code = child.text();
3042
3043 // <author>
3044
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3045
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "author")
3046 return false;
3047
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 m_author = child.text();
3048
3049 // <datetime>
3050
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3051
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "datetime")
3052 return false;
3053
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 m_dateTime = QDateTime::fromString(child.text(), "yyyy-MM-dd:mm:ss");
3054
3055 // <polseq>
3056
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3057
3058
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "polseq")
3059 return false;
3060
3061 /*
3062 <polseq>
3063 <codes>MEFEEDF</codes>
3064 <monomer>
3065 <code>S</code>
3066 <prop>
3067 <name>MODIF</name>
3068 <data>Phosphorylation</data>
3069 </prop>
3070 </monomer>
3071 <codes>GRKDKNFLKMGRK</codes>
3072 </polseq>
3073 <le_modif>
3074 <mdf>
3075 <name>Acetylation</name>
3076 <formula>-H+C2H3O</formula>
3077 <targets>*</targets>
3078 <maxcount>1</maxcount>
3079 </mdf>
3080 </le_modif>
3081 <re_modif>
3082 <mdf>
3083 <name>Phosphorylation</name>
3084 <formula>-H+H2PO3</formula>
3085 <targets>*</targets>
3086 <maxcount>1</maxcount>
3087 </mdf>
3088 </re_modif>
3089 */
3090
3091 80 qDebug() << "Now starting to render the <polseq> element.";
3092
3093 // There can be any number of <codes> and <monomer> elements, in
3094 // whatever order.
3095
3096
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 indentedChild = child.firstChildElement();
3097
3098
3/4
✓ Branch 1 taken 258 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 178 times.
✓ Branch 4 taken 80 times.
258 while(!indentedChild.isNull())
3099 {
3100
3/4
✓ Branch 1 taken 178 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 129 times.
✓ Branch 5 taken 49 times.
356 if(indentedChild.tagName() == "codes")
3101 {
3102 129 qDebug() << "Iterating in <codes> element.";
3103
3104
2/4
✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 129 times.
129 if(!renderXmlCodesElement(indentedChild))
3105 {
3106 qDebug() << "Failed to render the XML codes element.";
3107 return false;
3108 }
3109 }
3110
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 49 times.
98 else if(indentedChild.tagName() == "monomer")
3111 {
3112 49 qDebug() << "Iterating in <monomer> element.";
3113
3114
1/2
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
49 MonomerSPtr monomer_sp = std::make_shared<Monomer>(mcsp_polChemDef);
3115
3116
2/4
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
49 if(!monomer_sp->renderXmlMonomerElement(indentedChild, version))
3117 {
3118 qDebug() << "Failed to render the XML monomer element.";
3119 monomer_sp.reset();
3120
3121 return false;
3122 }
3123
2/6
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
98 m_sequence.storeMonomer(monomer_sp);
3124 49 }
3125 else
3126 return false;
3127
3128
2/4
✓ Branch 1 taken 178 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 178 times.
✗ Branch 5 not taken.
356 indentedChild = indentedChild.nextSiblingElement();
3129 }
3130
3131 // qDebug() << "The sequence turns out to be:"
3132 // << m_sequence.getSequence(0, size() - 1, /*with_modifs*/ true);
3133
3134 // Go on to the next element(has to be <le_modif>.
3135
3136 80 QString errors;
3137
3138
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3139
3140
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "le_modif")
3141 errors += "Expected le_modif element not found.\n";
3142
3143
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
80 if(!renderXmlPolymerModifElement(child, version))
3144 errors += "Failed to render the left end modif element.\n";
3145
3146 // Go on to the next element(has to be <re_modif>.
3147
3148
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3149
3150
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "re_modif")
3151 errors += "Expected re_modif element not found.\n";
3152
3153
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
80 if(!renderXmlPolymerModifElement(child, version))
3154 errors += "Failed to render the right end modif element.\n";
3155
3156 // Go on to the next element(has to be <crosslinks>.
3157
3158
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
160 child = child.nextSiblingElement();
3159
3160
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 80 times.
160 if(child.tagName() != "crosslinks")
3161 errors += "Expected crosslinks element not found.\n";
3162
3163
2/4
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
80 if(!renderXmlCrossLinksElement(child, version))
3164 errors += "Failed to render the crosslinks element.";
3165
3166
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 if(!errors.isEmpty())
3167 {
3168 qDebug().noquote() << "Rendering of XML file failed with error:"
3169 << errors;
3170 return false;
3171 }
3172
3173
1/2
✓ Branch 1 taken 80 times.
✗ Branch 2 not taken.
80 setFilePath(localFilePath);
3174
3175 // qDebug() << "Finished rendering the XML file, returning true.";
3176
3177 return true;
3178 160 }
3179
3180 /*!
3181 \brief Creates the XML DTD for a polymer sequence file.
3182
3183 Returns the DTD in string.
3184 */
3185 QString
3186 Polymer::formatXmlDtd()
3187 {
3188 return QString(
3189 "<?xml version=\"1.0\"?>\n"
3190 "<!-- DTD for polymer sequences, used by the\n"
3191 "'massXpert' mass spectrometry application.\n"
3192 "Copyright 2006, 2007, 2008 Filippo Rusconi - Licensed under "
3193 "the GNU GPL -->\n"
3194 "<!DOCTYPE polseqdata [\n"
3195 "<!ELEMENT polseqdata "
3196 "(polchemdef_name, name, code, author, datetime, polseq, le_modif, "
3197 "re_modif, crosslinks, prop*)>\n"
3198 "<!ATTLIST polseqdata version NMTOKEN #REQUIRED>\n"
3199 "<!ELEMENT polchemdef_name (#PCDATA)>\n"
3200 "<!ELEMENT mdf (name, formula, targets)>\n"
3201 "<!ELEMENT name (#PCDATA)>\n"
3202 "<!ELEMENT formula (#PCDATA)>\n"
3203 "<!ELEMENT targets (#PCDATA)>\n"
3204 "<!ELEMENT code (#PCDATA)>\n"
3205 "<!ELEMENT author (#PCDATA)>\n"
3206 "<!ELEMENT datetime (#PCDATA)>\n"
3207 "<!ELEMENT polseq (codes|monomer)*>\n"
3208 "<!ELEMENT le_modif (mdf?)>\n"
3209 "<!ELEMENT re_modif (mdf?)>\n"
3210 "<!ELEMENT codes (#PCDATA)>\n"
3211 "<!ELEMENT crosslink (name, targets)>\n"
3212 "<!ELEMENT crosslinks (crosslink*)>\n"
3213 "<!ELEMENT monomer (code, mdf*)>\n"
3214 "<!ELEMENT prop (name, data+)>\n"
3215 "<!ATTLIST data type (str | int | dbl) \"str\">\n"
3216 "<!ELEMENT data (#PCDATA)>\n"
3217 "]>\n");
3218 }
3219
3220 /*!
3221 \brief Writes this polymer to file.
3222
3223 Returns true if successful, false otherwise.
3224 */
3225 bool
3226 Polymer::writeXmlFile()
3227 {
3228 QString text;
3229 QString indent(" ");
3230
3231 // We are asked to send an xml description of the polymer sequence.
3232
3233 QFile file(m_filePath);
3234
3235 if(!file.open(QIODevice::WriteOnly))
3236 {
3237 qDebug() << "Failed to open file" << m_filePath << "for writing.";
3238
3239 return false;
3240 }
3241
3242 QTextStream stream(&file);
3243 stream.setEncoding(QStringConverter::Utf8);
3244
3245 // The DTD
3246 stream << formatXmlDtd();
3247
3248 // Open the <polseqdata> element.
3249 //"<!ELEMENT polseqdata(polchemdef_name, name, code,
3250 // author, datetime, polseq, prop*)>\n"
3251
3252 stream << QString("<polseqdata version=\"%1\">\n")
3253 .arg(POL_SEQ_FILE_FORMAT_VERSION);
3254
3255 if(mcsp_polChemDef->getName().isEmpty())
3256 qFatalStream()
3257 << "Programming error. The PolChemDef's name cannot be empty.";
3258
3259 stream << QString("%1<polchemdef_name>%2</polchemdef_name>\n")
3260 .arg(indent)
3261 .arg(mcsp_polChemDef->getName());
3262
3263 stream << QString("%1<name>%2</name>\n")
3264 .arg(indent)
3265 .arg(m_name.isEmpty() ? "Not Set" : m_name);
3266
3267 stream << QString("%1<code>%2</code>\n")
3268 .arg(indent)
3269 .arg(m_code.isEmpty() ? "Not Set" : m_code);
3270
3271 if(m_author.isEmpty())
3272 qFatalStream()
3273 << "Programming error. The Polymer's author cannot be empty.";
3274
3275 stream << QString("%1<author>%2</author>\n").arg(indent).arg(m_author);
3276
3277 m_dateTime = QDateTime::currentDateTime();
3278 stream
3279 << QString("%1<datetime>%2</datetime>\n").arg(indent).arg(getDateTime());
3280
3281 stream << formatXmlPolSeqElement(POL_SEQ_FILE_FORMAT_VERSION);
3282
3283 // Now deal with the polymer modifications. These are represented as
3284 // <mdf> elements.
3285
3286 // Left end modif
3287 stream << QString("%1<le_modif>\n").arg(indent);
3288 if(m_leftEndModif.isValid())
3289 stream << m_leftEndModif.formatXmlMdfElement(/*offset*/ 1);
3290 stream << QString("%1</le_modif>\n").arg(indent);
3291
3292
3293 stream << QString("%1<re_modif>\n").arg(indent);
3294 if(m_rightEndModif.isValid())
3295 stream << m_rightEndModif.formatXmlMdfElement(/*offset*/ 1);
3296 stream << QString("%1</re_modif>\n").arg(indent);
3297
3298 stream << formatXmlCrossLinksElement(/*offset*/ 1);
3299
3300 // Note that at some point, there might be any number of polymer
3301 // <prop> elements at this place...
3302
3303 // Finally close the polseqdata.
3304
3305 stream << QString("</polseqdata>\n");
3306
3307 return true;
3308 }
3309
3310 /*!
3311 \brief Formats this polymer's sequence as a string suitable to use as an XML
3312 element.
3313
3314 This function generates a string holding all the elements pertaining to this
3315 polymer' \e sequence (the list of
3316 monomers, potentially modified, \e not all the other data). The typical
3317 element that is generated in this function looks like this:
3318
3319 \code
3320 <polseq>
3321 <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
3322 <monomer>
3323 <code>S</code>
3324 <mdf>
3325 <name>Phosphorylation</name>
3326 <formula></formula>
3327 <targets>*</targets>
3328 </mdf>
3329 </monomer>
3330 </polseq>
3331 \endcode
3332
3333 \a offset times the \a indent string must be used as a lead in the
3334 formatting of elements.
3335
3336 Returns a dynamically allocated string that needs to be freed after
3337 use.
3338
3339 \sa writeXmlFile()
3340 */
3341 QString
3342 Polymer::formatXmlPolSeqElement(int offset, const QString &indent)
3343 {
3344 int newOffset;
3345 int iter = 0;
3346
3347 QString lead("");
3348 QString codesString("");
3349 QString text;
3350
3351 // Prepare the lead.
3352 newOffset = offset;
3353 while(iter < newOffset)
3354 {
3355 lead += indent;
3356 ++iter;
3357 }
3358
3359 // At this point, we have to iterate in the sequence. If the
3360 // monomers are not modified, then put their codes in a raw, like
3361 // "ETGSH", in a <codes> element. As soon as a monomer is modified,
3362 // whatever the modification --that is, it has a prop object in its
3363 // --m_propList, it and its contents should be listed in a detailed
3364 // <monomer> element.
3365
3366 text += QString("%1<polseq>\n").arg(lead);
3367
3368 // Prepare the lead.
3369 ++newOffset;
3370 lead.clear();
3371 iter = 0;
3372 while(iter < newOffset)
3373 {
3374 lead += indent;
3375 ++iter;
3376 }
3377
3378 // Iterate in the polymer sequence.
3379
3380 for(const MonomerSPtr &monomer_sp : m_sequence.getMonomersCstRef())
3381 {
3382 // Check if the monomer_csp is modified. If not, we just append its
3383 // code to the elongating codesString, else we use a more
3384 // thorough monomer_csp element-parsing function.
3385
3386 if(!monomer_sp->isModified())
3387 {
3388 codesString += monomer_sp->getCode();
3389 continue;
3390 }
3391 else
3392 {
3393 // If something was baking in codesString, then we have to
3394 // create the element right now, fill the data in it and
3395 // close it before opening one <monomer> element below.
3396
3397 if(!codesString.isEmpty())
3398 {
3399 text += QString("%1<codes>%2%3")
3400 .arg(lead)
3401 .arg(codesString)
3402 .arg("</codes>\n");
3403
3404 codesString.clear();
3405 }
3406
3407 text += monomer_sp->formatXmlMonomerElement(newOffset);
3408 }
3409 }
3410
3411 // If something was baking in codesString, then we have to
3412 // create the element right now, fill the data in it and
3413 // close it before opening one <monomer> element below.
3414
3415 if(!codesString.isEmpty())
3416 {
3417 text +=
3418 QString("%1<codes>%2%3").arg(lead).arg(codesString).arg("</codes>\n");
3419
3420 codesString.clear();
3421 }
3422
3423
3424 // Prepare the lead for the closing element.
3425 --newOffset;
3426 lead.clear();
3427 iter = 0;
3428 while(iter < newOffset)
3429 {
3430 lead += indent;
3431 ++iter;
3432 }
3433
3434 text += QString("%1</polseq>\n").arg(lead);
3435
3436 return text;
3437 }
3438
3439 /*!
3440 \brief Formats an XML element suitable to describe the \c <crosslinks>
3441 element.
3442
3443 Iterates in the cross-link list of this polymer and crafts XML elements
3444 describing them.
3445
3446 The XML element looks like this:
3447
3448 \code
3449 <crosslinks>
3450 <crosslink>
3451 <name>DisulfideBond</name>
3452 <targets>;2;6;</targets>
3453 </crosslink>
3454 </crosslinks>
3455 \endcode
3456
3457 \a offset times the \a indent string must be used as a lead in the
3458 formatting of elements.
3459
3460 Returns the XML element as a dynamically allocated string.
3461 */
3462 QString
3463 Polymer::formatXmlCrossLinksElement(int offset, const QString &indent)
3464 {
3465
3466 int newOffset;
3467 int iter = 0;
3468
3469 QString lead("");
3470 QString text;
3471
3472 // Prepare the lead.
3473 newOffset = offset;
3474 while(iter < newOffset)
3475 {
3476 lead += indent;
3477 ++iter;
3478 }
3479
3480 // This is the kind of string we have to generate.
3481
3482 // <crosslinks>
3483 // <crosslink>
3484 // <name>DisulfideBond</name>
3485 // <targets>;2;6;</targets>
3486 // </crosslink>
3487 // </crosslinks>
3488
3489
3490 // At this point, we have to iterate in the list of crosslinks and
3491 // for each crosslink determine what's the crosslinker and which
3492 // monomer are actually crosslinked together.
3493
3494 text += QString("%1<crosslinks>\n").arg(lead);
3495
3496 // Prepare the lead.
3497 ++newOffset;
3498 lead.clear();
3499 iter = 0;
3500 while(iter < newOffset)
3501 {
3502 lead += indent;
3503 ++iter;
3504 }
3505
3506 for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
3507 {
3508 if(cross_link_sp == nullptr)
3509 continue;
3510
3511 text += QString("%1<crosslink>\n").arg(lead);
3512
3513 // Prepare the lead.
3514 ++newOffset;
3515 lead.clear();
3516 iter = 0;
3517 while(iter < newOffset)
3518 {
3519 lead += indent;
3520 ++iter;
3521 }
3522
3523 text += QString("%1<name>%2</name>\n")
3524 .arg(lead)
3525 .arg(cross_link_sp->getCrossLinkerCstSPtr()->getName());
3526
3527 // Create the string with all the monomer indices(which are the
3528 // targets of the crossLink).
3529
3530 text +=
3531 QString("%1<targets>%2</targets>\n")
3532 .arg(lead)
3533 .arg(cross_link_sp->locationsOfOnlyExtremeSequenceMonomersAsText(
3534 Enums::LocationType::INDEX));
3535
3536 text += QString("%1<comment>%2</comment>\n")
3537 .arg(lead)
3538 .arg(cross_link_sp->getComment());
3539
3540 // Prepare the lead.
3541 --newOffset;
3542 lead.clear();
3543 iter = 0;
3544 while(iter < newOffset)
3545 {
3546 lead += indent;
3547 ++iter;
3548 }
3549
3550 text += QString("%1</crosslink>\n").arg(lead);
3551 }
3552
3553 // Prepare the lead.
3554 --newOffset;
3555 lead.clear();
3556 iter = 0;
3557 while(iter < newOffset)
3558 {
3559 lead += indent;
3560 ++iter;
3561 }
3562
3563 text += QString("%1</crosslinks>\n").arg(lead);
3564
3565 return text;
3566 }
3567
3568 // VALIDATIONS
3569 ////////////////////////////
3570 /*!
3571 \brief Validates the Sequence of this polymer, setting errors as messages in
3572 \a error_list_p.
3573
3574 Returns true if validation was successful, false, otherwise.
3575
3576 \sa Sequence::validate()
3577 */
3578 bool
3579 128 Polymer::validate(ErrorList *error_list_p) const
3580 {
3581 128 m_isValid = true;
3582
3583
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 qsizetype error_count = error_list_p->size();
3584
3585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
3586 {
3587 qCritical() << "The polymer has no available PolChemDef.";
3588 error_list_p->push_back("The polymer has no available PolChemDef");
3589 }
3590
3591 // Name, Code, Author, FilePath all are not compulsory.
3592 // DateTime, LE and RE modifs, Ionizer all are not compulsory.
3593
3594 // Sequence has to be non-empty and must be valid.
3595
3596
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 104 times.
128 if(!m_sequence.validate(error_list_p))
3597 {
3598
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 qCritical() << "The Sequence of the polymer does not validate.";
3599 48 error_list_p->push_back("The Sequence of the polymer does not validate");
3600 24 m_isValid = false;
3601 }
3602
3603 // CrossLinks are not compulsory, but if there, they need to validate
3604 // successfully.
3605
3606
3607
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 128 times.
170 for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
3608 {
3609
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
42 if(!cross_link_sp->validate(error_list_p))
3610 {
3611 qCritical() << "One cross-link of the polymer does not validate.";
3612 error_list_p->push_back(
3613 "One cross-link of the polymer does not validate");
3614 m_isValid = false;
3615 }
3616 }
3617
3618 128 m_isValid = error_list_p->size() > error_count ? false : true;
3619 128 return m_isValid;
3620 }
3621
3622 /*!
3623 \brief Returns the validity status of this instance.
3624 */
3625 bool
3626 4 Polymer::isValid() const
3627 {
3628 4 return m_isValid;
3629 }
3630
3631 // UTILITIES
3632 ////////////////////////////
3633 /*!
3634 \brief Computes a MD5SUM has with the data described in \a
3635 hash_data_specifs.
3636
3637 If hash_data_specifs & HASH_ACCOUNT_SEQUENCE, the sequence is included in
3638 the hash computation. If hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF, the
3639 monomer modifications are included in the hash computation. If
3640 hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF, the polymer modifications are
3641 include in the hash computation.
3642
3643 Returns the hash.
3644 */
3645 QByteArray
3646 Polymer::md5Sum(int hash_data_specifs) const
3647 {
3648 // We first need to craft a complete string that encapsulates the
3649 // maximum number of information from the polymer sequence (sequence,
3650 // modifications in the monomers...) depending on the parameter passed
3651 // to the function.
3652
3653 QString text;
3654
3655 if(static_cast<int>(hash_data_specifs) &
3656 static_cast<int>(Enums::HashAccountData::SEQUENCE))
3657 {
3658 bool with_mnm_modifs =
3659 static_cast<int>(hash_data_specifs) &
3660 static_cast<int>(Enums::HashAccountData::MONOMER_MODIF);
3661 text += m_sequence.getSequence(0, m_sequence.size(), with_mnm_modifs);
3662 }
3663
3664 bool with_polymer_modifs =
3665 static_cast<int>(hash_data_specifs) &
3666 static_cast<int>(Enums::HashAccountData::POLYMER_MODIF);
3667
3668 if(with_polymer_modifs)
3669 {
3670 if(isLeftEndModified())
3671 {
3672 text += m_leftEndModif.getFormula();
3673 }
3674 if(isRightEndModified())
3675 {
3676 text += m_rightEndModif.getFormula();
3677 }
3678 }
3679
3680 // Now that we have the data string, we can craft the hash itself:
3681
3682 QByteArray hash =
3683 QCryptographicHash::hash(text.toUtf8(), QCryptographicHash::Md5);
3684
3685 return hash;
3686 }
3687
3688 /*!
3689 \brief Stores \a cross_link_sp as a \l UuidCrossLinkWPtrPair in the member
3690 container.
3691
3692 A Uuid string is computed and associated unambiguously to \a cross_link_sp via a
3693 \l UuidCrossLinkWPtrPair object.
3694
3695 Returns the Uuid string (a \l QUuid).
3696 */
3697 QString
3698 101 Polymer::storeCrossLink(CrossLinkSPtr cross_link_sp)
3699 {
3700
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if(cross_link_sp == nullptr)
3701 qFatalStream() << "Cannot be that the pointer is nullptr.";
3702
3703
2/4
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 101 times.
✗ Branch 4 not taken.
202 if(hasCrossLink(cross_link_sp) ||
3704
2/4
✓ Branch 1 taken 101 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 101 times.
202 !getUuidForCrossLink(cross_link_sp).isEmpty())
3705 qFatalStream()
3706 << "It is prohibited to store the same CrossLinkSPtr more than once.";
3707
3708 // Even if we get a ref to shared_ptr, the reference count increment will
3709 // occur.
3710 101 m_crossLinks.push_back(cross_link_sp);
3711
3712 101 QString uuid = QUuid::createUuid().toString();
3713
1/2
✓ Branch 2 taken 101 times.
✗ Branch 3 not taken.
101 m_uuidCrossLinkPairs.push_back(UuidCrossLinkWPtrPair(uuid, cross_link_sp));
3714
3715 101 return uuid;
3716 }
3717
3718 /*!
3719 \brief Returns true if \a cross_link_sp was found in the member container of
3720 Monomer instances, false otherwise.
3721 */
3722 bool
3723 202 Polymer::hasCrossLink(const CrossLinkSPtr &cross_link_sp) const
3724 {
3725
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 202 times.
202 if(cross_link_sp == nullptr)
3726 qFatalStream() << "Pointer cannot be nullptr.";
3727
3728 202 std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
3729 404 std::find_if(m_crossLinks.cbegin(),
3730 m_crossLinks.cend(),
3731
5/10
✓ Branch 1 taken 202 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 202 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 202 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 202 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 202 times.
✗ Branch 15 not taken.
1212 [cross_link_sp](const CrossLinkSPtr &the_cross_link_sp) {
3732
7/14
✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 84 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 84 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 84 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 28 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 84 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 140 times.
336 return the_cross_link_sp == cross_link_sp;
3733 });
3734
3735
1/2
✓ Branch 0 taken 202 times.
✗ Branch 1 not taken.
202 if(the_iterator_cst == m_crossLinks.cend())
3736 202 return false;
3737
3738 // No sanity checks with getCrossLinkFromUuid() or getUuidForCrossLink()
3739 // because that makes circular calls (these functions make sanity
3740 // checks by calling this hasMonomer().)
3741
3742 return true;
3743 }
3744
3745 /*!
3746 \brief Returns true if \a cross_link_sp was found in the member container of
3747 Uuid-CrossLink pairs, false otherwise.
3748 */
3749 bool
3750 Polymer::hasUuid(const CrossLinkSPtr &cross_link_sp) const
3751 {
3752 if(cross_link_sp == nullptr)
3753 qFatalStream() << "Pointer cannot be nullptr.";
3754
3755 std::vector<UuidCrossLinkWPtrPair>::const_iterator the_iterator_cst =
3756 std::find_if(m_uuidCrossLinkPairs.cbegin(),
3757 m_uuidCrossLinkPairs.cend(),
3758 [cross_link_sp](const UuidCrossLinkWPtrPair &the_pair) {
3759 return the_pair.second.lock() == cross_link_sp;
3760 });
3761
3762 if(the_iterator_cst == m_uuidCrossLinkPairs.cend())
3763 return false;
3764
3765 // Sanity check
3766
3767 if(!hasCrossLink(cross_link_sp))
3768 qFatalStream()
3769 << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";
3770
3771 return true;
3772 }
3773
3774 /*!
3775 \brief Returns the CrossLinkSPtr that is associated with \a uuid, if it is
3776 found in the member container of \l UuidCrossLinkWPtrPair objects.
3777
3778 If no such pair is found to hold \a uuid as its first member, then nullptr is
3779 returned.
3780
3781 \sa getCrossLinkFromUuid()
3782 */
3783 CrossLinkSPtr
3784 Polymer::getCrossLinkFromUuid(const QString &uuid) const
3785 {
3786 std::vector<UuidCrossLinkWPtrPair>::const_iterator the_iterator_cst =
3787 std::find_if(m_uuidCrossLinkPairs.cbegin(),
3788 m_uuidCrossLinkPairs.cend(),
3789 [uuid](const UuidCrossLinkCstWPtrPair &the_pair) {
3790 return the_pair.first == uuid;
3791 });
3792
3793 if(the_iterator_cst == m_uuidCrossLinkPairs.end())
3794 return nullptr;
3795
3796 CrossLinkSPtr cross_link_sp = (*the_iterator_cst).second.lock();
3797
3798 // Sanity check
3799
3800 if(!hasCrossLink(cross_link_sp))
3801 qFatalStream()
3802 << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";
3803
3804 return cross_link_sp;
3805 }
3806
3807 /*!
3808 \brief Returns the UUID string identifying \a cross_link_sp in the member
3809 container.
3810
3811 If no such CrossLink is found, an empty string is returned.
3812 */
3813 QString
3814 101 Polymer::getUuidForCrossLink(const CrossLinkSPtr &cross_link_sp) const
3815 {
3816
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if(cross_link_sp == nullptr)
3817 qFatalStream() << "Pointer cannot be nullptr.";
3818
3819 101 std::vector<UuidCrossLinkWPtrPair>::const_iterator the_iterator_cst =
3820 202 std::find_if(m_uuidCrossLinkPairs.cbegin(),
3821 m_uuidCrossLinkPairs.cend(),
3822
5/10
✓ Branch 2 taken 101 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 101 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 101 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 101 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 101 times.
✗ Branch 16 not taken.
606 [cross_link_sp](const UuidCrossLinkWPtrPair &the_pair) {
3823 // Do not query the monomer_sp managed object because it can
3824 // be nullptr!
3825
1/2
✓ Branch 1 taken 294 times.
✗ Branch 2 not taken.
294 return the_pair.second.lock() == cross_link_sp;
3826 });
3827
3828
1/2
✓ Branch 0 taken 101 times.
✗ Branch 1 not taken.
101 if(the_iterator_cst == m_uuidCrossLinkPairs.cend())
3829 {
3830 // Sanity check
3831
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 101 times.
101 if(hasCrossLink(cross_link_sp))
3832 qFatalStream() << "Inconsistency between the m_crossLinks and the "
3833 "m_uuidCrossLinkPairs vectors.";
3834
3835 101 return QString();
3836 }
3837
3838 // Sanity check
3839 if(!hasCrossLink(cross_link_sp))
3840 qFatalStream() << "Inconsistency between the m_crossLinks and the "
3841 "m_uuidCrossLinkPairs vectors.";
3842
3843
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
101 return (*the_iterator_cst).first;
3844 }
3845
3846 /*!
3847 \brief Returns the MonomerCstSPtr that is associated with \a uuid, if it is
3848 found in any CrossLink belonging to this Polymer.
3849
3850 If no such Monomer is found, nullptr is returned.
3851
3852 \sa getCrossLinkFromUuid()
3853 */
3854 MonomerCstSPtr
3855 Polymer::getCrossLinkedMonomerCstSPtrFromUuid(const QString &uuid) const
3856 {
3857 for(const CrossLinkSPtr &cross_link_csp : m_crossLinks)
3858 {
3859 MonomerCstSPtr monomer_csp = cross_link_csp->getMonomerForUuid(uuid);
3860
3861 if(monomer_csp != nullptr)
3862 return monomer_csp;
3863 }
3864
3865 return nullptr;
3866 }
3867
3868 std::vector<QString>
3869 Polymer::getAllCrossLinkUuids() const
3870 {
3871 std::vector<QString> the_uuid_strings;
3872
3873 for(const UuidCrossLinkWPtrPair &pair : m_uuidCrossLinkPairs)
3874 the_uuid_strings.push_back(pair.first);
3875
3876 // Sanity check
3877 if(the_uuid_strings.size() != m_crossLinks.size())
3878 qFatalStream()
3879 << "Inconsistency between the <object>_s and <uuid-object> pairs.";
3880
3881 return the_uuid_strings;
3882 }
3883
3884 /*!
3885 \brief Removes the \l UuidCrossLinkWPtrPair object from the member container if
3886 the second member of that pair references the same CrossLinkSPtr as that
3887 referenced by \a cross_link_wp.
3888
3889 If no UuidCrossLinkWPtrPair object matches that criterion, nothing happens.
3890
3891 \sa cleanupCrossLinks()
3892 */
3893 bool
3894 Polymer::removeCrossLink(CrossLinkSPtr cross_link_sp)
3895 {
3896 if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
3897 qFatalStream() << "Cannot be that pointer is nullptr.";
3898
3899 qDebug() << "Going to remove cross-link:"
3900 << cross_link_sp->getCrossLinkerName();
3901
3902 // We will need this anyway.
3903 QString uuid = getUuidForCrossLink(cross_link_sp);
3904
3905 qDebug() << "The Uuid of the cross-link is:" << uuid;
3906
3907 std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
3908 std::find_if(m_crossLinks.begin(),
3909 m_crossLinks.end(),
3910 [cross_link_sp](CrossLinkSPtr iter_monomer_sp) {
3911 return iter_monomer_sp == cross_link_sp;
3912 });
3913
3914 if(the_iterator_cst == m_crossLinks.end())
3915 {
3916 qCritical() << "The CrossLinkSPtr was not found in the container.";
3917
3918 if(!uuid.isEmpty())
3919 qFatalStream()
3920 << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";
3921
3922 return false;
3923 }
3924
3925 // At this point, both containers contain cross_link_sp.
3926
3927 qDebug() << "Now effectively erasing the CrossLink from the Polymer "
3928 "container of CrossLink instances.";
3929
3930 m_crossLinks.erase(the_iterator_cst);
3931
3932 qDebug() << "Done.";
3933
3934 std::vector<UuidCrossLinkWPtrPair>::const_iterator the_pair_iterator_cst =
3935 std::find_if(m_uuidCrossLinkPairs.cbegin(),
3936 m_uuidCrossLinkPairs.cend(),
3937 [uuid](const UuidCrossLinkWPtrPair &the_pair) {
3938 // Do not query the cross_link_sp managed object because it
3939 // can be nullptr!
3940 return the_pair.first == uuid;
3941 });
3942
3943 if(the_pair_iterator_cst == m_uuidCrossLinkPairs.cend())
3944 qFatalStream()
3945 << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";
3946
3947 qDebug()
3948 << "Now effectively erasing the Uuid/CrossLink pair from the Polymer "
3949 "container of Uuid/CrossLink pairs.";
3950
3951 m_uuidCrossLinkPairs.erase(the_pair_iterator_cst);
3952
3953 qDebug() << "Done.";
3954
3955 return true;
3956 }
3957
3958 /*!
3959 \brief Removes the \l UuidCrossLinkWPtrPair object from the member container if
3960 the first member of that pair matches \a uuid.
3961
3962 If no such pair is found to hold \a uuid as its first member, then nothing
3963 happens.
3964
3965 \sa cleanupCrossLinks()
3966 */
3967 void
3968 Polymer::removeCrossLink(const QString &uuid)
3969 {
3970 std::vector<UuidCrossLinkWPtrPair>::const_iterator the_pair_iterator_cst =
3971 std::find_if(m_uuidCrossLinkPairs.cbegin(),
3972 m_uuidCrossLinkPairs.cend(),
3973 [uuid](const UuidCrossLinkWPtrPair &the_pair) {
3974 // Do not query the cross_link_sp managed object because it
3975 // can be nullptr!
3976 return the_pair.first == uuid;
3977 });
3978
3979 if(the_pair_iterator_cst == m_uuidCrossLinkPairs.cend())
3980 return;
3981
3982 CrossLinkSPtr cross_link_sp = (*the_pair_iterator_cst).second.lock();
3983
3984 std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
3985 std::find_if(m_crossLinks.begin(),
3986 m_crossLinks.end(),
3987 [cross_link_sp](CrossLinkSPtr iter_monomer_sp) {
3988 return iter_monomer_sp == cross_link_sp;
3989 });
3990
3991 if(the_iterator_cst == m_crossLinks.end())
3992 qFatalStream()
3993 << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";
3994
3995 m_crossLinks.erase(the_iterator_cst);
3996 }
3997
3998 /*!
3999 \brief Removes from the member container of \l UuidCrossLinkWPtrPair objects all
4000 the items having a CrossLinkWPtr referencing a dead CrossLinkSPtr.
4001 */
4002 void
4003 Polymer::cleanupCrossLinks()
4004 {
4005 qDebug() << "At beginning, count of UUID-CrossLink pairs:"
4006 << m_uuidCrossLinkPairs.size();
4007
4008 std::vector<UuidCrossLinkWPtrPair>::iterator the_iterator =
4009 m_uuidCrossLinkPairs.begin();
4010 std::vector<UuidCrossLinkWPtrPair>::iterator the_end_iterator =
4011 m_uuidCrossLinkPairs.end();
4012
4013 while(the_iterator != the_end_iterator)
4014 {
4015 if((*the_iterator).second.expired() ||
4016 (*the_iterator).second.lock() == nullptr ||
4017 !hasCrossLink((*the_iterator).second.lock()))
4018 the_iterator = m_uuidCrossLinkPairs.erase(the_iterator);
4019 else
4020 ++the_iterator;
4021 }
4022
4023 qDebug() << "At end, count of UUID-CrossLink pairs:"
4024 << m_uuidCrossLinkPairs.size();
4025 }
4026
4027 /*!
4028 \brief Returns a string describing the polymer.
4029 */
4030 QString
4031 Polymer::toString() const
4032 {
4033 QString text = QString("%1 - %2 - %3 - %4 - %5 - %6 - %7\n")
4034 .arg(m_name)
4035 .arg(m_code)
4036 .arg(m_author)
4037 .arg(mcsp_polChemDef->getName())
4038 .arg(m_filePath)
4039 .arg(m_leftEndModif.getName())
4040 .arg(m_rightEndModif.getName());
4041
4042 QString codes;
4043 for(const MonomerSPtr &monomer_sp : m_sequence.getMonomersCstRef())
4044 codes += monomer_sp->getCode();
4045
4046 text += codes;
4047
4048 return text;
4049 }
4050
4051 /*!
4052 \brief Clears all the member data.
4053 */
4054 void
4055 Polymer::clear()
4056 {
4057 m_name.clear();
4058 m_code.clear();
4059 m_author.clear();
4060 m_filePath.clear();
4061 m_sequence.clear();
4062 m_leftEndModif.clear();
4063 m_rightEndModif.clear();
4064 m_crossLinks.clear();
4065 m_uuidCrossLinkPairs.clear();
4066 }
4067
4068 void
4069 Polymer::registerJsConstructor(QJSEngine *engine)
4070 {
4071 if(!engine)
4072 {
4073 qWarning() << "Cannot register Polymer class: engine is null";
4074 return;
4075 }
4076
4077 // Register the meta object as a constructor
4078
4079 QJSValue jsMetaObject = engine->newQMetaObject(&Polymer::staticMetaObject);
4080 engine->globalObject().setProperty("Polymer", jsMetaObject);
4081 }
4082
4083
4084 } // namespace libXpertMassCore
4085 } // namespace MsXpS
4086