GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Fragmenter.cpp
Date: 2025-11-20 01:41:33
Lines:
380/820
46.3%
Functions:
18/21
85.7%
Branches:
364/1786
20.4%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the msXpertSuite project.
10 *
11 * The msXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Qt includes
35 #include <QDebug>
36
37
38 /////////////////////// Local includes
39 #include "MsXpS/libXpertMassCore/OligomerCollection.hpp"
40 #include "MsXpS/libXpertMassCore/PolChemDef.hpp"
41 #include "MsXpS/libXpertMassCore/Fragmenter.hpp"
42
43 namespace MsXpS
44 {
45 namespace libXpertMassCore
46 {
47
48
49 /*!
50 \class MsXpS::libXpertMassCore::Fragmenter
51 \inmodule libXpertMassCore
52 \ingroup PolChemDefAqueousChemicalReactions
53 \inheaderfile Fragmenter.hpp
54
55 \brief The Fragmenter class provides a model for performing gas phase
56 fragmentation reactions involving \l{FragmentationPathway} objects and
57 \l{Polymer} \l{Sequence}s.
58
59 The fragmentation process is configured by the member vector of
60 FragmentationConfig instances that may store, for example, all the
61 fragmentation pathways that need to be dealt with in the inner workings of this
62 Fragmenter class. For example, the user might want to perform a series of
63 fragmentation involving pathways b and y for protein fragmentation.
64
65 \sa FragmentationPathway, FragmentationRule, FragmentationConfig, Ionizer
66 */
67
68 /*!
69 \variable MsXpS::libXpertMassCore::Fragmenter::mcsp_polymer
70
71 \brief The \l Polymer polymer that is being cleaved (digested).
72 */
73
74 /*!
75 \variable MsXpS::libXpertMassCore::Fragmenter::mcsp_polChemDef
76
77 \brief The \l PolChemDef polymer chemistry definition that is the context in
78 which the Polymer exists.
79 */
80
81 /*!
82 \variable MsXpS::libXpertMassCore::Fragmenter::m_fragmentationConfigs
83 \brief The container of FragmentationConfig instances that collectively
84 configure the fragmentation pathways to implement during the fragmentation
85 process.
86 */
87
88 /*!
89 \variable MsXpS::libXpertMassCore::Fragmenter::m_calcOptions
90
91 \brief The CalcOptions that configure the way masses and formulas are to be
92 computed.
93 */
94
95 /*!
96 \variable MsXpS::libXpertMassCore::Fragmenter::m_ionizer
97
98 \brief The Ionizer that directs the ionization of the Oligomer instances
99 obtained by cleaving the Polymer.
100 */
101
102 /*!
103 \variable MsXpS::libXpertMassCore::Fragmenter::m_oligomers
104
105 \brief The vector of fragment Oligomer instances (product ions) generated as a
106 result of the fragmentation.
107 */
108
109 /*!
110 \variable MsXpS::libXpertMassCore::Fragmenter::m_crossLinkedRegions
111
112 \brief The vector of CrossLinkedRegion that describe the way product ion
113 fragments might involved CrossLink instances.
114
115 \sa CrossLinkedRegion
116 */
117
118
119 /*!
120 \brief Constructs a Fragmenter instance with a number of parameters.
121
122 \list
123 \li \a polymer_cqsp The Polymer instance to be fragmented.
124
125 \li \a pol_chem_def_csp The PolChemDef (polymer chemistry definition) that is
126 the context in which the Polymer exists.
127
128 \li \a fragmentation_configs The container of FragmentationConfig instances that
129 configure the fragmentation.
130
131 \li \a calc_options The CalcOptions instance that configures the mass and
132 formula calculations.
133
134 \li \a ionizer The Ionizer instance that drives the ionization of the Oligomer
135 instances generated by the cleavage.
136 \endlist
137
138 If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
139 */
140 13 Fragmenter::Fragmenter(
141 PolymerCstQSPtr polymer_cqsp,
142 PolChemDefCstSPtr pol_chem_def_csp,
143 const std::vector<FragmentationConfig> &fragmentation_configs,
144 const CalcOptions &calc_options,
145 13 const Ionizer &ionizer)
146
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 : mcsp_polymer(polymer_cqsp),
147 13 mcsp_polChemDef(pol_chem_def_csp),
148
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 m_fragmentationConfigs(fragmentation_configs),
149
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 m_calcOptions(calc_options),
150
3/6
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
26 m_ionizer(ionizer)
151 {
152
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
13 if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
153 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
154
155
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
13 if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
156 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
157
158 // qDebug() << "Constructing Fragmenter with CalcOptions:"
159 // << m_calcOptions.toString();
160
0/2
✗ Branch 5 not taken.
✗ Branch 6 not taken.
13 }
161
162 /*!
163 \brief Constructs Fragmenter instance as a copy of \a other.
164
165 If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
166 */
167 Fragmenter::Fragmenter(const Fragmenter &other)
168 : mcsp_polymer(other.mcsp_polymer),
169 mcsp_polChemDef(other.mcsp_polChemDef),
170 m_fragmentationConfigs(other.m_fragmentationConfigs),
171 m_calcOptions(other.m_calcOptions),
172 m_ionizer(other.m_ionizer)
173 {
174 if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
175 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
176
177 if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
178 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
179 }
180
181 /*!
182 \brief Desstructs this Fragmenter instance
183 */
184 13 Fragmenter::~Fragmenter()
185 {
186
1/2
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
26 }
187
188 /*!
189 \brief Adds \a fragmentation_config to the member container of
190 FragmentationConfig instances.
191 */
192 void
193 1 Fragmenter::addFragmentationConfig(
194 const FragmentationConfig &fragmentation_config)
195 {
196
197 1 m_fragmentationConfigs.push_back(fragmentation_config);
198 1 }
199
200 /*!
201 \brief Returns a constant reference to the container of FragmentationConfig
202 instances.
203 */
204 const std::vector<FragmentationConfig> &
205 1 Fragmenter::getFragmentationConfigsCstRef() const
206 {
207 1 return m_fragmentationConfigs;
208 }
209
210 /*!
211 \brief Returns a reference to the container of FragmentationConfig instances.
212 */
213 std::vector<FragmentationConfig> &
214 1 Fragmenter::getFragmentationConfigsRef()
215 {
216 1 return m_fragmentationConfigs;
217 }
218
219 /*!
220 \brief Returns a constant reference to the Ionizer instance.
221 */
222 const Ionizer &
223 1 Fragmenter::getIonizerCstRef() const
224 {
225 1 return m_ionizer;
226 }
227
228 /*!
229 \brief Returns a reference to the Ionizer instance.
230 */
231 Ionizer &
232 1 Fragmenter::getIonizerRef()
233 {
234 1 return m_ionizer;
235 }
236
237 /*!
238 \ b*rief Returns a constant reference to the CalcOptions instance.
239 */
240 const CalcOptions &
241 1 Fragmenter::getCalcOptionsCstRef() const
242 {
243 1 return m_calcOptions;
244 }
245
246 /*!
247 \ b*rief Returns a reference to the CalcOptions instance.
248 */
249 CalcOptions &
250 1 Fragmenter::getCalcOptionsRef()
251 {
252 1 return m_calcOptions;
253 }
254
255 /*!
256 \brief Transfers (using std::move()) all the Oligomer instances from \a source
257 to \a dest.
258
259 After the transfer, the \a source Oligomer container is cleared since it
260 contains only nullptr items.
261 */
262 std::size_t
263 284 Fragmenter::transferOligomers(OligomerCollection &source,
264 OligomerCollection &dest)
265 {
266 284 std::size_t dest_oligomer_count_before = dest.getOligomersRef().size();
267
268 // Move each element from source to dest
269 568 dest.getOligomersRef().insert(
270 284 dest.getOligomersRef().end(),
271 284 std::make_move_iterator(source.getOligomersRef().begin()),
272 284 std::make_move_iterator(source.getOligomersRef().end()));
273
274 284 std::size_t dest_oligomer_count_after = dest.getOligomersRef().size();
275 284 std::size_t transferred_count =
276 dest_oligomer_count_after - dest_oligomer_count_before;
277
278 // Sanity check
279
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 284 times.
284 if(transferred_count != source.getOligomersRef().size())
280 qFatalStream()
281 << "Programming error. Not all the Oligomers were transferred.";
282
283 // Now clear the source container which contains the same items as before but
284 // all the shared pointers are now nullptr.
285
286 284 source.getOligomersRef().clear();
287
288 284 return transferred_count;
289 }
290
291 /*!
292 \brief Transfers (using std::move()) \a source_oligomer_sp
293 to \a dest.
294 */
295 void
296 Fragmenter::transferOligomer(OligomerSPtr &&source_oligomer_sp,
297 OligomerCollection &dest)
298 {
299 dest.getOligomersRef().push_back(std::move(source_oligomer_sp));
300 }
301
302 /*!
303 \brief Returns a const reference to the member OligomerCollection instance.
304 */
305 const OligomerCollection &
306 35 Fragmenter::getOligomerCollectionCstRef() const
307 {
308 35 return m_oligomers;
309 }
310
311 /*!
312 \brief Returns a reference to the member OligomerCollection instance.
313 */
314 OligomerCollection &
315 1 Fragmenter::getOligomerCollectionRef()
316 {
317 1 return m_oligomers;
318 }
319
320 /*!
321 \brief Performs the actual fragmentation and returns true if successful, false
322 otherwise.
323
324 All the FragmentationConfig instances in the member container are iterated into
325 and the fragmentation procedure is implemented, storing all the generated
326 fragment Oligomer instances in the member OligomerCollection instance.
327 */
328 bool
329 12 Fragmenter::fragment()
330 {
331 // If the polymer sequence is empty, just return.
332
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if(!mcsp_polymer->size())
333 {
334 qCritical() << "The polymer sequence is empty: nothing to fragment.";
335 return true;
336 }
337
338 // Ensure that the list of fragmentation configs is not empty.
339
340
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if(!m_fragmentationConfigs.size())
341 {
342 qWarning() << "List of fragmentation configs is empty !";
343
344 return false;
345 }
346
347 // qDebug() << "Number of fragmentation configurations:"
348 // << m_fragmentationConfigs.size();
349
350 // Check if we have to account for monomer modifications. If so, DEEP-compute
351 // the mass of all the monomers that are comprised in the sequence range.
352
353 12 double mono;
354 12 double avg;
355
356 // if(static_cast<int>(m_calcOptions.getMonomerEntities()) &
357 // static_cast<int>(Enums::ChemicalEntity::MODIF))
358 // Use the overload (see globals.hpp)
359
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
12 if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
360 Enums::ChemicalEntity::MODIF))
361 {
362 // qDebug() << "Fragmentation calculations take "
363 // "into account the monomer modifications";
364
365 6 m_calcOptions.setDeepCalculation(true);
366
367 6 Polymer::calculateMasses(
368 mcsp_polymer.get(), m_calcOptions, mono, avg, /*reset*/ false);
369 }
370
371 // Before starting the calculation we ought to know if there are
372 // cross-links in the oligomer to be fragmented and if the user
373 // has asked that these cross-linked be taken into account during
374 // the fragmentation.
375
376 // If there are cross-links, then we have to deal with
377 // them. The strategy is to first get a list of all the
378 // monomer indices for the monomers in the Oligomer (being
379 // fragmented) that are involved in cross-links.
380
381
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 FragmentationConfig fragmentation_config = m_fragmentationConfigs.at(0);
382
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 std::vector<std::size_t> cross_link_indices;
383 12 std::size_t partials = 0;
384
385 // qDebug() << "Fragmentation config:"
386 // << fragmentation_config.toString();
387
388
3/6
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
12 mcsp_polymer->crossLinkedMonomersIndicesInRange(
389 fragmentation_config.getStartIndex(),
390 fragmentation_config.getStopIndex(),
391 cross_link_indices,
392 partials);
393
394
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
395 Enums::ChemicalEntity::CROSS_LINKER) &&
396 partials)
397 {
398 12 qDebug() << "Fragmentation calculations do not\n"
399 "take into account partial cross-links.\n"
400 "These partial cross-links are ignored.";
401 }
402
403 // We do run into the if block below only if the CrossLinks
404 // should be taken into account (if any) and if there are
405 // at least one CrossLink that is not partial (that is, that is
406 // fully encompassed by the startIndex--stopIndex region).
407
408
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
12 if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
409
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 Enums::ChemicalEntity::CROSS_LINKER) &&
410 cross_link_indices.size())
411 {
412 qDebug() << "Fragmentation calculations take "
413 "into account the cross-links and there are "
414 << cross_link_indices.size()
415 << "cross-links in the selected Oligomer.";
416
417 // Let's put one of the fragmentation configs (the first) so that
418 // we can get the indices of the oligomer to fragment.
419
420 FragmentationConfig fragmentation_config = m_fragmentationConfigs.at(0);
421
422 if(partials)
423 qDebug() << "Fragmentation calculations do not\n"
424 "take into account partial cross-links.\n"
425 "These partial cross-links are ignored.";
426
427 // Now that we have a container with all of the indices of all the
428 // monomers that are cross-linked, we can iterate in it and create a list
429 // of CrossLinkedRegion instances that will allow us to "segment" the
430 // to-fragment oligomer so as to ease the calculation of product ion
431 // masses.
432
433 // Sort the indices.
434 std::sort(cross_link_indices.begin(), cross_link_indices.end());
435
436 #ifdef QT_DEBUG
437
438 QString indices_text;
439
440 for(std::size_t index : cross_link_indices)
441 indices_text += QString("%1\n").arg(index);
442
443 // qDebug().noquote()
444 // << "Indices of all the Monomer instances involved in "
445 // "cross-linked sequences: \n"
446 // << indices_text;
447
448 #endif
449
450 // Now find continuous regions and create a new region each
451 // time we find one.
452
453 std::size_t first = 0;
454 std::size_t last = 0;
455
456 std::size_t prev = 0;
457 std::size_t next = 0;
458
459 std::size_t cross_link_indices_size = cross_link_indices.size();
460 std::size_t last_cross_link_indices_index = cross_link_indices_size - 1;
461 bool iterated_in_loop = false;
462
463 for(std::size_t iter = 0; iter < last_cross_link_indices_index; ++iter)
464 {
465 iterated_in_loop = true;
466
467 // Seed the system only at the first iteration.
468 if(!iter)
469 {
470 first = cross_link_indices.at(iter);
471 last = cross_link_indices.at(iter + 1);
472
473 // qDebug() << __FILE__ << __LINE__
474 // << "Seeding with first and last:" << first << "--"
475 // << last;
476 }
477
478 prev = cross_link_indices.at(iter);
479 next = cross_link_indices.at(iter + 1);
480
481 if(next - prev == 1)
482 {
483 // We are going on with a continuum. Fine.
484 last = next;
485
486 // qDebug() << "Elongating continuum:"
487 // << "[" << first << "-" << last << "]";
488
489 continue;
490 }
491 else
492 {
493 // There is a gap. Close the previous continuum and
494 // start another one.
495
496 last = prev;
497
498 // qDebug() << "Closing continuum:"
499 // << "[" << first << "-" << last << "]";
500
501 CrossLinkedRegion region(first, last);
502
503 // Get the cross-links for the region.
504 std::vector<CrossLinkSPtr> cross_links;
505
506 partials = 0;
507
508 mcsp_polymer->crossLinksInRange(
509 first, last, cross_links, partials);
510
511 if(partials)
512 qDebug() << "Fragmentation calculations do not\n"
513 "take into account partial cross-links.\n"
514 "These partial cross-links are ignored.";
515
516 // Append the obtained cross-links to the region so
517 // that we finalize its construction. Finally append
518 // the new region to the list.
519
520 region.appendCrossLinks(cross_links);
521
522 m_crossLinkedRegions.push_back(region);
523
524 // Now that we have closed a continuum, start seeding
525 // a new one.
526 first = next;
527 }
528 }
529 // End of
530 // for(std::size_t iter = 0; iter < cross_link_indices_size; ++iter)
531
532 // qDebug() << "Did iterate in loop ?" << iterated_in_loop;
533
534 // Only close the pending CrossLinkedRegion if we actually did
535 // iterate in the loop above.
536 if(iterated_in_loop)
537 {
538 // We have to close the last continuum that we could not close
539 // because we ended off the for loop.
540
541 last = next;
542
543 // qDebug() << "Closing continuum:"
544 // << "[" << first << "-" << last << "]";
545
546 CrossLinkedRegion region(first, last);
547
548 // Get the cross-links for the region.
549 std::vector<CrossLinkSPtr> cross_links;
550
551 partials = 0;
552
553 mcsp_polymer->crossLinksInRange(first, last, cross_links, partials);
554
555 if(partials)
556 qDebug() << "Fragmentation calculations do not\n"
557 "take into account partial cross-links.\n"
558 "These partial cross-links are ignored.";
559
560 // Append the obtained cross-links to the region so
561 // that we finalize its construction. Finally append
562 // the new region to the list.
563
564 region.appendCrossLinks(cross_links);
565
566 m_crossLinkedRegions.push_back(region);
567
568 // qDebug() << "Determined" << m_crossLinkedRegions.size() <<
569 // "regions";
570
571 // for(CrossLinkedRegion &cross_linked_region : m_crossLinkedRegions)
572 // qDebug() << "Region [" << cross_linked_region.getStartIndex() <<
573 // "-"
574 // << cross_linked_region.getStopIndex() << "]";
575 }
576 }
577 // End of
578 // if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
579 // Enums::ChemicalEntity::CROSS_LINKER) &&
580 // cross_link_indices.size())
581
582 // At this point we have a list of regions that we'll be able to
583 // use to compute the fragment masses (if cross-links apply).
584
585 // For each fragmentation configs instance in the list, perform the
586 // required fragmentation.
587
588
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
24 for(const FragmentationConfig &fragmentation_config : m_fragmentationConfigs)
589 {
590
3/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 9 times.
12 if(fragmentation_config.getFragEnd() == Enums::FragEnd::NE)
591 {
592
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
3 if(fragmentEndNone(fragmentation_config) == -1)
593 return false;
594 }
595
3/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 4 times.
9 else if(fragmentation_config.getFragEnd() == Enums::FragEnd::LE)
596 {
597
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
5 if(fragmentEndLeft(fragmentation_config) == -1)
598 return false;
599 }
600
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 else if(fragmentation_config.getFragEnd() == Enums::FragEnd::RE)
601 {
602
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if(fragmentEndRight(fragmentation_config) == -1)
603 return false;
604 }
605 else
606 qFatalStream() << "Programming error. Erroneous "
607 "Fragmentation End.";
608 }
609
610 return true;
611 12 }
612
613 /*!
614 \brief Performs the actual fragmentation in the specific case that \a
615 fragmentation_config indicates the fragment Oligomers to be generated contain no
616 Polymer end (like immonium ions in protein gas phase chemistry).
617
618 Returns the count of produced fragment Oliogomer instances.
619 */
620 int
621 3 Fragmenter::fragmentEndNone(const FragmentationConfig &fragmentation_config)
622 {
623 3 int count = 0;
624
625 // qDebug().noquote()
626 // << "Now fragmenting according to fragmentation_config:"
627 // << fragmentation_config.toString();
628
629 // We are generating fragments that are made of a single monomer,
630 // like in the proteinaceous world we have the immonium ions.
631
632 // We will need the isotopic data throughout all of this function.
633 3 IsotopicDataCstSPtr isotopic_data_csp =
634 3 mcsp_polChemDef->getIsotopicDataCstSPtr();
635
636
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr)
637 qFatalStream() << "Programming error. The isotopic data must be available.";
638
639
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 CalcOptions calc_options(m_calcOptions);
640
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 calc_options.setCapType(Enums::CapType::NONE);
641
642 // qDebug() << "Now fragmenting END::NONE with calculation options:"
643 // << calc_options.toString();
644
645
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
78 for(std::size_t iter = fragmentation_config.getStartIndex();
646
3/4
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 75 times.
✓ Branch 4 taken 3 times.
78 iter < fragmentation_config.getStopIndex() + 1;
647 ++iter)
648 {
649 75 bool frag_rule_applied = false;
650
651 // We create an oligomer which is not ionized(false) but that
652 // bears the default ionization rule, because this oligomer
653 // might be later used in places where the ionization rule has
654 // to be valid. For example, one drag and drop operation might
655 // copy this oligomer into a mzLab dialog window where its
656 // ionization rule validity might be challenged. Because this
657 // fragmentation oligomer will be a neutral species, we should
658 // set the level member of the ionization to 0.
659
660 // The general idea is that we will later create as many different
661 // oligomers as there are requested levels of ionization. So, for the
662 // moment we try to create a "template" oligomer with the ionization rule
663 // set but with the ionization level set to 0.
664
665 // Old version, see below for Ionizer() in the construction of the
666 // Oligomer.
667 // Ionizer temp_ionizer(m_ionizer);
668 // temp_ionizer.setLevel(0);
669
670 // Allocate an Oligomer in which we'll update each time the formula
671 // that mirrors the masses of it.
672
673
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 CalcOptions fragment_calc_options(calc_options);
674
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 fragment_calc_options.setIndexRange(iter, iter);
675
676 75 OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
677
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 mcsp_polymer,
678 "NOT_SET",
679 fragmentation_config.getName() /*fragmentationPathway.m_name*/,
680
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
75 mcsp_polymer->modifiedMonomerCount(IndexRangeCollection(iter, iter)),
681
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
150 Ionizer(),
682
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
150 fragment_calc_options);
683
684 // The formula of the fragmentation specification should yield a neutral
685 // molecular species, which is then ionized according to the current
686 // Ionizer in the caller context. The levels of this Ionizer are set by
687 // the user and default to a single level of ionization.
688
689 // The first step is to calculate the masses of the fragment
690 // oligomer without taking into account the ionization,
691 // because we still have other things to account for that
692 // might interfere with the mass of the fragment. So we pass
693 // an invalid temp_ionizer object(upon creation, an Ionizer
694 // is invalid).
695
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 Ionizer temp_ionizer;
696
697
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 75 times.
75 if(!oligomer1_sp->calculateMasses(fragment_calc_options, temp_ionizer))
698 {
699 oligomer1_sp.reset();
700 return -1;
701 }
702
703 // qDebug() << "Right after creation with Ionizer() at index:" << iter
704 // << "mass calculation:"
705 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
706 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);
707
708 // Account in the oligomer's formula for the formula of the monomer.
709
710 // Ask the monomer to actually compute its formula either by taking into
711 // account the modifications or not depending on the calculation options
712 // below.
713 75 QString monomer_formula_string =
714
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 mcsp_polymer->getSequenceCstRef()
715
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 .getMonomerCstSPtrAt(iter)
716
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
75 ->calculateFormula(m_calcOptions.getMonomerEntities());
717
718 75 bool ok = false;
719
720
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 75 times.
✗ Branch 6 not taken.
75 oligomer1_sp->getFormulaRef().accountFormula(
721 monomer_formula_string, isotopic_data_csp, 1, ok);
722
723
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if(!ok)
724 {
725 qCritical() << "Failed to account formula:" << monomer_formula_string;
726 return -1;
727 }
728
729 // Now start getting deep inside the chemistry of the fragmentation
730 // oligomer.
731
3/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 75 times.
✗ Branch 8 not taken.
75 if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
732 {
733 75 Formula temp_formula(
734
3/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 75 times.
✗ Branch 8 not taken.
75 fragmentation_config.getFormulaCstRef().getActionFormula());
735
736 75 bool ok = false;
737
738 // First account the masses, that is MONO and AVG.
739
3/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 75 times.
✗ Branch 9 not taken.
75 temp_formula.accountMasses(
740 ok,
741 isotopic_data_csp,
742 oligomer1_sp->getMassRef(Enums::MassType::MONO),
743 oligomer1_sp->getMassRef(Enums::MassType::AVG));
744
745
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if(!ok)
746 {
747 qCritical() << "Failed to account masses for:"
748 << temp_formula.getActionFormula();
749 return -1;
750 }
751
752 // qDebug() << "After accounting fragmentation pathway formula:"
753 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
754 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);
755
756 // Second, account the formula.
757
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 75 times.
✗ Branch 6 not taken.
150 oligomer1_sp->getFormulaRef().accountFormula(
758
2/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 75 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
150 temp_formula.getActionFormula(), isotopic_data_csp, 1, ok);
759
760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if(!ok)
761 {
762 qCritical() << "Failed to account formula:"
763 << temp_formula.getActionFormula();
764 return -1;
765 }
766 75 }
767
768 // qDebug() << "After accounting formula, Oligomer:"
769 // << oligomer1_sp->toString();
770
771 // At this moment, the new fragment might be challenged for
772 // the fragmented monomer's contribution. For example, in
773 // nucleic acids, it happens that during a fragmentation, the
774 // base of the fragmented monomer is decomposed and goes
775 // away. This is implemented here with the ability to
776 // tell the fragmenter that upon fragmentation the mass of the
777 // monomer is to be removed. The skeleton mass is then added
778 // to the formula of the fragmentation pattern (FragmentationPathway).
779
780
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 int monomer_contribution = fragmentation_config.getMonomerContribution();
781
782
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if(monomer_contribution)
783 {
784 MonomerSPtr monomer_csp =
785 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
786
787 // First the masses.
788 monomer_csp->accountMasses(
789 oligomer1_sp->getMassRef(Enums::MassType::MONO),
790 oligomer1_sp->getMassRef(Enums::MassType::AVG),
791 monomer_contribution);
792
793 // qDebug() << "After accounting monomer contribution:"
794 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
795 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);
796
797 bool ok = false;
798
799 // Next the formula.
800 oligomer1_sp->getFormulaRef().accountFormula(
801 monomer_csp->getFormula(),
802 isotopic_data_csp,
803 monomer_contribution,
804 ok);
805
806 if(!ok)
807 {
808 qCritical() << "Failed to account formula:"
809 << monomer_csp->getFormula();
810 oligomer1_sp.reset();
811
812 return -1;
813 }
814 }
815
816 // At this point we should check if the fragmentation
817 // specification includes fragmentation rules that apply to this
818 // fragment. FragmentationConfig is derived from FragmentationPathway.
819
820 75 for(const FragmentationRuleSPtr &fragmentation_rule_sp :
821
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 75 times.
75 fragmentation_config.getRulesCstRef())
822 {
823 // The accounting of the fragmentationrule is performed on a
824 // neutral oligomer, as defined by the fragmentation
825 // formula. Later, we'll have to take into account the
826 // fact that the user might want to calculate fragment m/z values
827 // with z>1.
828
829 // This round is not for real accounting, but only to check if the
830 // currently iterated fragmentation rule should be accounted for (see
831 // the true that indicates this call is only for checking).
832
833 double mono;
834 double avg;
835
836 if(!accountFragmentationRule(fragmentation_rule_sp,
837 /*only for checking*/ true,
838 iter,
839 Enums::FragEnd::NE,
840 mono,
841 avg))
842 continue;
843
844 // This is why we first check above if the fragmentation rule was to
845 // be accounted for: each fragmentationrule triggers the creation of a
846 // new oligomer.
847
848 OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);
849
850 // qDebug() << "After copying oligomer1_sp into oligomer2_sp:"
851 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
852 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG);
853
854 accountFragmentationRule(
855 fragmentation_rule_sp,
856 false,
857 iter,
858 Enums::FragEnd::NE,
859 oligomer2_sp->getMassRef(Enums::MassType::MONO),
860 oligomer2_sp->getMassRef(Enums::MassType::AVG));
861
862 // qDebug() << "After accounting fragmentation rule:"
863 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
864 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG);
865
866 bool ok = false;
867
868 oligomer2_sp->getFormulaRef().accountFormula(
869 fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
870 isotopic_data_csp,
871 1,
872 ok);
873
874 if(!ok)
875 {
876 qCritical()
877 << "Failed to account formula:"
878 << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
879 oligomer1_sp.reset();
880 oligomer2_sp.reset();
881
882 return -1;
883 }
884
885 // Let the following steps know that we actually succeeded in
886 // preparing a fragment oligomer with a fragmentation rule applied.
887 frag_rule_applied = true;
888
889 // At this point we have the fragment oligomer in a
890 // neutral state (the fragmentation specification specifies a formula
891 // to create a neutral fragmentation product). This is so that we can
892 // later charge the ions as many times as requested by the user.
893
894 // We still have to account for potential formulas set to the
895 // FragmentationConfig, like when the user asks that for each product
896 // ion -H2O or -NH3 be accounted for,
897 // which happens all the time when doing peptide fragmentations.
898 // These formulas are stored in the m_formulas member of
899 // FragmentationConfig.
900
901 // And once we have done this, we'll still have to actually charge the
902 // fragments according to the FragmentationConfig's
903 // [m_charge_level_start--m_stopIonizeLevel] range.
904
905 // Each supplementary step above demultiplies the "versions" of the
906 // fragment Oligomer currently created (oligomer2_sp). All the newly
907 // created "versions"
908 // will need to be stored in a container
909 // (formula_variant_oligomers). So, for the moment copy the current
910 // oligomer into a template oligomer that will be used as a template
911 // to create all the "variant" oligomers.
912
913 OligomerSPtr template_oligomer_for_formula_variants_sp =
914 std::make_shared<Oligomer>(*oligomer2_sp);
915
916 // We can immediately set the name of template oligomer on which
917 // to base the creation of the derivative formula-based
918 // oligomers.
919 QString name = QString("%1#%2#(%3)")
920 .arg(fragmentation_config.getName())
921 .arg(mcsp_polymer->getSequenceCstRef()
922 .getMonomerCstSPtrAt(iter)
923 ->getCode())
924 .arg(fragmentation_rule_sp->getName());
925
926 // qDebug() << "Short name of oligomer:" << name;
927
928 int charge = 0;
929
930 // Set the name of this template oligomer, but with the
931 // charge in the form of "#z=1".
932 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
933
934 // qDebug() << "Long name of oligomer:" << name_with_charge;
935
936 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
937
938 // We have not yet finished characterizing fully the oligomer, for
939 // example, there might be formulas to be applied to the oligomer.
940 // Also, the user might have asked for a number of charge states. To
941 // store all these variants, create an oligomer container:
942
943 OligomerCollection formula_variant_oligomers(
944 fragmentation_config.getName(), mcsp_polymer);
945
946 // Ask that this new container be filled-in with the variants obtained
947 // by applying the Formula instances onto the template oligomer.
948 // Indeed, the user may have asked in FragmentationConfig that
949 // formulas like -H2O or -NH3 be accounted for
950 // in the generation of the fragment oligomers. Account
951 // for these potential formulas...
952
953 // Note that we use std::move when passing
954 // template_oligomer_for_formula_variants_sp to the function because
955 // the function will move it as the first item in the
956 // formula_variant_oligomers container.
957
958 // int accountedFormulas =
959 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
960 formula_variant_oligomers,
961 fragmentation_config,
962 name,
963 charge);
964
965 // qDebug() << "There are now"
966 // << formula_variant_oligomers.getOligomersCstRef().size()
967 // << "formula variant oligomers";
968
969 // We now have a container of oligomers (or only one if there
970 // was no formula to take into account). For each
971 // oligomer, we have to account for the charge levels
972 // asked by the user.
973
974 OligomerCollection ionization_variant_oligomers =
975 accountIonizationLevels(formula_variant_oligomers,
976 fragmentation_config);
977
978 // At this point we do not need the initial *uncharged* Oligomer
979 // instances anymore.
980 formula_variant_oligomers.clear();
981
982 // First off, we can finally delete the grand template oligomer.
983 oligomer2_sp.reset();
984
985 if(!ionization_variant_oligomers.size())
986 {
987 qCritical() << "Failed to generate ionized fragment oligomers.";
988 return -1;
989 }
990
991 // Finally transfer all the oligomers generated for this fragmentation
992 // to the container of ALL the oligomers. But before making
993 // the transfer, compute the elemental composition and store it
994 // as a property object.
995
996 // Involves a std::move operation.
997 // std::size_t transferred_count =
998 transferOligomers(ionization_variant_oligomers, m_oligomers);
999
1000 // qDebug() << "The number of transferred Oligomers:"
1001 // << transferred_count;
1002 }
1003 // End of
1004 // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
1005 // fragmentation_config.getRulesCstRef())
1006
1007 // We are here because of two reasons:
1008
1009 // 1. because the container of fragmentation_rule_sp was empty, in which
1010 // case we still have to validate and terminate the oligomer1
1011 // (frag_rule_applied is false);
1012
1013 // 2. because we finished dealing with the container of
1014 // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
1015 // of fragments if none of the fragmentationrules analyzed above gave a
1016 // successfully generated fragment(frag_rule_applied is false).
1017
1018
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 if(!frag_rule_applied)
1019 {
1020 // At this point we have a single fragment oligomer because we did not
1021 // had to apply any fragmentation rule, and thus have generated no
1022 // variant of it. We may have formulas to apply, as there might be
1023 // formulas in FragmentationConfig.
1024
1025 // So, first create an oligomer with the "default"
1026 // fragmentation specification-driven neutral state (that
1027 // is, charge = 0).
1028
1029
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 OligomerSPtr template_oligomer_for_formula_variants_sp =
1030
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 std::make_shared<Oligomer>(*oligomer1_sp);
1031
1032 // We can immediately set the name of template oligomer on which
1033 // to base the creation of the derivative formula-based
1034 // oligomers.
1035
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 QString name = QString("%1#%2")
1036
2/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
150 .arg(fragmentation_config.getName())
1037
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 .arg(mcsp_polymer->getSequenceCstRef()
1038
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 .getMonomerCstSPtrAt(iter)
1039
3/6
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 75 times.
✗ Branch 7 not taken.
225 ->getCode());
1040
1041 // qDebug() << "Short name of oligomer:" << name;
1042
1043 75 int charge = 0;
1044
1045 // Set the name of this template oligomer, but with the
1046 // charge in the form of "#z=1".
1047
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
150 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
1048
1049 // qDebug() << "Long name of oligomer:" << name_with_charge;
1050
1051
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
1052
1053 // We have not yet finished characterizing fully the oligomer, for
1054 // example, there might be formulas to be applied to the oligomer.
1055 // Also, the user might have asked for a number of charge states. To
1056 // store all these variants, create an oligomer container:
1057
1058 75 OligomerCollection formula_variant_oligomers(
1059
3/6
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 75 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 75 times.
✗ Branch 7 not taken.
150 fragmentation_config.getName(), mcsp_polymer);
1060
1061 // Ask that this new container be filled-in with the variants obtained
1062 // by applying the Formula instances onto the template oligomer.
1063 // Indeed, the user may have asked in FragmentationConfig that
1064 // formulas like -H2O or -NH3 be accounted for
1065 // in the generation of the fragment oligomers. Account
1066 // for these potential formulas...
1067
1068 // Note that we use std::move when passing
1069 // template_oligomer_for_formula_variants_sp to the function because
1070 // the function will move it as the first item in the
1071 // formula_variant_oligomers container.
1072
1073 // int accountedFormulas =
1074
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
1075 formula_variant_oligomers,
1076 fragmentation_config,
1077 name,
1078 charge);
1079
1080 // qDebug() << "There are now"
1081 // << formula_variant_oligomers.getOligomersCstRef().size()
1082 // << "formula variant oligomers";
1083
1084 // We now have a container of oligomers (or only one if there
1085 // was no formula to take into account). For each
1086 // oligomer, we have to account for the charge levels
1087 // asked by the user.
1088
1089 75 OligomerCollection ionization_variant_oligomers =
1090 accountIonizationLevels(formula_variant_oligomers,
1091
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 fragmentation_config);
1092
1093 // At this point we do not need the initial *uncharged* Oligomer
1094 // instances anymore.
1095
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 formula_variant_oligomers.clear();
1096
1097 // First off, we can finally delete the grand template
1098 // oligomer (oligomer with no fragmentation rules applied).
1099
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 oligomer1_sp.reset();
1100
1101
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 75 times.
75 if(!ionization_variant_oligomers.size())
1102 {
1103 qCritical() << "Failed to generate ionized fragment oligomers.";
1104 return -1;
1105 }
1106
1107 // Finally transfer all the oligomers generated for this fragmentation
1108 // to the container of ALL the oligomers. But before making
1109 // the transfer, compute the elemental composition and store it
1110 // as a property object.
1111
1112 // Involves a std::move operation.
1113 75 std::size_t transferred_count =
1114
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 transferOligomers(ionization_variant_oligomers, m_oligomers);
1115
1116 // qDebug() << "The number of transferred Oligomers:"
1117 // << transferred_count;
1118
1119 75 count += transferred_count;
1120
1/4
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 75 times.
75 }
1121 // End of
1122 // if(!frag_rule_applied)
1123 else // (frag_rule_applied == true)
1124 {
1125 // There were fragmentation rule(s) that could be
1126 // successfully applied. Thus we already have created the
1127 // appropriate oligomers. Simply delete the template
1128 // oligomer.
1129
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
75 oligomer1_sp.reset();
1130 }
1131
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
75 }
1132 // End of
1133 // for (int iter = fragmentation_config.getStartIndex();
1134 // fragmentation_config.getStopIndex() + 1; ++iter)
1135
1136 return count;
1137
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 }
1138
1139 /*!
1140 \brief Performs the actual fragmentation in the specific case that \a
1141 fragmentation_config indicates the fragment Oligomers to be generated contain
1142 the left Polymer end (like b ions in protein gas phase chemistry).
1143
1144 Returns the count of produced fragment Oliogomer instances.
1145 */
1146 int
1147 5 Fragmenter::fragmentEndLeft(const FragmentationConfig &fragmentation_config)
1148 {
1149 5 int count = 0;
1150 5 std::size_t number = 0;
1151 5 bool ok;
1152
1153 5 double mono = 0;
1154 5 double avg = 0;
1155
1156 // qDebug().noquote() << "Now fragmenting according to fragmentation_config:"
1157 // << fragmentation_config.toString();
1158
1159 // Formula to hold the summative result of accounting all of the residues
1160 // making the residual chain of the fragmentation ion product. Thus, this
1161 // formula is to be incremented at the beginning of each for loop iteration
1162 // below.
1163 5 Formula residue_chain_formula;
1164
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 residue_chain_formula.clear();
1165
1166 // We will need the isotopic data throughout all of this function.
1167 5 IsotopicDataCstSPtr isotopic_data_csp =
1168
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 mcsp_polChemDef->getIsotopicDataCstSPtr();
1169
1170 // If the crosslinks are to be taken into account, then make a local copy of
1171 // the m_crossLinkedRegions because we are going to remove items from it
1172 // during the calculation of the fragments and we do not want to modify the
1173 // contents of the original list (remember that this fragmenter might be
1174 // created not to perform a single fragmentation, but a set of
1175 // fragmentations).
1176
1177 5 std::vector<CrossLinkedRegion> local_cross_linked_regions =
1178
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 m_crossLinkedRegions;
1179
1180 // At this point we can start making the calculations of the
1181 // fragments. Because we are generating fragments that contain the
1182 // left part of the oligomer, we iterate in the
1183 // fragmentation_config.getStartIndex() -->
1184 // fragmentation_config.getStopIndex() direction.
1185
1186 // The general idea of the for loop below is that we iterate in the
1187 // [startIndex-stopIndex) range of the polymer that describes the oligomer to
1188 // be fragmented. Each iteration adds one monomer. The code below is thus
1189 // sectioned into parts that are additive between one iteration and another
1190 // and then parts that are renewed each time.
1191
1192 // The for loop below is designed to construct product ion oligomers that
1193 // start at the left end of the precursor ion (a ions, for example, in
1194 // protein chemistry).
1195
1196 // If the peptide precursor ion is MAMISGMSGR, for example,
1197
1198 // The first pass over the loop below, creates fragment
1199
1200 // M
1201
1202 // That fragment will have its masses (and chemical formula) computed like so:
1203
1204 // 1. the mass/formula of the monomer 'M'
1205 // 2. the mass/formula of the CrossLink instances found being
1206 // encompassed by the startIndex--stopIndex length of the fragment.
1207 // Obviously, for this first M, that is not going to be happening.
1208
1209 // Points 1 and 2 above produce data that are then incremented during
1210 // the next loop iteration, so that we add the masses for monomer 'A'
1211 // in the example sequence at the second iteration and then of 'M' at the
1212 // third...
1213
1214 // But then, at each iteration (first 'M', then 'A', then 'M' and so
1215 // on...), there are calculations that will not apply at each iteration.
1216 // For example, the chemical formula that describes the fragmentation
1217 // chemistry (for a fragments, -CHO) should be applied only once for all of
1218 // the fragments that are elongating through the iteration in the loop.
1219
1220 // So, at each iteration, we perform the summed calculations 1. and 2. above,
1221 // then we make a copy of the obtained results and we apply the
1222 // only-once-per-oligomer calculations to these copies.
1223
1224 // One very important concept here: the Oligomer instances that represent the
1225 // fragmentation product-ions cannot be represented fully: in particular,
1226 // the elementalComposition() function cannot be used to compute the formula
1227 // of the fragmentation oligomer just using CalcOptions (index range data)
1228 // because the Oligomer object does not know anything of the fragmentation
1229 // rules or added formula (+H2O or -NH3, for example). So we rely on the
1230 // member Oligomer::m_formula of Oligomer for this.
1231
1232
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 for(std::size_t iter = fragmentation_config.getStartIndex();
1233
3/4
✓ Branch 1 taken 125 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
✓ Branch 4 taken 5 times.
125 iter < fragmentation_config.getStopIndex();
1234 120 ++iter, ++number)
1235 {
1236 // This is the common part that will be incremented at each iteration,
1237 // thus first creating a fragment of one single monomer (index
1238 // startIndex), then at the second iteration creating an oligomer of 2
1239 // monomers, [startIndex, startIndex+1] and so on. The formula that gets
1240 // incremented at each iteration with the monomer's formula is
1241 // residue_chain_formula.
1242
1243 120 bool frag_rule_applied = false;
1244
1245 // Get a pointer to the polymer's monomer at index iter.
1246 120 MonomerSPtr monomer_csp =
1247
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
120 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
1248
1249 // The accountMasses function below automatically accounts for the monomer
1250 // modification(s), if any and if the calculation options do require that
1251 // these be accounted for.
1252
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 monomer_csp->accountMasses(mono, avg, 1);
1253
1254 // Ask the monomer to actually compute its formula either by taking into
1255 // account the modifications or not depending on the calculation options
1256 // below.
1257
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 QString monomer_formula_string =
1258
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
120 monomer_csp->calculateFormula(m_calcOptions.getMonomerEntities());
1259
1260
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
120 residue_chain_formula.accountFormula(
1261 monomer_formula_string, isotopic_data_csp, 1, ok);
1262
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1263 {
1264 qCritical() << "Failed to account formula:" << monomer_formula_string;
1265
1266 return -1;
1267 }
1268
1269 // qDebug() << "Accounted masses for monomer:" << monomer_csp->getCode()
1270 // << "at index:" << iter << "mono:" << mono << "avg:" << avg
1271 // << "with formula:" <<
1272 // residue_chain_formula.getActionFormula();
1273
1274 // If we are to take into account the cross-links, we ought to
1275 // take them into account here *once* and then remove them
1276 // from the local_cross_linked_regions so that we do not take them
1277 // into account more than once.
1278
1279
1280 // [3] [4] [5] [6] [9] [11]
1281 // o---o---o---o---o--o---o---o---o---o---o---o---o---o---o
1282 // | |__| | | | | |
1283 // | +-----------+ +-------+
1284 // | |
1285 // +------------------+
1286 //
1287 //
1288 // In the example above, there are two cross-linked regions: [3--9]
1289 // and [11--13].
1290
1291 120 std::vector<CrossLinkedRegion>::iterator the_iterator =
1292 120 local_cross_linked_regions.begin();
1293
1294 // Note below how we need to recompute the end iterator at each loop
1295 // iteration because we erase items in the loop and thus the
1296 // end iterator gets invalidated.
1297
1298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 while(the_iterator != local_cross_linked_regions.end())
1299 {
1300 CrossLinkedRegion &cross_linked_region = *the_iterator;
1301
1302 // Because we are generating fragments that increase in size at each
1303 // iteration from the left end, we know a given fragment encompasses
1304 // fully a region if its right end (that is the current iteration
1305 // 'iter') is at least equal to the right end (stop index) of a given
1306 // region.
1307
1308 if(cross_linked_region.getStopIndex() == iter)
1309 {
1310 // We are iterating in a fragment monomer index (iter) that
1311 // corresponds to the right end side of a region. Since we already
1312 // have gone through all the monomer positions at the left of
1313 // 'iter', by essence we know we are constructing a fragment that
1314 // encompasses fully the region.
1315
1316 // In this case, we have to take into account the cross-links
1317 // that are referenced in the region.
1318
1319 // qDebug() << "There are"
1320 // << cross_linked_region.getCrossLinksCstRef().size()
1321 // << "cross-links involved in the current region.";
1322
1323 std::size_t iteration_count_for_debugging = 0;
1324
1325 for(const CrossLinkSPtr &cross_link_sp :
1326 cross_linked_region.getCrossLinksCstRef())
1327 {
1328 ++iteration_count_for_debugging;
1329
1330 // qDebug() << "At iteration count"
1331 // << iteration_count_for_debugging
1332 // << "going to account for masses for cross-link:"
1333 // <<
1334 // cross_link_sp->getCrossLinkerCstSPtr()->getName();
1335
1336 cross_link_sp->accountMasses(mono, avg, 1);
1337
1338 // qDebug() << "Accounted masses for cross-link:"
1339 // <<
1340 // cross_link_sp->getCrossLinkerCstSPtr()->getName()
1341 // << "mono:" << mono << "avg:" << avg;
1342
1343 // The cross-linker formula might be empty.
1344 if(!cross_link_sp->getCrossLinkerCstSPtr()
1345 ->getFormula()
1346 .isEmpty())
1347 {
1348 // qDebug()
1349 // << "Now accounting for the cross-link formula:"
1350 // <<
1351 // cross_link_sp->getCrossLinkerCstSPtr()->getFormula();
1352
1353 residue_chain_formula.accountFormula(
1354 cross_link_sp->getCrossLinkerCstSPtr()->getFormula(),
1355 isotopic_data_csp,
1356 1,
1357 ok);
1358
1359 if(!ok)
1360 {
1361 qWarning() << "Failed to account formula:"
1362 << cross_link_sp->getCrossLinkerCstSPtr()
1363 ->getFormula();
1364
1365 return -1;
1366 }
1367
1368 // qDebug()
1369 // << "Accounted formula for cross-link:"
1370 // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
1371 // << residue_chain_formula.getActionFormula();
1372 }
1373 }
1374
1375 the_iterator = local_cross_linked_regions.erase(the_iterator);
1376 }
1377 // End of
1378 // if(cross_linked_region.getStopIndex() == iter)
1379 else
1380 ++the_iterator;
1381 }
1382 // End of
1383 // while (the_iterator != the_end_iterator)
1384
1385 #if 0
1386
1387 // Old QList-based version
1388
1389 // Iterate in the local_cross_linked_regions (do that in reverse
1390 // order because we'll have at some point to have to remove
1391 // items) and...
1392
1393 int jter = local_cross_linked_regions.size() - 1;
1394
1395 while(jter >= 0)
1396 {
1397 // ... for each item in it ask if the region encompasses
1398 // the current monomer index (value of iter)....
1399
1400 CrossLinkedRegion region = local_cross_linked_regions.at(jter);
1401
1402 if(region.getStopIndex() == iter)
1403 {
1404 // ... if so, iterate in the list of cross-links that
1405 // is stored in the CrossLinkedRegion...
1406
1407 const QList<CrossLink *> &crossLinkList = region.crossLinkList();
1408
1409 for(int kter = 0; kter < crossLinkList.size(); ++kter)
1410 {
1411 // ... and for each cross-link, account its mass
1412 // in the fragment (that is, ponderable)...
1413
1414 CrossLink *crossLink = crossLinkList.at(kter);
1415
1416 crossLink->accountMasses(&ponderable, 1);
1417
1418 residue_chain_formula.accountFormula(
1419 crossLink->formula(), isotopic_data_csp, ok, 1);
1420
1421 if(!ok)
1422 {
1423 qWarning()
1424 << "Failed to account formula:" << crossLink->formula();
1425
1426 return -1;
1427 }
1428 }
1429
1430 // ... and remove+delete the CrossLinkedRegion from
1431 // the list so that we are sure we do not take that
1432 // cross-link into account more than once.
1433
1434 delete local_cross_linked_regions.takeAt(jter);
1435 }
1436
1437 --jter;
1438 }
1439 #endif
1440
1441 // Now start a new section that will be specific to this iteration in the
1442 // for loop. We have summed the masses/formulas of the monomers up to this
1443 // iteration and we need to finalize the current fragment with
1444 // calculations that apply only once for each fragment. We thus create a
1445 // copy of the initial residual-chain-only formula and of the masses.
1446 // These data are updated each time a new aspect of the fragmentation
1447 // chemistry will be accounted for.
1448
1449 120 double frag_chemistry_mono = mono;
1450 120 double frag_chemistry_avg = avg;
1451
1452 // Make a copy of the residual chain-only formula so that we can aggregate
1453 // specific formula components later.
1454
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 Formula frag_chemistry_formula(residue_chain_formula);
1455
1456 // qDebug() << "Now starting fragment position-specific calculations with
1457 // "
1458 // "masses mono:"
1459 // << frag_chemistry_mono << "avg:" << frag_chemistry_avg
1460 // << "with formula:" <<
1461 // frag_chemistry_formula.getActionFormula();
1462
1463 // FragmentationConfig is a FragmentationPathway that holds the formula
1464 // that makes a fragment out of an oligomer.
1465
3/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 120 times.
✗ Branch 8 not taken.
120 if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
1466 {
1467 120 Formula frag_specific_formula(
1468
3/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 120 times.
✗ Branch 8 not taken.
120 fragmentation_config.getFormulaCstRef().getActionFormula());
1469
1470 120 bool ok = false;
1471
1472 // First account the masses, that is MONO and AVG.
1473
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
120 frag_specific_formula.accountMasses(
1474 ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);
1475
1476
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1477 {
1478 qCritical() << "Failed to account masses for:"
1479 << frag_specific_formula.getActionFormula();
1480 return -1;
1481 }
1482
1483 // Second, account the formula.
1484
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
240 frag_chemistry_formula.accountFormula(
1485
2/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
240 frag_specific_formula.getActionFormula(), isotopic_data_csp, 1, ok);
1486
1487
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1488 {
1489 qCritical() << "Failed to account formula:"
1490 << frag_specific_formula.getActionFormula();
1491
1492 return -1;
1493 }
1494
1495 // qDebug() << "Accounted masses for fragmentation formula:"
1496 // << frag_specific_formula.getActionFormula()
1497 // << "mono:" << frag_chemistry_mono
1498 // << "avg:" << frag_chemistry_avg << "and with formula:"
1499 // << frag_chemistry_formula.getActionFormula();
1500 120 }
1501
1502 // Account for the left cap since we are dealing with fragments that
1503 // retain the left end.
1504
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
120 Formula left_cap_formula = Formula(mcsp_polChemDef->getLeftCap());
1505
1506
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
120 left_cap_formula.accountMasses(
1507 ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);
1508
1509
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1510 {
1511 qCritical() << "Failed to account masses for left cap:"
1512 << left_cap_formula.getActionFormula();
1513 return -1;
1514 }
1515
1516
1/2
✓ Branch 2 taken 120 times.
✗ Branch 3 not taken.
240 frag_chemistry_formula.accountFormula(
1517
2/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
240 left_cap_formula.getActionFormula(), isotopic_data_csp, 1, ok);
1518
1519
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1520 {
1521 qCritical() << "Failed to account left cap formula:"
1522 << left_cap_formula.getActionFormula();
1523
1524 return -1;
1525 }
1526
1527 // qDebug() << "Accounted masses for left cap formula:"
1528 // << left_cap_formula.getActionFormula()
1529 // << "mono:" << frag_chemistry_mono << "avg:" <<
1530 // frag_chemistry_avg
1531 // << "and with formula:"
1532 // << frag_chemistry_formula.getActionFormula();
1533
1534 // Same of left end chemical modification only if the left end of this
1535 // fragment oligomer actually encompasses the real left end monomer
1536 // of the polymer (that is the start index is actually 0).
1537
1538
3/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✓ Branch 4 taken 72 times.
120 if(static_cast<bool>(m_calcOptions.getPolymerEntities() &
1539
3/4
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
168 Enums::ChemicalEntity::LEFT_END_MODIF) &&
1540
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 !fragmentation_config.getStartIndex())
1541 {
1542
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 Polymer::accountEndModifMasses(mcsp_polymer.get(),
1543 Enums::ChemicalEntity::LEFT_END_MODIF,
1544 frag_chemistry_mono,
1545 frag_chemistry_avg);
1546
1547
2/4
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
96 frag_chemistry_formula.accountFormula(
1548
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 mcsp_polymer->getLeftEndModifCstRef().getFormula(),
1549 isotopic_data_csp,
1550 1,
1551 ok);
1552
1553
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if(!ok)
1554 {
1555 qCritical() << "Failed to account left end formula:"
1556 << mcsp_polymer->getLeftEndModifCstRef().getFormula();
1557
1558 return -1;
1559 }
1560
1561 // qDebug() << "Accounted masses for left end formula:"
1562 // << mcsp_polymer->getLeftEndModifCstRef().getFormula()
1563 // << "mono:" << frag_chemistry_mono
1564 // << "avg:" << frag_chemistry_avg << "and with formula:"
1565 // << frag_chemistry_formula.getActionFormula();
1566 }
1567
1568 // At this moment, we have crafted the skeleton of the fragment oligomer
1569 // but we still potentially need to account for a number of chemical
1570 // entities.
1571
1572 // We will account for a number of formulas later, for the moment make the
1573 // oligomer as naked as possible. We'll dress it with formulas as we go.
1574 // Naked here means just account for the monomers (that is, the monomers
1575 // and not the monomers along with the end caps, the charge and so on).
1576
1577
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 CalcOptions local_calc_options(m_calcOptions);
1578
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 local_calc_options.setCapType(Enums::CapType::NONE);
1579
1580 // qDebug() << "Going to use CalcOptions:" <<
1581 // local_calc_options.toString()
1582 // << "to initialize Oligomer.";
1583
1584 // We need to set the proper IndexRange for the current iteration in the
1585 // sequence of the oligomer being fragmented. We know the start index is
1586 // certainly not changing because we are doing LE fragmentation. The
1587 // stop index, instead, changes with each iteration of this loop.
1588
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
120 local_calc_options.setIndexRange(fragmentation_config.getStartIndex(),
1589 iter);
1590
1591 // Create a fragmentation oligomer by constructing it with the
1592 // data that signal what oligomer it is, but for the moment
1593 // empty as far as the formula and the masses are concerned.
1594
1595 120 OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
1596
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 mcsp_polymer,
1597 "NOT_SET",
1598 fragmentation_config.getName() /*fragmentationPathway.m_name*/,
1599
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
240 mcsp_polymer->modifiedMonomerCount(
1600
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
120 IndexRangeCollection(fragmentation_config.getStartIndex(), iter)),
1601
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
240 Ionizer(),
1602
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
240 local_calc_options);
1603
1604 // Now set the masses that were additively crafted along the iterations
1605 // in this loop:
1606
1607
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 oligomer1_sp->setMasses(frag_chemistry_mono, frag_chemistry_avg);
1608
1609 // Now set the formula of the oligomer: set its constructed-empty
1610 // formula to the formula that resulted from all the previous steps.
1611
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 120 times.
✗ Branch 6 not taken.
240 oligomer1_sp->getFormulaRef().accountFormula(
1612
2/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
240 frag_chemistry_formula.getActionFormula(), isotopic_data_csp, 1, ok);
1613
1614
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(!ok)
1615 {
1616 qCritical() << "Failed to account formula:"
1617 << frag_chemistry_formula.getActionFormula();
1618 oligomer1_sp.reset();
1619 return -1;
1620 }
1621
1622 // qDebug() << "The oligomer1_sp has been created using "
1623 // "data crafted up to now and has masses:"
1624 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
1625 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
1626 // << "and formula:"
1627 // << oligomer1_sp->getFormulaRef().getActionFormula()
1628 // << "Now accounting (if set) for monomer contribution.";
1629
1630 // At this moment, the new fragment might be challenged for the
1631 // fragmented monomer's side chain contribution. For example, in
1632 // nucleic acids, it happens that during a fragmentation, the
1633 // base of the fragmented monomer is decomposed and goes
1634 // away. This is implemented in massXpert with the ability to
1635 // tell the fragmenter that upon fragmentation the mass of the
1636 // monomer is to be removed. The skeleton mass is then added to
1637 // the formula of the fragmentation pattern.
1638
1639
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 int monomer_contribution = fragmentation_config.getMonomerContribution();
1640
1641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
120 if(monomer_contribution)
1642 {
1643 MonomerSPtr monomer_csp =
1644 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
1645
1646 // First the masses.
1647 monomer_csp->accountMasses(
1648 oligomer1_sp->getMassRef(Enums::MassType::MONO),
1649 oligomer1_sp->getMassRef(Enums::MassType::AVG),
1650 monomer_contribution);
1651
1652 bool ok = false;
1653
1654 // Next the formula.
1655 oligomer1_sp->getFormulaRef().accountFormula(
1656 monomer_csp->getFormula(),
1657 isotopic_data_csp,
1658 monomer_contribution,
1659 ok);
1660
1661 if(!ok)
1662 {
1663 qCritical() << "Failed to account formula:"
1664 << monomer_csp->getFormula();
1665 oligomer1_sp.reset();
1666
1667 return -1;
1668 }
1669
1670 // qDebug() << "The oligomer1_sp after side chain contribution "
1671 // "has masses:"
1672 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
1673 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
1674 // << "updated formula:"
1675 // << oligomer1_sp->getFormulaRef().getActionFormula();
1676 }
1677
1678 // At this point we should check if the fragmentation
1679 // pathway includes fragmentation rules that apply to this
1680 // fragment. FragmentationConfig is derived from FragmentationPathway.
1681
1682 120 for(const FragmentationRuleSPtr &fragmentation_rule_sp :
1683
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 120 times.
120 fragmentation_config.getRulesCstRef())
1684 {
1685 // The accounting of the fragmentationrule is performed on a
1686 // neutral oligomer, as defined by the fragmentation
1687 // formula. Later, we'll have to take into account the
1688 // fact that the user might want to calculate fragment m/z values
1689 // with z>1.
1690
1691 // This round is not for real accounting, but only to check of the
1692 // currently iterated fragmentation rule should be accounted for (see
1693 // the true that indicates this call is only for checking).
1694
1695 double mono;
1696 double avg;
1697
1698 if(!accountFragmentationRule(fragmentation_rule_sp,
1699 /*only for checking*/ true,
1700 iter,
1701 Enums::FragEnd::LE,
1702 mono,
1703 avg))
1704 continue;
1705
1706 // This is why we first check above if the fragmentation rule was to
1707 // be accounted for: each fragmentationrule triggers the creation of a
1708 // new oligomer.
1709
1710 OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);
1711
1712 // qDebug() << "The oligomer2_sp has been copied from "
1713 // "oligomer1_sp and has masses:"
1714 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
1715 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
1716 // << "and formula:"
1717 // << oligomer2_sp->getFormulaRef().getActionFormula();
1718
1719 accountFragmentationRule(
1720 fragmentation_rule_sp,
1721 false,
1722 iter,
1723 Enums::FragEnd::LE,
1724 oligomer2_sp->getMassRef(Enums::MassType::MONO),
1725 oligomer2_sp->getMassRef(Enums::MassType::AVG));
1726
1727 bool ok = false;
1728
1729 oligomer2_sp->getFormulaRef().accountFormula(
1730 fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
1731 isotopic_data_csp,
1732 1,
1733 ok);
1734
1735 if(!ok)
1736 {
1737 qCritical()
1738 << "Failed to account formula:"
1739 << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
1740 oligomer1_sp.reset();
1741 oligomer2_sp.reset();
1742
1743 return -1;
1744 }
1745
1746 // qDebug() << "The oligomer2_sp after accounting for "
1747 // "fragmentation rule"
1748 // << fragmentation_rule_sp->getName() << "has masses:"
1749 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
1750 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
1751 // << "updated formula:"
1752 // << oligomer2_sp->getFormulaRef().getActionFormula();
1753
1754 // Let the following steps know that we actually succeeded in
1755 // preparing an oligomer (oligonucleotide, for example) with a
1756 // fragmentation rule applied.
1757
1758 frag_rule_applied = true;
1759
1760 // At this point we have the fragment oligomer in a
1761 // neutral state (the fragmentation specification specifies a formula
1762 // to create a neutral fragmentation product). This is so that we can
1763 // later charge the ions as many times as requested by the user.
1764
1765 // We still have to account for potential formulas set to the
1766 // FragmentationConfig, like when the user asks that for each product
1767 // ion -H2O or -NH3 be accounted for,
1768 // which happens all the time when doing peptide fragmentations.
1769 // These formulas are stored in the m_formulas member of
1770 // FragmentationConfig.
1771
1772 // And once we have done this, we'll still have to actually charge the
1773 // fragments according to the FragmentationConfig's
1774 // [m_startIonizeLevel--m_stopIonizeLevel] range.
1775
1776 // Each supplementary step above demultiplies the "versions" of the
1777 // fragment Oligomer currently created (oligomer2_sp). All the newly
1778 // created "versions"
1779 // will need to be stored in a container
1780 // (formula_variant_oligomers). So, for the moment copy the current
1781 // oligomer into a template oligomer that will be used as a template
1782 // to create all the "variant" oligomers.
1783
1784 OligomerSPtr template_oligomer_for_formula_variants_sp =
1785 std::make_shared<Oligomer>(*oligomer2_sp);
1786
1787 // We can immediately set the name of template oligomer on which
1788 // to base the creation of the derivative formula-based
1789 // oligomers.
1790 QString name = QString("%1#%2#(%3)")
1791 .arg(fragmentation_config.getName())
1792 .arg(number + 1)
1793 .arg(fragmentation_rule_sp->getName());
1794
1795 int charge = 0;
1796
1797 // Set the name of this template oligomer, but with the
1798 // charge in the form of "#z=1".
1799 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
1800
1801 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
1802
1803 // qDebug() << "The template_oligomer_for_formula_variants_sp is
1804 // created"
1805 // "by copying over oligomer2_sp. It now has a new name:"
1806 // << fragmentation_rule_sp->getName() << " and has masses:"
1807 // << "mono:"
1808 // << template_oligomer_for_formula_variants_sp->getMass(
1809 // Enums::MassType::MONO)
1810 // << "avg:"
1811 // << template_oligomer_for_formula_variants_sp->getMass(
1812 // Enums::MassType::AVG)
1813 // << "updated formula:"
1814 // <<
1815 // template_oligomer_for_formula_variants_sp->getFormulaRef()
1816 // .getActionFormula();
1817
1818 // We have not yet finished characterizing fully the oligomer, for
1819 // example, there might be formulas to be applied to the oligomer.
1820 // Also, the user might have asked for a number of charge states. To
1821 // store all these variants, create an oligomer container:
1822
1823 OligomerCollection formula_variant_oligomers(
1824 fragmentation_config.getName(), mcsp_polymer);
1825
1826 // Ask that this new container be filled-in with the variants obtained
1827 // by applying the Formula instances onto the template oligomer.
1828 // Indeed, the user may have asked in FragmentationConfig that
1829 // formulas like -H2O or -NH3 be accounted for
1830 // in the generation of the fragment oligomers. Account
1831 // for these potential formulas...
1832
1833 // Note that we use std::move when passing
1834 // template_oligomer_for_formula_variants_sp to the function because
1835 // the function will move it as the first item in the
1836 // formula_variant_oligomers container.
1837
1838 // int accountedFormulas =
1839 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
1840 formula_variant_oligomers,
1841 fragmentation_config,
1842 name,
1843 charge);
1844
1845 // qDebug() << "There are now"
1846 // << formula_variant_oligomers.getOligomersCstRef().size()
1847 // << "formula variant oligomers";
1848
1849 // We now have a container of oligomers (or only one if there
1850 // was no formula to take into account). For each
1851 // oligomer, we have to account for the charge levels
1852 // asked by the user.
1853
1854 OligomerCollection ionization_variant_oligomers =
1855 accountIonizationLevels(formula_variant_oligomers,
1856 fragmentation_config);
1857
1858 // qDebug() << "We now got"
1859 // <<
1860 // ionization_variant_oligomers.getOligomersCstRef().size()
1861 // << "ionization variant oligomers:\n"
1862 // << ionization_variant_oligomers.toString();
1863
1864 // At this point we do not need the initial *uncharged* Oligomer
1865 // instances anymore.
1866 formula_variant_oligomers.clear();
1867
1868 // qDebug() << "We still have"
1869 // <<
1870 // ionization_variant_oligomers.getOligomersCstRef().size()
1871 // << "ionization variant oligomers";
1872
1873 // First off, we can finally delete the grand template oligomer.
1874 oligomer2_sp.reset();
1875
1876 if(!ionization_variant_oligomers.size())
1877 {
1878 qCritical() << "Failed to generate ionized fragment oligomers.";
1879 return -1;
1880 }
1881
1882 // Finally transfer all the oligomers generated for this fragmentation
1883 // to the container of ALL the oligomers. But before making
1884 // the transfer, compute the elemental composition and store it
1885 // as a property object.
1886
1887 // Involves a std::move operation.
1888 std::size_t transferred_count =
1889 transferOligomers(ionization_variant_oligomers, m_oligomers);
1890
1891 // qDebug() << "The number of transferred Oligomers:"
1892 // << transferred_count;
1893
1894 // qDebug().noquote()
1895 // << "And now we have" << m_oligomers.getOligomersCstRef().size()
1896 // << "oligomers in total, listed below:\n"
1897 // << m_oligomers.toString();
1898
1899 count += transferred_count;
1900 }
1901 // End of
1902 // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
1903 // fragmentation_config.getRulesCstRef())
1904
1905 // We are here because of two possible reasons:
1906
1907 // 1. because the container of fragmentation_rule_sp was empty, in which
1908 // case we still have to validate and terminate the oligomer1
1909 // (frag_rule_applied is false);
1910
1911 // 2. because we finished dealing with the container of
1912 // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
1913 // of fragments if none of the fragmentationrules analyzed above gave a
1914 // successfully generated fragment(frag_rule_applied is false).
1915
1916
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 if(!frag_rule_applied)
1917 {
1918 // At this point we have a single fragment oligomer because we did not
1919 // had to apply any fragmentation rule, and thus have generated no
1920 // variant of it. We may have formulas to apply, as there might be
1921 // formulas in FragmentationConfig.
1922
1923 // qDebug() << "No FragmentationRule had to be applied. We thus have "
1924 // "a single oligomer at the moment.";
1925
1926 // So, first create an oligomer with the "default"
1927 // fragmentation specification-driven neutral state (that
1928 // is, charge = 0).
1929
1930
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 OligomerSPtr template_oligomer_for_formula_variants_sp =
1931
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 std::make_shared<Oligomer>(*oligomer1_sp);
1932
1933 // qDebug()
1934 // << "Created copy of oligomer1_sp as "
1935 // "template_oligomer_for_formula_variants_sp for accounting "
1936 // "formulas, like +H2O or -NH3, for example, with masses:"
1937 // << "mono:"
1938 // << template_oligomer_for_formula_variants_sp->getMass(
1939 // Enums::MassType::MONO)
1940 // << "avg:"
1941 // <<
1942 // template_oligomer_for_formula_variants_sp->getMass(Enums::MassType::AVG)
1943 // << "and formula:"
1944 // << template_oligomer_for_formula_variants_sp->getFormulaCstRef()
1945 // .getActionFormula();
1946
1947 // We can immediately set the name of template oligomer on which
1948 // to base the creation of the derivative formula-based
1949 // oligomers.
1950
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 QString name = QString("%1#%2")
1951
2/6
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
240 .arg(fragmentation_config.getName())
1952
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 .arg(number + 1);
1953
1954 120 int charge = 0;
1955
1956 // Set the name of this template oligomer, but with the
1957 // charge in the form of "#z=1".
1958
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
240 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
1959
1960
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
1961
1962 // We have not yet finished characterizing fully the oligomer, for
1963 // example, there might be formulas to be applied to the oligomer.
1964 // Also, the user might have asked for a number of charge states. To
1965 // store all these variants, create an oligomer container:
1966
1967 120 OligomerCollection formula_variant_oligomers(
1968
3/6
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 120 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 120 times.
✗ Branch 7 not taken.
240 fragmentation_config.getName(), mcsp_polymer);
1969
1970 // Ask that this new container be filled-in with the variants obtained
1971 // by applying the Formula instances onto the template oligomer.
1972 // Indeed, the user may have asked in FragmentationConfig that
1973 // formulas like -H2O or -NH3 be accounted for
1974 // in the generation of the fragment oligomers. Account
1975 // for these potential formulas...
1976
1977 // Note that we use std::move when passing
1978 // template_oligomer_for_formula_variants_sp to the function because
1979 // the function will move it as the first item in the
1980 // formula_variant_oligomers container.
1981
1982 // qDebug() << "Now accounting (if any) for added formulas, like +H2O
1983 // "
1984 // "or -NH3 or any user-defined formula.";
1985
1986 // int accountedFormulas =
1987
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
1988 formula_variant_oligomers,
1989 fragmentation_config,
1990 name,
1991 charge);
1992
1993 // qDebug() << "After accounting (if any) for formulas, there are"
1994 // << formula_variant_oligomers.getOligomersCstRef().size()
1995 // << "formula variant oligomers.";
1996
1997 // We now have a list of oligomers (or only one if there
1998 // was no formula to take into account). For each
1999 // oligomer, we have to account for the charge levels
2000 // asked by the user.
2001
2002 // qDebug() << "Now accounting for the ionization level(s).";
2003
2004 120 OligomerCollection ionization_variant_oligomers =
2005 accountIonizationLevels(formula_variant_oligomers,
2006
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 fragmentation_config);
2007
2008 // qDebug().noquote()
2009 // << "We now got"
2010 // << ionization_variant_oligomers.getOligomersCstRef().size()
2011 // << "ionization variant oligomers:\n"
2012 // << ionization_variant_oligomers.toString();
2013
2014 // At this point we do not need the initial *uncharged* Oligomer
2015 // instances anymore.
2016
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 formula_variant_oligomers.clear();
2017
2018 // qDebug() << "We still have"
2019 // <<
2020 // ionization_variant_oligomers.getOligomersCstRef().size()
2021 // << "ionization variant oligomers";
2022
2023
2/4
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 120 times.
120 if(!ionization_variant_oligomers.size())
2024 {
2025 qCritical() << "Failed to generate ionized fragment oligomers.";
2026 return -1;
2027 }
2028
2029 // First off, we can finally delete the grand template
2030 // oligomer (oligomer with no fragmentation rules applied).
2031
1/2
✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
120 oligomer1_sp.reset();
2032
2033 // Finally transfer all the oligomers generated for this fragmentation
2034 // to the container of ALL the oligomers. But before making
2035 // the transfer, compute the elemental composition and store it
2036 // as a property object.
2037
2038 // Involves a std::move operation.
2039 120 std::size_t transferred_count =
2040
1/2
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
120 transferOligomers(ionization_variant_oligomers, m_oligomers);
2041
2042 // qDebug() << "The number of transferred Oligomers:"
2043 // << transferred_count;
2044
2045 // qDebug().noquote()
2046 // << "And now we have" << m_oligomers.getOligomersCstRef().size()
2047 // << "oligomers in total, listed below:\n"
2048 // << m_oligomers.toString();
2049
2050 120 count += transferred_count;
2051
1/4
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 120 times.
120 }
2052 // End of
2053 // if(!frag_rule_applied)
2054 else // (frag_rule_applied == true)
2055 {
2056 // There were fragmentation rule(s) that could be
2057 // successfully applied. Thus we already have created the
2058 // appropriate oligomers. Simply delete the template
2059 // oligomer.
2060
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
120 oligomer1_sp.reset();
2061 }
2062
1/4
✓ Branch 4 taken 120 times.
✗ Branch 5 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
240 }
2063 // End of
2064 // for (int iter = fragmentation_config.getStartIndex();
2065 // iter < fragmentation_config.getStopIndex() + 1; ++iter, ++count)
2066
2067 return count;
2068
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
10 }
2069
2070 /*!
2071 \briefPerforms the actual fragmentation in the specific case that \a
2072 fragmentation_config indicates the fragment Oligomers to be generated contain
2073 the right Polymer end (like y ions in protein gas phase chemistry).
2074
2075 Returns the count of produced fragment Oliogomer instances.
2076 */
2077 int
2078 4 Fragmenter::fragmentEndRight(const FragmentationConfig &fragmentation_config)
2079 {
2080 4 int count = 0;
2081 4 std::size_t number = 0;
2082 4 bool ok;
2083
2084 4 double mono = 0;
2085 4 double avg = 0;
2086
2087 // qDebug().noquote() << "Now fragmenting according to fragmentation_config:"
2088 // << fragmentation_config.toString();
2089
2090 // Formula to hold the summative result of accounting all of the residues
2091 // making the residual chain of the fragmentation ion product. Thus, this
2092 // formula is to be incremented at the beginning of each for loop iteration
2093 // below.
2094 4 Formula residue_chain_formula;
2095
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 residue_chain_formula.clear();
2096
2097 // We will need the isotopic data throughout all of this function.
2098 4 IsotopicDataCstSPtr isotopic_data_csp =
2099
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 mcsp_polChemDef->getIsotopicDataCstSPtr();
2100
2101 // If the crosslinks are to be taken into account, then make a local copy of
2102 // the m_crossLinkedRegions because we are going to remove items from it
2103 // during the calculation of the fragments and we do not want to modify the
2104 // contents of the original list (remember that this fragmenter might be
2105 // created not to perform a single fragmentation, but a set of
2106 // fragmentations).
2107
2108 4 std::vector<CrossLinkedRegion> local_cross_linked_regions =
2109
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 m_crossLinkedRegions;
2110
2111 // See the fragmentEndLeft() very detailed explanations above.
2112
2113
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 for(std::size_t iter = fragmentation_config.getStopIndex();
2114
3/4
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✓ Branch 4 taken 4 times.
92 iter > fragmentation_config.getStartIndex();
2115 88 --iter, ++number)
2116 {
2117 // This is the common part that will be incremented at each iteration,
2118 // thus first creating a fragment of one single monomer (index
2119 // stopIndex), then at the second run creating an oligomer of 2 monomers,
2120 // [stopIndex -1, stopIndex] and so on. The formula that gets incremented
2121 // at each iteration with the monomer's formula is residue_chain_formula.
2122
2123 88 bool frag_rule_applied = false;
2124
2125 // Get a pointer to the polymer's monomer at index iter.
2126 88 MonomerSPtr monomer_csp =
2127
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
88 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
2128
2129 // The accountMasses function below automatically accounts for the monomer
2130 // modification(s), if any and if the calculation options do required that
2131 // these be accounted for.
2132
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 monomer_csp->accountMasses(mono, avg, 1);
2133
2134 // Ask the monomer to actually compute its formula either by taking into
2135 // account the modifications or not depending on the calculation options
2136 // below.
2137
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 QString monomer_formula_string =
2138
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
88 monomer_csp->calculateFormula(m_calcOptions.getMonomerEntities());
2139
2140
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 residue_chain_formula.accountFormula(
2141 monomer_formula_string, isotopic_data_csp, 1, ok);
2142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2143 {
2144 qCritical() << "Failed to account formula:" << monomer_formula_string;
2145
2146 return -1;
2147 }
2148
2149 // qDebug() << "Accounted masses for monomer:" << monomer_csp->getCode()
2150 // << "at index:" << iter << "mono:" << mono << "avg:" << avg
2151 // << "with formula:" <<
2152 // residue_chain_formula.getActionFormula();
2153
2154 // qDebug() << "Accounted formula for monomer:"
2155 // << residue_chain_formula.getActionFormula();
2156
2157 // If we are to take into account the cross-links, we ought to
2158 // take them into account here *once* and then remove them
2159 // from the local_cross_linked_regions so that we do not take them
2160 // into account more than once.
2161
2162
2163 // [3] [4] [5] [6] [9] [11]
2164 // o---o---o---o---o--o---o---o---o---o---o---o---o---o---o
2165 // | |__| | | | | |
2166 // | +-----------+ +-------+
2167 // | |
2168 // +------------------+
2169 //
2170 //
2171 // In the example above, there are two cross-linked regions: [3--9]
2172 // and [11--13].
2173
2174 // Iterate in the crossLinkedRegionList (do that in reverse
2175 // order because we'll have at some point to have to remove
2176 // items) and...
2177
2178 88 std::vector<CrossLinkedRegion>::iterator the_iterator =
2179 88 local_cross_linked_regions.begin();
2180
2181 // Note below how we need to recompute the end iterator at each loop
2182 // iteration because we erase items in the loop and thus the
2183 // end iterator gets invalidated.
2184
2185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 while(the_iterator != local_cross_linked_regions.end())
2186 {
2187 CrossLinkedRegion &cross_linked_region = *the_iterator;
2188
2189 // Because we are generating fragments that increase in size at each
2190 // iteration from the right end, we know a given fragment encompasses
2191 // fully a region if its left end (that is the current iteration
2192 // 'iter') is at least equal to the left end (start index) of a given
2193 // region.
2194
2195 if(cross_linked_region.getStartIndex() == iter)
2196 {
2197 // We are iterating in a fragment monomer index (iter) that
2198 // corresponds to the left end side of a region. Since we already
2199 // have gone through all the monomer positions at the right of
2200 // 'iter', by essence we know we are constructing a fragment that
2201 // encompasses fully the region.
2202
2203 // In this case, we have to take into account the cross-links
2204 // that are referenced in the region.
2205
2206 // qDebug() << "There are"
2207 // << cross_linked_region.getCrossLinksCstRef().size()
2208 // << "cross-links involved in the current region.";
2209
2210 std::size_t iteration_count_for_debugging = 0;
2211
2212 for(const CrossLinkSPtr &cross_link_sp :
2213 cross_linked_region.getCrossLinksCstRef())
2214 {
2215 ++iteration_count_for_debugging;
2216
2217 // qDebug() << "At iteration count"
2218 // << iteration_count_for_debugging
2219 // << "going to account for masses for cross-link:"
2220 // <<
2221 // cross_link_sp->getCrossLinkerCstSPtr()->getName();
2222
2223 cross_link_sp->accountMasses(mono, avg, 1);
2224
2225 // qDebug() << "Accounted masses for cross-link:"
2226 // <<
2227 // cross_link_sp->getCrossLinkerCstSPtr()->getName()
2228 // << "mono:" << mono << "avg:" << avg;
2229
2230 // The cross-linker formula might be empty.
2231 if(!cross_link_sp->getCrossLinkerCstSPtr()
2232 ->getFormula()
2233 .isEmpty())
2234 {
2235 // qDebug()
2236 // << "Now accounting for the cross-link formula:"
2237 // <<
2238 // cross_link_sp->getCrossLinkerCstSPtr()->getFormula();
2239
2240 residue_chain_formula.accountFormula(
2241 cross_link_sp->getCrossLinkerCstSPtr()->getFormula(),
2242 isotopic_data_csp,
2243 1,
2244 ok);
2245
2246 if(!ok)
2247 {
2248 qCritical() << "Failed to account formula:"
2249 << cross_link_sp->getCrossLinkerCstSPtr()
2250 ->getFormula();
2251
2252 return -1;
2253 }
2254
2255 // qDebug()
2256 // << "Accounted formula for cross-link:"
2257 // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
2258 // << residue_chain_formula.getActionFormula();
2259 }
2260 }
2261
2262 the_iterator = local_cross_linked_regions.erase(the_iterator);
2263 }
2264 // End of
2265 // if(cross_linked_region.getStopIndex() == iter)
2266 else
2267 ++the_iterator;
2268 }
2269 // End of
2270 // while (the_iterator != the_end_iterator)
2271
2272 // Now start a new section that will be specific to this iteration in the
2273 // for loop. We have summed the masses/formulas of the monomers up to this
2274 // iteration and we need to finalize the current fragment with
2275 // calculations that apply only once for each fragment. We thus create a
2276 // copy of the initial residual-chain-only formula and of the masses.
2277 // These data are updated each time a new aspect of the fragmentation
2278 // chemistry will be accounted for.
2279
2280 88 double frag_chemistry_mono = mono;
2281 88 double frag_chemistry_avg = avg;
2282
2283
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 Formula frag_chemistry_formula(residue_chain_formula);
2284
2285 // FragmentationConfig is a FragmentationPathway that holds the formula
2286 // that makes a fragment out of an oligomer.
2287
3/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✗ Branch 8 not taken.
88 if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
2288 {
2289 88 Formula frag_specific_formula(
2290
3/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 88 times.
✗ Branch 8 not taken.
88 fragmentation_config.getFormulaCstRef().getActionFormula());
2291
2292 88 bool ok = false;
2293
2294 // First account the masses, that is MONO and AVG.
2295
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 frag_specific_formula.accountMasses(
2296 ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);
2297
2298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2299 {
2300 qCritical() << "Failed to account masses for:"
2301 << frag_specific_formula.getActionFormula();
2302 return -1;
2303 }
2304
2305 // Second, account the formula.
2306
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
176 frag_chemistry_formula.accountFormula(
2307
2/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
176 frag_specific_formula.getActionFormula(), isotopic_data_csp, 1, ok);
2308
2309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2310 {
2311 qCritical() << "Failed to account formula:"
2312 << frag_specific_formula.getActionFormula();
2313
2314 return -1;
2315 }
2316
2317 // qDebug() << "Accounted masses for fragmentation formula:"
2318 // << frag_specific_formula.getActionFormula()
2319 // << "mono:" << frag_chemistry_mono
2320 // << "avg:" << frag_chemistry_avg << "and with formula:"
2321 // << frag_chemistry_formula.getActionFormula();
2322 88 }
2323
2324 // Account for the right cap since we are dealing with fragments that
2325 // retain the right end.
2326
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
88 Formula right_cap_formula = Formula(mcsp_polChemDef->getRightCap());
2327
2328
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 right_cap_formula.accountMasses(
2329 ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);
2330
2331
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2332 {
2333 qCritical() << "Failed to account masses for right cap:"
2334 << right_cap_formula.getActionFormula();
2335 return -1;
2336 }
2337
2338
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
176 frag_chemistry_formula.accountFormula(
2339
2/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
176 right_cap_formula.getActionFormula(), isotopic_data_csp, 1, ok);
2340
2341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2342 {
2343 qCritical() << "Failed to account right cap formula:"
2344 << right_cap_formula.getActionFormula();
2345
2346 return -1;
2347 }
2348
2349 // qDebug() << "Accounted masses for right cap formula:"
2350 // << right_cap_formula.getActionFormula()
2351 // << "mono:" << frag_chemistry_mono << "avg:" <<
2352 // frag_chemistry_avg
2353 // << "and with formula:"
2354 // << frag_chemistry_formula.getActionFormula();
2355
2356 // Same of right end chemical modification only if the right end of this
2357 // fragment oligomer actually encompasses the real left end monomer
2358 // of the polymer (that is the end index is actually size() - 1).
2359
2360
3/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 72 times.
88 if(static_cast<bool>(m_calcOptions.getPolymerEntities() &
2361
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 72 times.
88 Enums::ChemicalEntity::RIGHT_END_MODIF) &&
2362
3/6
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
16 fragmentation_config.getStopIndex() == mcsp_polymer->size() - 1)
2363 {
2364
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 Polymer::accountEndModifMasses(mcsp_polymer.get(),
2365 Enums::ChemicalEntity::RIGHT_END_MODIF,
2366 frag_chemistry_mono,
2367 frag_chemistry_avg);
2368
2369
2/4
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
32 frag_chemistry_formula.accountFormula(
2370
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 mcsp_polymer->getRightEndModifCstRef().getFormula(),
2371 isotopic_data_csp,
2372 1,
2373 ok);
2374
2375
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if(!ok)
2376 {
2377 qCritical()
2378 << "Failed to account right end formula:"
2379 << mcsp_polymer->getRightEndModifCstRef().getFormula();
2380
2381 return -1;
2382 }
2383
2384 // qDebug() << "Accounted masses for right end formula:"
2385 // << mcsp_polymer->getRightEndModifCstRef().getFormula()
2386 // << "mono:" << frag_chemistry_mono
2387 // << "avg:" << frag_chemistry_avg << "and with formula:"
2388 // << frag_chemistry_formula.getActionFormula();
2389 }
2390
2391 // The polymer chemistry definition defines a formula for the
2392 // FragmentationPathway that yields a fragment oligomer having no
2393 // charge: in a neutral state.
2394
2395 // The general idea is that we will later create as many different
2396 // oligomers as there are requested levels of ionization. So, for the
2397 // moment we try to create a "template" oligomer with the ionization rule
2398 // set but with the ionization level set to 0 and false for the ionization
2399 // status.
2400
2401 // We will account for a number of formulas later, for the moment make the
2402 // oligomer as naked as possible. We'll dress it with formulas as we go.
2403 // Naked here means just account for the monomers (that its the monomers
2404 // and not the monomers along with the end caps, the charge and so on).
2405
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 CalcOptions local_calc_options(m_calcOptions);
2406
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 local_calc_options.setCapType(Enums::CapType::NONE);
2407
2408 // qDebug() << "Going to use CalcOptions:" <<
2409 // local_calc_options.toString()
2410 // << "to initialize Oligomer.";
2411
2412 // We need to set the proper IndexRange for the current iteration in the
2413 // sequence of the oligomer being fragmented. We know the stop index is
2414 // certainly not changing because we are doing RE fragmentation. The
2415 // start index, instead, changes with each iteration of this loop.
2416
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 local_calc_options.setIndexRange(iter,
2417
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 fragmentation_config.getStopIndex());
2418
2419 // Create a fragmentation oligomer by constructing it with the
2420 // data that signal what oligomer it is, but for the moment
2421 // empty as far as the formula and the masses are concerned.
2422
2423 88 OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
2424
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 mcsp_polymer,
2425 "NOT_SET",
2426 fragmentation_config.getName() /*fragmentationPathway.m_name*/,
2427
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
176 mcsp_polymer->modifiedMonomerCount(
2428
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
88 IndexRangeCollection(iter, fragmentation_config.getStopIndex())),
2429
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
176 Ionizer(),
2430
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
176 local_calc_options);
2431
2432 // Now set the masses that were additively crafted along the iterations
2433 // in this loop:
2434
2435
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 oligomer1_sp->setMasses(frag_chemistry_mono, frag_chemistry_avg);
2436
2437 // Now set the formula of the oligomer: set its constructed-empty
2438 // formula to the formula that resulted from all the previous steps.
2439
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 88 times.
✗ Branch 6 not taken.
176 oligomer1_sp->getFormulaRef().accountFormula(
2440
2/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
176 frag_chemistry_formula.getActionFormula(), isotopic_data_csp, 1, ok);
2441
2442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(!ok)
2443 {
2444 qCritical() << "Failed to account formula:"
2445 << frag_chemistry_formula.getActionFormula();
2446 oligomer1_sp.reset();
2447 return -1;
2448 }
2449
2450 // qDebug() << "The oligomer1_sp before side chain contribution "
2451 // "has masses:"
2452 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
2453 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
2454 // << "and formula:"
2455 // << oligomer1_sp->getFormulaCstRef().getActionFormula();
2456
2457 // At this moment, the new fragment might be challenged for the
2458 // fragmented monomer's side chain contribution. For example, in
2459 // nucleic acids, it happens that during a fragmentation, the
2460 // base of the fragmented monomer is decomposed and goes
2461 // away. This is implemented in massXpert with the ability to
2462 // tell the fragmenter that upon fragmentation the mass of the
2463 // monomer is to be removed. The skeleton mass is then added to
2464 // the formula of the fragmentation pattern.
2465
2466
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 int monomer_contribution = fragmentation_config.getMonomerContribution();
2467
2468
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if(monomer_contribution)
2469 {
2470 MonomerSPtr monomer_csp =
2471 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
2472
2473 // First the masses.
2474 monomer_csp->accountMasses(
2475 oligomer1_sp->getMassRef(Enums::MassType::MONO),
2476 oligomer1_sp->getMassRef(Enums::MassType::AVG),
2477 monomer_contribution);
2478
2479 bool ok = false;
2480
2481 // Next the formula.
2482 oligomer1_sp->getFormulaRef().accountFormula(
2483 monomer_csp->getFormula(),
2484 isotopic_data_csp,
2485 monomer_contribution,
2486 ok);
2487
2488 if(!ok)
2489 {
2490 qCritical() << "Failed to account formula:"
2491 << monomer_csp->getFormula();
2492 oligomer1_sp.reset();
2493
2494 return -1;
2495 }
2496
2497 // qDebug() << "The oligomer1_sp after side chain contribution "
2498 // "has masses:"
2499 // << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
2500 // << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
2501 // << "updated formula:"
2502 // << oligomer1_sp->getFormulaRef().getActionFormula();
2503 }
2504
2505 // At this point we should check if the fragmentation
2506 // specification includes fragmentation rules that apply to this
2507 // fragment.
2508
2509 88 for(const FragmentationRuleSPtr &fragmentation_rule_sp :
2510
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 88 times.
88 fragmentation_config.getRulesCstRef())
2511 {
2512 // The accounting of the fragmentationrule is performed on a
2513 // neutral oligomer, as defined by the fragmentation
2514 // formula. Later, we'll have to take into account the
2515 // fact that the user might want to calculate fragment m/z values
2516 // with z>1.
2517
2518 // This round is not for real accounting, but only to check of the
2519 // currently iterated fragmentation rule should be accounted for (see
2520 // the true that indicates this call is only for checking).
2521
2522 double mono;
2523 double avg;
2524
2525 if(!accountFragmentationRule(fragmentation_rule_sp,
2526 /*only for checking*/ true,
2527 iter,
2528 Enums::FragEnd::RE,
2529 mono,
2530 avg))
2531 continue;
2532
2533 // This is why we first check above if the fragmentation rule was to
2534 // be accounted for: each fragmentationrule triggers the creation of a
2535 // new oligomer.
2536
2537 OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);
2538
2539 // qDebug() << "The oligomer2_sp has been copied from "
2540 // "oligomer1_sp and "
2541 // "has masses:"
2542 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
2543 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
2544 // << "and formula:"
2545 // << oligomer2_sp->getFormulaRef().getActionFormula();
2546
2547
2548 accountFragmentationRule(
2549 fragmentation_rule_sp,
2550 false,
2551 iter,
2552 Enums::FragEnd::RE,
2553 oligomer2_sp->getMassRef(Enums::MassType::MONO),
2554 oligomer2_sp->getMassRef(Enums::MassType::AVG));
2555
2556 bool ok = false;
2557
2558 oligomer2_sp->getFormulaRef().accountFormula(
2559 fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
2560 isotopic_data_csp,
2561 1,
2562 ok);
2563
2564 if(!ok)
2565 {
2566 qCritical()
2567 << "Failed to account formula:"
2568 << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
2569 oligomer1_sp.reset();
2570 oligomer2_sp.reset();
2571
2572 return -1;
2573 }
2574
2575 // qDebug() << "The oligomer2_sp after accounting for "
2576 // "fragmentation rule"
2577 // << fragmentation_rule_sp->getName() << "has masses:"
2578 // << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
2579 // << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
2580 // << "updated formula:"
2581 // << oligomer2_sp->getFormulaRef().getActionFormula();
2582
2583 // Let the following steps know that we actually succeeded
2584 // in preparing an oligonucleotide with a fragmentation
2585 // rule applied.
2586
2587 frag_rule_applied = true;
2588
2589 // At this point we have the fragment oligomer in a
2590 // neutral state (the fragmentation specification specifies a formula
2591 // to create a neutral fragmentation product). This is so that we can
2592 // later charge the ions as many times as requested by the user.
2593
2594 // We still have to account for potential formulas set to the
2595 // FragmentationConfig, like when the user asks that for each product
2596 // ion -H2O or -NH3 be accounted for,
2597 // which happens all the time when doing peptide fragmentations.
2598 // These formulas are stored in the m_formulas member of
2599 // FragmentationConfig.
2600
2601 // And once we have done this, we'll still have to actually charge the
2602 // fragments according to the FragmentationConfig's
2603 // [m_charge_level_start--m_stopIonizeLevel] range.
2604
2605 // Each supplementary step above demultiplies the "versions" of the
2606 // fragment Oligomer currently created (oligomer2_sp). All the newly
2607 // created "versions"
2608 // will need to be stored in a container
2609 // (formula_variant_oligomers). So, for the moment copy the current
2610 // oligomer into a template oligomer that will be used as a template
2611 // to create all the "variant" oligomers.
2612
2613 OligomerSPtr template_oligomer_for_formula_variants_sp =
2614 std::make_shared<Oligomer>(*oligomer2_sp);
2615
2616 // We can immediately set the name of template oligomer on which
2617 // to base the creation of the derivative formula-based
2618 // oligomers.
2619 QString name = QString("%1#%2#(%3)")
2620 .arg(fragmentation_config.getName())
2621 .arg(number + 1)
2622 .arg(fragmentation_rule_sp->getName());
2623
2624 int charge = 0;
2625
2626 // Set the name of this template oligomer, but with the
2627 // charge in the form of "#z=1".
2628 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
2629
2630 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
2631
2632 // We have not yet finished characterizing fully the oligomer, for
2633 // example, there might be formulas to be applied to the oligomer.
2634 // Also, the user might have asked for a number of charge states. To
2635 // store all these variants, create an oligomer container:
2636
2637 OligomerCollection formula_variant_oligomers(
2638 fragmentation_config.getName(), mcsp_polymer);
2639
2640 // Ask that this new container be filled-in with the variants obtained
2641 // by applying the Formula instances onto the template oligomer.
2642 // Indeed, the user may have asked in FragmentationConfig that
2643 // formulas like -H2O or -NH3 be accounted for
2644 // in the generation of the fragment oligomers. Account
2645 // for these potential formulas...
2646
2647 // Note that we use std::move when passing
2648 // template_oligomer_for_formula_variants_sp to the function because
2649 // the function will move it as the first item in the
2650 // formula_variant_oligomers container.
2651
2652 // int accountedFormulas =
2653 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
2654 formula_variant_oligomers,
2655 fragmentation_config,
2656 name,
2657 charge);
2658
2659 // qDebug() << "There are now"
2660 // << formula_variant_oligomers.getOligomersCstRef().size()
2661 // << "formula variant oligomers";
2662
2663 // We now have a container of oligomers (or only one if there
2664 // was no formula to take into account). For each
2665 // oligomer, we have to account for the charge levels
2666 // asked by the user.
2667
2668 OligomerCollection ionization_variant_oligomers =
2669 accountIonizationLevels(formula_variant_oligomers,
2670 fragmentation_config);
2671
2672 // qDebug() << "We now got"
2673 // <<
2674 // ionization_variant_oligomers.getOligomersCstRef().size()
2675 // << "ionization variant oligomers";
2676
2677 // At this point we do not need the initial *uncharged* Oligomer
2678 // instances anymore.
2679 formula_variant_oligomers.clear();
2680
2681 // qDebug() << "We still have"
2682 // <<
2683 // ionization_variant_oligomers.getOligomersCstRef().size()
2684 // << "ionization variant oligomers";
2685
2686 // First off, we can finally delete the grand template oligomer.
2687 oligomer2_sp.reset();
2688
2689 if(!ionization_variant_oligomers.size())
2690 {
2691 qCritical() << "Failed to generate ionized fragment oligomers.";
2692 return -1;
2693 }
2694
2695 // Finally transfer all the oligomers generated for this fragmentation
2696 // to the container of ALL the oligomers. But before making
2697 // the transfer, compute the elemental composition and store it
2698 // as a property object.
2699
2700 // Involves a std::move operation.
2701 std::size_t transferred_count =
2702 transferOligomers(ionization_variant_oligomers, m_oligomers);
2703
2704 // qDebug() << "The number of transferred Oligomers:"
2705 // << transferred_count;
2706
2707 // qDebug() << "And now we have"
2708 // << m_oligomers.getOligomersCstRef().size()
2709 // << "oligomers in total";
2710
2711 count += transferred_count;
2712 }
2713 // End of
2714 // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
2715 // fragmentation_config.getRulesCstRef())
2716
2717 // We are here because of two possible reasons:
2718
2719 // 1. because the container of fragmentation_rule_sp was empty, in which
2720 // case we still have to validate and terminate the oligomer1
2721 // (frag_rule_applied is false);
2722
2723 // 2. because we finished dealing with the container of
2724 // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
2725 // of fragments if none of the fragmentationrules analyzed above gave a
2726 // successfully generated fragment(frag_rule_applied is false).
2727
2728
1/2
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
88 if(!frag_rule_applied)
2729 {
2730 // At this point we have a single fragment oligomer because we did not
2731 // had to apply any fragmentation rule, and thus have generated no
2732 // variant of it. We may have formulas to apply, as there might be
2733 // formulas in FragmentationConfig.
2734
2735 // So, first create an oligomer with the "default"
2736 // fragmentation specification-driven neutral state (that
2737 // is, charge = 0).
2738
2739
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 OligomerSPtr template_oligomer_for_formula_variants_sp =
2740
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 std::make_shared<Oligomer>(*oligomer1_sp);
2741
2742 // We can immediately set the name of template oligomer on which
2743 // to base the creation of the derivative formula-based
2744 // oligomers.
2745
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 QString name = QString("%1#%2")
2746
2/6
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
176 .arg(fragmentation_config.getName())
2747
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 .arg(number + 1);
2748
2749 88 int charge = 0;
2750
2751 // Set the name of this template oligomer, but with the
2752 // charge in the form of "#z=1".
2753
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
176 QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);
2754
2755
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 template_oligomer_for_formula_variants_sp->setName(name_with_charge);
2756
2757 // We have not yet finished characterizing fully the oligomer, for
2758 // example, there might be formulas to be applied to the oligomer.
2759 // Also, the user might have asked for a number of charge states. To
2760 // store all these variants, create an oligomer container:
2761
2762 88 OligomerCollection formula_variant_oligomers(
2763
3/6
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 88 times.
✗ Branch 7 not taken.
176 fragmentation_config.getName(), mcsp_polymer);
2764
2765 // Ask that this new container be filled-in with the variants obtained
2766 // by applying the Formula instances onto the template oligomer.
2767 // Indeed, the user may have asked in FragmentationConfig that
2768 // formulas like -H2O or -NH3 be accounted for
2769 // in the generation of the fragment oligomers. Account
2770 // for these potential formulas...
2771
2772 // Note that we use std::move when passing
2773 // template_oligomer_for_formula_variants_sp to the function because
2774 // the function will move it as the first item in the
2775 // formula_variant_oligomers container.
2776
2777 // int accountedFormulas =
2778
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
2779 formula_variant_oligomers,
2780 fragmentation_config,
2781 name,
2782 charge);
2783
2784 // qDebug() << "There are now"
2785 // << formula_variant_oligomers.getOligomersCstRef().size()
2786 // << "formula variant oligomers";
2787
2788 // We now have a list of oligomers (or only one if there
2789 // was no formula to take into account). For each
2790 // oligomer, we have to account for the charge levels
2791 // asked by the user.
2792
2793 88 OligomerCollection ionization_variant_oligomers =
2794 accountIonizationLevels(formula_variant_oligomers,
2795
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 fragmentation_config);
2796
2797 // qDebug() << "We now got"
2798 // <<
2799 // ionization_variant_oligomers.getOligomersCstRef().size()
2800 // << "ionization variant oligomers";
2801
2802 // At this point we do not need the initial *uncharged* Oligomer
2803 // instances anymore.
2804
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 formula_variant_oligomers.clear();
2805
2806 // qDebug() << "We still have"
2807 // <<
2808 // ionization_variant_oligomers.getOligomersCstRef().size()
2809 // << "ionization variant oligomers";
2810
2811 // First off, we can finally delete the grand template
2812 // oligomer (oligomer with no fragmentation rules applied).
2813
1/2
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
88 oligomer1_sp.reset();
2814
2815
2/4
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 88 times.
88 if(!ionization_variant_oligomers.size())
2816 {
2817 qCritical() << "Failed to generate ionized fragment oligomers.";
2818 return -1;
2819 }
2820
2821 // Finally transfer all the oligomers generated for this fragmentation
2822 // to the container of ALL the oligomers. But before making
2823 // the transfer, compute the elemental composition and store it
2824 // as a property object.
2825
2826 // Involves a std::move operation.
2827 88 std::size_t transferred_count =
2828
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 transferOligomers(ionization_variant_oligomers, m_oligomers);
2829
2830 // qDebug() << "The number of transferred Oligomers:"
2831 // << transferred_count;
2832
2833 // qDebug() << "And now we have"
2834 // << m_oligomers.getOligomersCstRef().size()
2835 // << "oligomers in total";
2836
2837 88 count += transferred_count;
2838
1/4
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 88 times.
88 }
2839 // End of
2840 // if(!frag_rule_applied)
2841 else // (frag_rule_applied == true)
2842 {
2843 // There were fragmentation rule(s) that could be
2844 // successfully applied. Thus we already have created the
2845 // appropriate oligomers. Simply delete the template
2846 // oligomer.
2847
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
88 oligomer1_sp.reset();
2848 }
2849
1/4
✓ Branch 4 taken 88 times.
✗ Branch 5 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
176 }
2850 // End of
2851 // for (int iter = fragmentation_config.getStopIndex();
2852 // iter > fragmentation_config.getStopIndex() - 1; --iter, ++number)
2853
2854 return count;
2855
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 }
2856
2857 /*!
2858 \brief Accounts for the \a fragmentation_rule_sp FragmentationRule if the
2859 Monomer instance at \a monomer_index matches the requirement of the rule along
2860 with \a frag_end.
2861
2862 The masses are accounted to \a mono and \a avg.
2863
2864 If \a only_for_checking is true, then the computation is only a verification
2865 that the FragmentationRule is to be accounted for. If \a only_for_checking is
2866 false, the fragmentation rule is actually accounted for.
2867
2868 Returns true if the FragmentationRule should be accounted for (if \a
2869 only_for_checking is true) or if it was effectively accounted for (if \a
2870 only_for_checking is false), false otherwise.
2871 */
2872 bool
2873 Fragmenter::accountFragmentationRule(
2874 FragmentationRuleSPtr fragmentation_rule_sp,
2875 bool only_for_checking,
2876 std::size_t monomer_index,
2877 Enums::FragEnd frag_end,
2878 double &mono,
2879 double &avg)
2880 {
2881 MonomerSPtr prev_monomer_csp = 0;
2882 MonomerSPtr next_monomer_csp = 0;
2883
2884 IsotopicDataCstSPtr isotopic_data_csp =
2885 mcsp_polChemDef->getIsotopicDataCstSPtr();
2886
2887 if(fragmentation_rule_sp == nullptr || fragmentation_rule_sp.get() == nullptr)
2888 qFatalStream() << "Programming error. Pointer cannot be nullptr.";
2889
2890 if(monomer_index >= mcsp_polymer->size())
2891 qFatalStream() << "Programming error. The index is out of bounds.";
2892
2893 MonomerSPtr monomer_csp =
2894 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(monomer_index);
2895
2896 if(!fragmentation_rule_sp->getCurrCode().isEmpty())
2897 if(fragmentation_rule_sp->getCurrCode() != monomer_csp->getCode())
2898 return false;
2899
2900 if(!fragmentation_rule_sp->getPrevCode().isEmpty() &&
2901 !fragmentation_rule_sp->getNextCode().isEmpty())
2902 {
2903 // Use the overload (see globals.hpp)
2904 if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
2905 static_cast<bool>(frag_end & Enums::FragEnd::NE))
2906 {
2907 if(!monomer_index)
2908 // There cannot be any getPrevCode since we are at monomer_index ==
2909 // 0, at the first monomer_csp of the fragmentation
2910 // series. That means that we can return immediately.
2911 return false;
2912
2913 // Since we know that we are either in LEFT or NONE end
2914 // mode, we know that previous is at monomer_index 'monomer_index'
2915 // - 1. Thus get the monomer_csp out of the sequence for this
2916 // monomer_index.
2917
2918 prev_monomer_csp =
2919 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
2920 monomer_index - 1);
2921
2922 if(monomer_index == mcsp_polymer->size() - 1)
2923 // There cannot be any next code since we are already at
2924 // the last monomer_csp in the fragmentation series.
2925 return false;
2926
2927 next_monomer_csp =
2928 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
2929 monomer_index + 1);
2930 }
2931 else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
2932 {
2933 if(!monomer_index)
2934 // There cannot be any getNextCode since getCurrCode is the last
2935 // monomer_csp in the fragmentation series.
2936 return false;
2937
2938 next_monomer_csp =
2939 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
2940 monomer_index - 1);
2941
2942 if(monomer_index == mcsp_polymer->size() - 1)
2943 // There cannot be any previous code since getCurrCode is the
2944 // first in the fragmentation series.
2945 return false;
2946
2947 prev_monomer_csp =
2948 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
2949 monomer_index + 1);
2950 }
2951 else
2952 return false;
2953
2954 // Now that the getPrevCode and getNextCode have been correctly
2955 // identified, we can go on and check if some conditions are
2956 // met.
2957
2958 if(fragmentation_rule_sp->getPrevCode() == prev_monomer_csp->getCode() &&
2959 fragmentation_rule_sp->getNextCode() == next_monomer_csp->getCode())
2960 {
2961 if(only_for_checking)
2962 return true;
2963
2964 // The fragmentation rule condition is met, we can apply its
2965 // formula.
2966
2967 bool ok;
2968
2969 Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
2970 temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);
2971
2972 if(!ok)
2973 {
2974 qCritical() << "Failed to account fragmentation rule";
2975
2976 return false;
2977 }
2978
2979 return true;
2980 }
2981 else
2982 {
2983 if(only_for_checking)
2984 return false;
2985 else
2986 return true;
2987 }
2988 }
2989 // End of
2990 // if (!fragmentation_rule_sp->getPrevCode().isEmpty() &&
2991 // !fragmentation_rule_sp->getNextCode().isEmpty())
2992 else if(!fragmentation_rule_sp->getPrevCode().isEmpty())
2993 {
2994 if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
2995 static_cast<bool>(frag_end & Enums::FragEnd::NE))
2996 {
2997 if(!monomer_index)
2998 // There cannot be any getPrevCode since getCurrCode is already
2999 // the first of the fragmentation series.
3000 return false;
3001
3002 // Since we know that fragEnd is either LEFT or NONE end, we
3003 // know what monomer_index has the getPrevCode:
3004
3005 prev_monomer_csp =
3006 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
3007 monomer_index - 1);
3008 }
3009 else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
3010 {
3011 if(monomer_index == mcsp_polymer->size() - 1)
3012 // There cannot be any getPrevCode since getCurrCode is already
3013 // the first of the fragmentation series.
3014 return false;
3015
3016 prev_monomer_csp =
3017 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
3018 monomer_index + 1);
3019 }
3020 else
3021 return false;
3022
3023 // Now that we have correctly identified the getPrevCode, we can go
3024 // on and check if some conditions are met.
3025
3026 if(fragmentation_rule_sp->getPrevCode() == prev_monomer_csp->getCode())
3027 {
3028 if(only_for_checking)
3029 return true;
3030
3031 // The fragmentation rule condition is met, we can apply its
3032 // formula.
3033
3034 bool ok;
3035
3036 Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
3037 temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);
3038
3039 if(!ok)
3040 {
3041 qCritical() << "Failed to account fragmentation rule";
3042
3043 return false;
3044 }
3045
3046 return true;
3047 }
3048 else
3049 {
3050 if(only_for_checking)
3051 return false;
3052 else
3053 return true;
3054 }
3055 }
3056 // End of
3057 // else if (!fragmentation_rule_sp->getPrevCode().isEmpty())
3058 else if(!fragmentation_rule_sp->getNextCode().isEmpty())
3059 {
3060 if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
3061 static_cast<bool>(frag_end & Enums::FragEnd::NE))
3062 {
3063 if(monomer_index == mcsp_polymer->size() - 1)
3064 // There cannot be any getNextCode since getCurrCode is already
3065 // the last of the fragmentation series.
3066 return false;
3067
3068 // Since we know that fragEnd is either LEFT or NONE end, we
3069 // know what monomer_index has the getPrevCode:
3070
3071 next_monomer_csp =
3072 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
3073 monomer_index + 1);
3074 }
3075 else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
3076 {
3077 if(!monomer_index)
3078 // There cannot be any getPrevCode since getCurrCode is already
3079 // the last of the fragmentation series.
3080 return false;
3081
3082 next_monomer_csp =
3083 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
3084 monomer_index - 1);
3085 }
3086 else
3087 return false;
3088
3089 // Now that we have correctly identified the getNextCode, we can go
3090 // on and check if some conditions are met.
3091
3092 if(fragmentation_rule_sp->getNextCode() == next_monomer_csp->getCode())
3093 {
3094 if(only_for_checking)
3095 return true;
3096
3097 // The fragmentation rule condition is met, we can apply its
3098 // formula.
3099
3100 bool ok;
3101
3102 Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
3103 temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);
3104
3105 if(!ok)
3106 {
3107 qCritical() << "Failed to account fragmentation rule";
3108
3109 return false;
3110 }
3111
3112 return true;
3113 }
3114 else
3115 {
3116 if(only_for_checking)
3117 return false;
3118 else
3119 return true;
3120 }
3121 }
3122 // End of
3123 // else if (!fragmentation_rule_sp->getNextCode().isEmpty())
3124 else
3125 {
3126 // All the prev and next codes are empty, which means that we
3127 // consider the conditions verified.
3128 if(only_for_checking)
3129 return true;
3130
3131 bool ok;
3132
3133 Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
3134 temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);
3135
3136 if(!ok)
3137 {
3138 qCritical() << "Failed to account fragmentation rule";
3139
3140 return false;
3141 }
3142
3143 return true;
3144 }
3145
3146 // We should never reach this point !
3147 Q_ASSERT(0);
3148
3149 return false;
3150 }
3151
3152 /*!
3153 \brief Generates as many more Oligomer fragments are there are Formula instances
3154 to be accounted for.
3155
3156 \a template_oligomer_sp is the Oligomer instance that serves as a template to
3157 generate as many variants as there are Formula instances in the \a
3158 fragmentation_config's member container of Formula instances. For example, if
3159 the caller wants to generate for each Oligomer frament a variant with -H2O and
3160 -NH3 formulas applied, then each fragment Oligomer in the \a oligomers
3161 collection will generate two variants: one with water-production decomposition
3162 and one with ammonia-producing decomposition. Thus, in the end, there will be
3163 as many times more fragment Oligomer instances in \a oligomers as there are
3164 Formula instances to be accounted for.
3165
3166 \a name and \a charge are the name and charge onto which base the creation of
3167 the variant fragment Oligomer new name so that the fragment Oligomer has a name
3168 that documents the kind of fragmentation pathway is was generated from along
3169 with both the Formula that was accounted for and finally its charge.
3170
3171 Returns the count of variant fragment Oligomer instances that were added into
3172 the \a oligomers collection.
3173 */
3174 std::size_t
3175 283 Fragmenter::accountFormulas(OligomerSPtr &&template_oligomer_sp,
3176 OligomerCollection &oligomers,
3177 const FragmentationConfig &fragmentation_config,
3178 const QString &name,
3179 int charge)
3180 {
3181 // qDebug() << "Now accounting for formulas checked by the user (-NH3, -H20, "
3182 //"for example.)";
3183
3184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 283 times.
283 if(template_oligomer_sp == nullptr || template_oligomer_sp.get() == nullptr)
3185 qFatalStream() << "Programming error. Pointer cannot be nullptr.";
3186
3187 283 IsotopicDataCstSPtr isotopic_data_csp =
3188 283 mcsp_polChemDef->getIsotopicDataCstSPtr();
3189
3190 283 std::size_t count = 0;
3191
3192 // The oligomer that we get as parameter is the
3193 // template on which to base the derivatives made on the basis of the
3194 // formulas. However, we must use it also as a mere fragmentation oligomer.
3195 // We get it as a pointer to be moved into the container as the very first
3196 // item in it.
3197
3198 // qDebug() << "Template we got to base the formula variants on:"
3199 // << template_oligomer_sp->toString();
3200
3201
2/4
✓ Branch 1 taken 283 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 283 times.
✗ Branch 5 not taken.
283 oligomers.getOligomersRef().push_back(std::move(template_oligomer_sp));
3202
3203 // template_oligomer_sp is now nullptr. We'll base the copies below
3204 // on the first item of the container.
3205
3206
3/4
✓ Branch 1 taken 283 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✓ Branch 4 taken 283 times.
331 for(const Formula &formula : fragmentation_config.getFormulasCstRef())
3207 {
3208 // We will apply the formula to a copy of the template oligomer
3209 48 OligomerSPtr formula_variant_oligomer_sp =
3210
2/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
48 std::make_shared<Oligomer>(*oligomers.getOligomersRef().front());
3211
3212 // qDebug() << "Created new oligomer as a copy of the template:"
3213 // << formula_variant_oligomer_sp->toString();
3214
3215 // First, account the masses
3216
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 Formula temp_formula(formula);
3217
3218 // qDebug() << "Accounting of fragmentation config's formula:"
3219 // << temp_formula.getActionFormula();
3220
3221 48 bool ok;
3222
3223
3/6
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 48 times.
✗ Branch 9 not taken.
48 temp_formula.accountMasses(
3224 ok,
3225 isotopic_data_csp,
3226 formula_variant_oligomer_sp->getMassRef(Enums::MassType::MONO),
3227 formula_variant_oligomer_sp->getMassRef(Enums::MassType::AVG));
3228
3229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if(!ok)
3230 {
3231 qCritical() << "Failed to account formula' masses:"
3232 << formula.getActionFormula();
3233
3234 formula_variant_oligomer_sp.reset();
3235 continue;
3236 }
3237
3238 // qDebug() << "Right after accounting the formula's masses"
3239 // << temp_formula.getActionFormula() << "the oligomer becomes:"
3240 // << formula_variant_oligomer_sp->toString();
3241
3242 // Second, account the formula.
3243
2/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
96 formula_variant_oligomer_sp->getFormulaRef().accountFormula(
3244
2/6
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
96 formula.getActionFormula(), isotopic_data_csp, 1, ok);
3245
3246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if(!ok)
3247 {
3248 qCritical() << "Failed to account formula:"
3249 << formula.getActionFormula();
3250
3251 formula_variant_oligomer_sp.reset();
3252 continue;
3253 }
3254
3255 // qDebug() << "Right after accounting the formula proper"
3256 // << temp_formula.getActionFormula() << "the oligomer becomes:"
3257 // << formula_variant_oligomer_sp->toString();
3258
3259 // The new oligomer could be generated correctly. Append the
3260 // formula to its name, so that we'll be able to recognize it.
3261
3262
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 QString name_with_formula = QString("%1#%2#z=%3")
3263
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
96 .arg(name)
3264
2/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
96 .arg(formula.getActionFormula())
3265 48 .arg(charge);
3266
3267
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 formula_variant_oligomer_sp->setName(name_with_formula);
3268
3269 // At this point append the new oligomer to the list.
3270 // qDebug() << "Pushing back to oligomers, the new variant:"
3271 // << formula_variant_oligomer_sp->toString();
3272
3273
2/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
48 oligomers.getOligomersRef().push_back(formula_variant_oligomer_sp);
3274
3275 48 ++count;
3276
1/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
96 }
3277
3278 // qDebug() << "At the time of returning, the number of oligomers:"
3279 // << oligomers.getOligomersRef().size();
3280
3281
1/2
✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
283 return count;
3282 283 }
3283
3284 /*!
3285 \brief For each ionization level contained in the \a fragmentation_config
3286 start and stop ionization level members, create fragment Oligomer variants of
3287 all the Oligomer instances present in \a oligomers.
3288
3289 The generated fragment Oligomer instances are stored in a OligomerCollection
3290 that is returned.
3291 */
3292 OligomerCollection
3293 283 Fragmenter::accountIonizationLevels(
3294 OligomerCollection &oligomers,
3295 const FragmentationConfig &fragmentation_config)
3296 {
3297 283 bool failed = false;
3298
3299 283 IsotopicDataCstSPtr isotopic_data_csp =
3300 283 mcsp_polChemDef->getIsotopicDataCstSPtr();
3301
3302 // qDebug() << "Handling the ionization levels for a container of"
3303 // << oligomers.getOligomersCstRef().size() << "oligomers";
3304
3305 // We get an Oligomer container (or only one, in fact, if no
3306 // -H2O/-NH3 formulas, for example, were asked for
3307 // (accountFormulas()), and we have for each one to compute the required
3308 // ionisation levels. Indeed, the user might ask for fragments
3309 // that are charged with different charge levels. Thus we need to create as
3310 // many new oligomers as needed for the different charge levels. Because the
3311 // ionization changes the values in the oligomer, and we need a new oligomer
3312 // each time, we duplicate the oligomer each time we need it.
3313
3314
1/2
✓ Branch 1 taken 283 times.
✗ Branch 2 not taken.
283 int ionization_level = fragmentation_config.getStartIonizeLevel();
3315
1/2
✓ Branch 1 taken 283 times.
✗ Branch 2 not taken.
283 int ionization_stop_level = fragmentation_config.getStopIonizeLevel();
3316
3317 // qDebug() << "Requested ionization level range:"
3318 // << "[" << ionization_level << "-" << ionization_stop_level << "]";
3319
3320 // We have to perform the operation for each oligomer in
3321 // oligomers. We populate a new Oligomers container that we will return
3322 // filled with at least the same Oligomer instances that were in
3323 // the Oligomer container passed as parameter.
3324
3325 283 OligomerCollection charge_state_oligomers(fragmentation_config.getName(),
3326
3/8
✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 283 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 283 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
566 mcsp_polymer);
3327
3328
3/4
✓ Branch 1 taken 283 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 331 times.
✓ Branch 4 taken 283 times.
614 for(const OligomerSPtr &iter_oligomer_sp : oligomers.getOligomersRef())
3329 {
3330 // The oligomer being iterated into is a non-ionized oligomer and
3331 // we will create as many ionized variants as there are ionization levels
3332 // requested.
3333
3334 // qDebug() << "Now handling non-ionized oligomer with masses:"
3335 // << "mono:" << iter_oligomer_sp->getMass(Enums::MassType::MONO)
3336 // << "avg:" << iter_oligomer_sp->getMass(Enums::MassType::AVG)
3337 // << "and formula:"
3338 // << iter_oligomer_sp->getFormulaCstRef().getActionFormula();
3339
3340 // For this new oligomer, reset the initial ionization level.
3341
1/2
✓ Branch 1 taken 331 times.
✗ Branch 2 not taken.
331 ionization_level = fragmentation_config.getStartIonizeLevel();
3342
3343
2/2
✓ Branch 0 taken 467 times.
✓ Branch 1 taken 331 times.
798 while(ionization_level <= ionization_stop_level)
3344 {
3345 // qDebug() << "Current ionization level:" << ionization_level;
3346
3347
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 Ionizer temp_ionizer(m_ionizer);
3348
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 temp_ionizer.setLevel(ionization_level);
3349
3350 // Make a copy of the non-ionized oligomer.
3351
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 OligomerSPtr new_oligomer_sp =
3352
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 std::make_shared<Oligomer>(*iter_oligomer_sp);
3353
3354
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 new_oligomer_sp->setIonizer(temp_ionizer);
3355
3356 // If the result of the call below is
3357 // Enums::IonizationOutcome::FAILED, then that means that there was an
3358 // error and we should return immediately. If it is
3359 // Enums::IonizationOutcome::UNCHANGED, then that means that no error
3360 // was encountered, but that no actual ionization took place, so we
3361 // need not take into account the oligomer.
3362
3363
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 Enums::IonizationOutcome ionization_outcome =
3364
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 new_oligomer_sp->ionize();
3365
3366
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 467 times.
467 if(ionization_outcome == Enums::IonizationOutcome::FAILED)
3367 {
3368 qCritical() << "Failed to ionize the oligomer.";
3369
3370 new_oligomer_sp.reset();
3371 failed = true;
3372 break;
3373 }
3374
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 467 times.
467 else if(ionization_outcome == Enums::IonizationOutcome::UNCHANGED)
3375 {
3376 qInfo() << "The Oligomer ionization changed nothing.";
3377 new_oligomer_sp.reset();
3378 continue;
3379 }
3380
3381 // qDebug() << "After ionization with level:"
3382 // << ionization_level << ", oligomer has masses:"
3383 // << "mono:" <<
3384 // new_oligomer_sp->getMass(Enums::MassType::MONO)
3385 // << "avg:" <<
3386 // new_oligomer_sp->getMass(Enums::MassType::AVG);
3387
3388 // Go on with effectively ionized oligomer variant.
3389
3390 467 bool ok = false;
3391
3392
2/4
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 467 times.
✗ Branch 6 not taken.
1401 new_oligomer_sp->getFormulaRef().accountFormula(
3393
3/8
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 467 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 467 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
934 temp_ionizer.getFormulaCstRef().getActionFormula(),
3394 isotopic_data_csp,
3395
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 temp_ionizer.getLevel(),
3396 ok);
3397
3398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 467 times.
467 if(!ok)
3399 {
3400 qCritical() << "Failed to account the ionizer formula:"
3401 << temp_ionizer.getFormulaCstRef().getActionFormula();
3402 new_oligomer_sp.reset();
3403
3404 continue;
3405 }
3406
3407 // qDebug() << "After ionization with level:"
3408 // << ionization_level << ", oligomer has formula:"
3409 // << new_oligomer_sp->getFormulaCstRef().getActionFormula();
3410
3411 // At this point the ionization did indeed perform
3412 // something interesting, craft the name of the resulting
3413 // oligomer and set it. We must of the name of the
3414 // oligomer, but simply replace the value substring
3415 // "#z=xx" with "z=yy".
3416
3417
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 QString name = new_oligomer_sp->getName();
3418
3/6
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 467 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 467 times.
✗ Branch 8 not taken.
467 QString charge_string = QString("z=%1").arg(temp_ionizer.charge());
3419
3420
3/6
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 467 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 467 times.
✗ Branch 8 not taken.
467 name.replace(QRegularExpression("z=\\d+$"), charge_string);
3421
3422
1/2
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
467 new_oligomer_sp->setName(name);
3423
3424 // qDebug() << "After ionization, oligomer has name:"
3425 // << new_oligomer_sp->getName();
3426
3427
2/4
✓ Branch 1 taken 467 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 467 times.
✗ Branch 5 not taken.
467 charge_state_oligomers.getOligomersRef().push_back(new_oligomer_sp);
3428
3429 467 ++ionization_level;
3430
1/2
✓ Branch 2 taken 467 times.
✗ Branch 3 not taken.
934 }
3431 // End of
3432 // while(ionization_level <= ionization_stop_level)
3433 // for (int iter = charge_level_start; iter < charge_level_stop;
3434 // ++iter)
3435
3436 // If there was a single failure, we get here with failed
3437 // set to true. In that case, free the charge_state_oligomers and
3438 // return NULL.
3439
3440 331 if(failed)
3441 {
3442 charge_state_oligomers.clear();
3443
1/2
✓ Branch 0 taken 283 times.
✗ Branch 1 not taken.
283 return charge_state_oligomers;
3444 }
3445 }
3446 // End of
3447 // for(const OligomerSPtr &iter_oligomer_sp : oligomers.getOligomersRef())
3448
3449 return charge_state_oligomers;
3450 283 }
3451
3452
3453 } // namespace libXpertMassCore
3454 } // namespace MsXpS
3455