GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Cleaver.cpp
Date: 2025-11-20 01:41:33
Lines:
384/469
81.9%
Functions:
19/20
95.0%
Branches:
347/870
39.9%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * msXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009, ..., 2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the msXpertSuite project.
10 *
11 * The msXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Local includes
35 #include "MsXpS/libXpertMassCore/OligomerCollection.hpp"
36 #include "MsXpS/libXpertMassCore/PolChemDef.hpp"
37 #include "MsXpS/libXpertMassCore/Cleaver.hpp"
38 #include "MsXpS/libXpertMassCore/CrossLink.hpp"
39
40 namespace MsXpS
41 {
42 namespace libXpertMassCore
43 {
44
45
46 /*!
47 \class MsXpS::libXpertMassCore::Cleaver
48 \inmodule libXpertMassCore
49 \ingroup PolChemDefAqueousChemicalReactions
50 \inheaderfile Cleaver.hpp
51
52 \brief The Cleaver class provides a model for performing aqueous cleavage
53 reactions involving \l{CleavageAgent} objects and \l{Polymer} \l{Sequence}s.
54
55 \sa CleavageAgent, CleavageMotif, CleavageConfig, Ionizer
56 */
57
58
59 /*!
60 \variable MsXpS::libXpertMassCore::Cleaver::mcsp_polymer
61
62 \brief The \l Polymer polymer that is being cleaved (digested).
63 */
64
65
66 /*!
67 \variable MsXpS::libXpertMassCore::Cleaver::mcsp_polChemDef
68
69 \brief The \l PolChemDef polymer chemistry definition.
70 */
71
72 /*!
73 \variable MsXpS::libXpertMassCore::Cleaver::m_cleavageConfig
74
75 \brief The CleavageConfig that configures the cleavage reaction.
76 */
77
78 /*!
79 \variable MsXpS::libXpertMassCore::Cleaver::m_calcOptions
80
81 \brief The CalcOptions that configure the way masses and formulas are to be
82 computed.
83 */
84
85 /*!
86 \variable MsXpS::libXpertMassCore::Cleaver::m_ionizer
87
88 \brief The Ionizer that directs the ionization of the Oligomer instances
89 obtained by cleaving the Polymer.
90 */
91
92 /*!
93 \variable MsXpS::libXpertMassCore::Cleaver::m_doCleaveIndices
94
95 \brief The vector of indices in the Polymer Sequence that are \e{indeed} the
96 site of cleavage.
97 */
98
99 /*!
100 \variable MsXpS::libXpertMassCore::Cleaver::m_doNotCleaveIndices
101
102 \brief The vector of indices in the Polymer Sequence that are \e{not} the site
103 of cleavage.
104 */
105
106 /*!
107 \variable MsXpS::libXpertMassCore::Cleaver::m_oligomers
108
109 \brief The vector of Oligomer instances generated as a result of the cleavage.
110 */
111
112
113 /*!
114 \brief Constructs an entirely empty Cleaver instance.
115 */
116
4/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
2 Cleaver::Cleaver()
117 {
118
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
1 }
119
120 /*!
121 \brief Constructs a Cleaver instance with a number of parameters.
122
123 \list
124 \li \a polymer_cqsp The Polymer instance to be cleaved.
125
126 \li \a pol_chem_def_csp The PolChemDef (polymer chemistry definition) that is
127 the context in which the Polymer exists.
128
129 \li \a cleavage_config The CleavageConfig instance that configures the cleavage.
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 17 Cleaver::Cleaver(PolymerCstQSPtr polymer_cqsp,
141 PolChemDefCstSPtr pol_chem_def_csp,
142 const CleavageConfig &cleavage_config,
143 const CalcOptions &calc_options,
144 17 const Ionizer &ionizer)
145
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 : mcsp_polymer(polymer_cqsp),
146 17 mcsp_polChemDef(pol_chem_def_csp),
147
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 m_cleavageConfig(cleavage_config),
148
3/6
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 17 times.
✗ Branch 8 not taken.
34 m_ionizer(ionizer)
149 {
150
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
17 if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
151 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
152
153
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
17 if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
154 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
155
156
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 m_calcOptions.initialize(calc_options);
157
158 // qDebug() << "Other calc options:" << calc_options.toString();
159 // qDebug() << "And now *this calc options:" << m_calcOptions.toString();
160
161 // qDebug() << "Instantiating Cleave with Ionizer:" << m_ionizer.toString();
162 // qDebug() << "Ionizer' isotopic data:"
163 // << m_ionizer.getIsotopicDataCstSPtr()->size();
164
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
17 }
165
166 /*!
167 \brief Constructs Cleaver instance as a copy of \a other.
168
169 If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
170 */
171 1 Cleaver::Cleaver(const Cleaver &other)
172
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 : mcsp_polymer(other.mcsp_polymer),
173 1 mcsp_polChemDef(other.mcsp_polChemDef),
174
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 m_cleavageConfig(other.m_cleavageConfig),
175
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
2 m_ionizer(other.m_ionizer)
176 {
177
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1 if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
178 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
179
180
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1 if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
181 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
182
183
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 m_calcOptions.initialize(other.m_calcOptions);
184
185 // qDebug() << "Other calc options:" << other.m_calcOptions.toString();
186 // qDebug() << "And now *this calc options:" << m_calcOptions.toString();
187
188 // qDebug() << "Instantiating Cleave with Ionizer:" << m_ionizer.toString();
189
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
1 }
190
191 /*!
192 \brief Desstructs this Cleaver instance
193 */
194 19 Cleaver::~Cleaver()
195 {
196
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 9 times.
✓ Branch 8 taken 19 times.
✗ Branch 9 not taken.
48 }
197
198 /*!
199 \brief Returns the CleavageAgent name (from the member CleavageConfig instance's
200 base class).
201 */
202 QString
203 2 Cleaver::getCleaveAgentName() const
204 {
205
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 return m_cleavageConfig.getName();
206 }
207
208 /*!
209 \brief Transfers (using std::move()) all the Oligomer instances from \a source
210 to \a dest.
211
212 After the transfer, the \a source Oligomer container is cleared since it
213 contains only nullptr items.
214 */
215 std::size_t
216 44 Cleaver::transferOligomers(OligomerCollection &source, OligomerCollection &dest)
217 {
218 44 std::size_t dest_oligomer_count_before = dest.getOligomersRef().size();
219
220 // Move each element from source to dest
221 88 dest.getOligomersRef().insert(
222 44 dest.getOligomersRef().end(),
223 44 std::make_move_iterator(source.getOligomersRef().begin()),
224 44 std::make_move_iterator(source.getOligomersRef().end()));
225
226 44 std::size_t dest_oligomer_count_after = dest.getOligomersRef().size();
227 44 std::size_t transferred_count =
228 dest_oligomer_count_after - dest_oligomer_count_before;
229
230 // Sanity check
231
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
44 if(transferred_count != source.getOligomersRef().size())
232 qFatalStream()
233 << "Programming error. Not all the Oligomers were transferred.";
234
235 // Now clear the source container which contains the same items as before but
236 // all the shared pointers are now nullptr.
237
238 44 source.getOligomersRef().clear();
239
240 44 return transferred_count;
241 }
242
243 /*!
244 \brief Returns a const reference to the member OligomerCollection instance.
245 */
246 const OligomerCollection &
247 30 Cleaver::getOligomerCollectionCstRef() const
248 {
249 30 return m_oligomers;
250 }
251
252 /*!
253 \brief Returns a reference to the member OligomerCollection instance.
254 */
255 OligomerCollection &
256 1 Cleaver::getOligomerCollectionRef()
257 {
258 1 return m_oligomers;
259 }
260
261 /*!
262 \brief Performs the actual cleavage, thus generating Oligomer instances that are
263 added to the member OligomerCollection instance.
264
265 If \a reset is true, the member OligomerCollection is first cleared; otherwise
266 the newly generated Oligomer instances are simply added to it.
267
268 Returns true upon success, false otherwise.
269 */
270 bool
271 10 Cleaver::cleave(bool reset)
272 {
273 // If the polymer sequence is empty, just return.
274
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(!mcsp_polymer->size())
275 {
276 qCritical() << "The polymer sequence is empty: nothing to cleave.";
277 return true;
278 }
279
280 // Ensure that the cleavage pattern was already parsed.
281
282
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(!m_cleavageConfig.getMotifsCstRef().size())
283 {
284 if(!m_cleavageConfig.parse())
285 {
286 qCritical() << "Failed to parse the cleavage options";
287
288 return false;
289 }
290 }
291
292 // qDebug() << "Number of motifs:"
293 // << m_cleavageConfig.getMotifsCstRef().size();
294
295
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(!fillCleavageIndices())
296 {
297 // qDebug() << "Index lists(cleave/nocleave) are empty."
298 // "No oligomer generated.";
299
300 // We can return true, as no error condition was found but not
301 // oligomers were generated.
302
303 return true;
304 }
305
306
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(!resolveCleavageNoCleavage())
307 {
308 qDebug() << "There are no cleavage indices left. Nothing to do.";
309
310 return false;
311 }
312
313
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if(!removeDuplicateCleavageIndices())
314 {
315 qDebug() << "There are no cleavage indices left. Nothing to do.";
316
317 return false;
318 }
319
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if(reset)
321 m_oligomers.clear();
322
323
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 10 times.
32 for(int iter = 0; iter <= m_cleavageConfig.getPartials(); ++iter)
324 {
325 // qDebug() << "Now performing partial cleavage" << iter;
326
327
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
22 if(cleavePartial(iter) == -1)
328 {
329 qCritical() << "Failed to perform partial cleavage" << iter;
330
331 return false;
332 }
333 }
334
335 // At this point we have the list of lists of oligomers, one list of
336 // oligomers for each partial cleavage.
337
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 m_doCleaveIndices.clear();
338
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
20 m_doNotCleaveIndices.clear();
339
340 return true;
341 }
342
343 /*!
344 \brief Fills-in all the index values that correspond to precise locations where
345 the cleavage reactions must occur. These indices represent location in the
346 member \l{Polymer} \l{Sequence}.
347
348 If the pattern only contains cleaving sites, all the indices are added to the
349 member container of cleavage indices. If the pattern contains also no-cleaving
350 sites (like with Trypsin's "-Lys/Pro" pattern), then the corresponding indices
351 are set to the member container of no-cleavage indices.
352
353 Returns the sum of the two cleavage/no-cleavage containers sizes. Or 0 if not a
354 single cleavage site was found in the Polymer Sequence.
355 */
356 int
357 10 Cleaver::fillCleavageIndices()
358 {
359 10 const std::vector<CleavageMotifSPtr> &cleavage_motifs =
360 10 m_cleavageConfig.getMotifsCstRef();
361
362
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 m_doCleaveIndices.clear();
363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 m_doNotCleaveIndices.clear();
364
365 // The cleavage might be performed on a selected portion of a sequence only,
366 // not necessarily on the whole polymer sequence.
367
368 // qDebug() << "The calculation options:" << m_calcOptions.toString();
369
370 10 IndexRangeCollection index_range_collection(
371 10 m_calcOptions.getIndexRangeCollectionCstRef());
372
373 // qDebug() << "The index range collection:"
374 // << index_range_collection.indicesAsText();
375
376 10 std::size_t index_start;
377 10 std::size_t index_stop;
378
379
2/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
10 if(!index_range_collection.size())
380 {
381 index_start = 0;
382 index_stop = mcsp_polymer->size();
383 }
384 else
385 {
386
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 index_start = index_range_collection.getRangeCstRefAt(0).m_start;
387
1/2
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
10 index_stop = index_range_collection.getRangeCstRefAt(0).m_stop;
388 }
389
390 // qDebug() << "index_start:" << index_start << "index_stop:" << index_stop;
391
392
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 10 times.
36 for(const CleavageMotifSPtr &cleavage_motif_sp : cleavage_motifs)
393 {
394 26 int index = index_start;
395
396 250 while(1)
397 {
398 250 ++index;
399
1/2
✓ Branch 1 taken 250 times.
✗ Branch 2 not taken.
250 index = findCleavageMotif(*cleavage_motif_sp, index, index_stop);
400
401
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 26 times.
250 if(index == -1)
402 break;
403
404 // Do not forget: The position at which the motif is found
405 // in the polymer sequence is not necessarily the position
406 // at which the cleavage will effectively occur. Indeed,
407 // let's say that we found such motif in the polymer
408 // sequence: "KKRKGP". This motif was extracted from a
409 // cleavage agent that had a pattern like this: "KKRK/GP". What
410 // we see here is that the cleavage occurs after the fourth
411 // monomer! And we must realize that the 'index' returned
412 // above corresponds to the index of the first 'K' in
413 // "KKRKGP" motif that was found in the polymer
414 // sequence. Thus we have to take into account the offset
415 //(+4, in our example, WHICH IS A POSITION and not an
416 // index, which is why we need to remove 1 below) of the
417 // cleavage:
418
419
1/2
✓ Branch 1 taken 224 times.
✗ Branch 2 not taken.
224 int actual_cleave_index = index + cleavage_motif_sp->getOffset() - 1;
420
421
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
224 if(actual_cleave_index < 0)
422 continue;
423
424
1/2
✓ Branch 0 taken 224 times.
✗ Branch 1 not taken.
224 if(actual_cleave_index >= static_cast<int>(index_stop))
425 break;
426
427 // qDebug() << __FILE__ << __LINE__
428 // << "Found new cleavage index:"
429 // << actual_cleave_index;
430
431
2/4
✓ Branch 1 taken 224 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 224 times.
✗ Branch 4 not taken.
224 if(cleavage_motif_sp->getCleavageAction() ==
432 Enums::CleavageAction::CLEAVE)
433 {
434
2/2
✓ Branch 0 taken 170 times.
✓ Branch 1 taken 54 times.
224 m_doCleaveIndices.push_back(actual_cleave_index);
435
436 // qDebug() << __FILE__ << __LINE__
437 // << "For cleavage, index:" << actual_cleave_index;
438 }
439 else if(cleavage_motif_sp->getCleavageAction() ==
440 Enums::CleavageAction::NO_CLEAVE)
441 {
442
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
224 m_doNotCleaveIndices.push_back(actual_cleave_index);
443
444 // qDebug() << __FILE__ << __LINE__
445 // << "Not for cleavage";
446 }
447 else
448 qFatalStream()
449 << "Programming error. Enums::CleavageAction::NOT_SET is not "
450 "possible here.";
451 }
452 // End of
453 // while (1)
454 }
455 // End of
456 // for (int iter = 0; iter < cleavage_motifs->size(); ++iter)
457
458 // Note that returning 0 is not an error condition, because a
459 // sequence where no site is found whatsoever will result in 0.
460 10 return m_doCleaveIndices.size() + m_doNotCleaveIndices.size();
461 10 }
462
463 /*!
464 \brief Removes form the container of cleavage indices all the indices that were
465 found to be no-cleavage indices in the corresponding container.
466
467 Returns the size of the container of cleavage indices.
468 */
469 int
470 10 Cleaver::resolveCleavageNoCleavage()
471 {
472 // Remove from the m_cleaveIndices container all the indices
473 // that are found in the m_noCleaveIndices vector.
474
475
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 for(const int &no_cleave_index : m_doNotCleaveIndices)
476 {
477 std::vector<int>::iterator the_iterator = m_doCleaveIndices.begin();
478
479 // The erase() below works because in the while statement we
480 // do test for the end() of the vector instead of determining
481 // that end once and storing it in a variable.
482 while(the_iterator != m_doCleaveIndices.end())
483 {
484 if((*the_iterator) == no_cleave_index)
485 the_iterator = m_doCleaveIndices.erase(the_iterator);
486 else
487 ++the_iterator;
488 }
489 }
490
491 #if 0
492 #Old version
493 for(int iter = 0; iter < m_noCleaveIndices.size(); ++iter)
494 {
495 int noCleaveIndex = m_noCleaveIndices.at(iter);
496
497 for(int jter = 0; jter < m_cleaveIndices.size(); ++jter)
498 {
499 int cleaveIndex = m_cleaveIndices.at(jter);
500
501 if(noCleaveIndex == cleaveIndex)
502 m_cleaveIndices.removeAt(jter);
503 }
504 }
505 #endif
506
507 10 return m_doCleaveIndices.size();
508 }
509
510 /*!
511 \brief Removes the duplicate cleavage indices from the container of cleavage
512 indices.
513 */
514 int
515 10 Cleaver::removeDuplicateCleavageIndices()
516 {
517 10 std::sort(m_doCleaveIndices.begin(), m_doCleaveIndices.end());
518 10 auto last = std::unique(m_doCleaveIndices.begin(), m_doCleaveIndices.end());
519 10 m_doCleaveIndices.erase(last, m_doCleaveIndices.end());
520
521 10 return m_doCleaveIndices.size();
522 }
523
524 /*!
525 \brief Returns an index at which the \a cleavage_motif CleavageMotif is found in
526 the Polymer Sequence.
527
528 The search is started at index \a index_start and is stopped at index \a
529 index_stop.
530
531 If \a cleavage_motif is not found, returns -1.
532 */
533 int
534 250 Cleaver::findCleavageMotif(CleavageMotif &cleavage_motif,
535 std::size_t index_start,
536 std::size_t index_stop)
537 {
538 // qDebug() << "start:" << index_start << "stop:" << index_stop;
539
540 250 bool search_failed = false;
541
542 250 int first_index = 0;
543
544 250 const std::vector<MonomerSPtr> &polymer_sequence_monomers =
545 250 mcsp_polymer->getSequenceCstRef().getMonomersCstRef();
546
547 250 const std::vector<MonomerSPtr> &cleavage_motif_monomers =
548 250 cleavage_motif.getMonomersCstRef();
549
550 // We have to iterate in the polymer sequence starting at 'index', in
551 // search for a sequence element identical to the sequence that is represented
552 // in the cleavage_motif in the form of a container of MonomerCstSPtr.
553
554 // This means that if
555
556 // cleavage_motif_monomers[0]->getCode() = "Lys"
557 // cleavage_motif_monomers[1]->getCode() = "Pro"
558
559 // then, we want to search in the polymer_sequence_monomers the same
560 // sequence by iterating in this list from index 'index' onwards,
561 // and we stop searching when the list's end is found or if
562
563 // list [n] = "Lys" and
564 // list [n+1] = "Pro".
565
566
567
1/2
✓ Branch 1 taken 250 times.
✗ Branch 2 not taken.
250 if(!mcsp_polymer->size())
568 return 0;
569
570
1/2
✓ Branch 0 taken 250 times.
✗ Branch 1 not taken.
250 if(!cleavage_motif_monomers.size())
571 return -1;
572
573
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 250 times.
250 if(index_stop >= mcsp_polymer->size())
574 {
575 qFatal() << "Programming error. Index is out of bounds:" << index_stop
576 << "polymer size:" << mcsp_polymer->size();
577 }
578
579 // Seed the routine by setting 'first' to the first motif in the
580 // cleavage_motif_monomers (in our example this is "Lys").
581
582 250 QString first_code = cleavage_motif_monomers.front()->getCode();
583
584 // And now iterate (starting from 'index') in the polymer
585 // sequence's list of monomers in search for a monomer having the
586 // proper code ("Lys").
587
588 std::size_t iter_index = index_start;
589
590
591
2/2
✓ Branch 0 taken 6509 times.
✓ Branch 1 taken 26 times.
6535 while(iter_index < index_stop)
592 {
593
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6509 times.
6509 MonomerSPtr monomer_sp = polymer_sequence_monomers.at(iter_index);
594
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6509 times.
6509 if(monomer_sp == nullptr)
595 qFatalStream() << "Programming error.";
596
597
3/6
✓ Branch 1 taken 6509 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6153 times.
✓ Branch 5 taken 356 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
6509 if(monomer_sp->getCode() != first_code)
598 {
599 // The polymer sequence currently iterated code is not the one we
600 // search. So go one code further in the polymer sequence.
601
602 6153 ++iter_index;
603 6153 continue;
604 }
605
606 // If we are here, then that means that we actually found one
607 // monomer code in the polymer sequence that matches the one of
608 // the cleavage_motif we are looking for.
609
610 356 first_index = iter_index;
611 356 search_failed = false;
612
613 // Now that we have anchored our search at first_index in the
614 // polymer sequence, continue with next polymer sequence monomer and check
615 // if it matches the next monomer in the cleavage motif that we are
616 // looking for.
617
618
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 224 times.
356 for(std::size_t iter = 1; iter < cleavage_motif_monomers.size(); ++iter)
619 {
620
1/2
✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
132 if(iter_index + iter >= index_stop)
621 {
622 search_failed = true;
623 132 break;
624 }
625
626
1/2
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
132 QString next_code = cleavage_motif_monomers.at(iter)->getCode();
627
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 132 times.
132 monomer_sp = polymer_sequence_monomers.at(iter_index + iter);
628
629
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 132 times.
132 if(monomer_sp == nullptr)
630 qFatalStream() << "Programming error.";
631
632
2/4
✓ Branch 1 taken 132 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 132 times.
132 if(monomer_sp->getCode() == next_code)
633 continue;
634 else
635 {
636 132 search_failed = true;
637 132 break;
638 }
639 132 }
640 // End of
641 // for (int iter = 1; iter < cleavage_motif_monomers.size(); ++iter)
642
643 356 if(search_failed)
644 {
645 132 ++iter_index;
646 132 continue;
647 }
648 else
649 {
650
1/2
✓ Branch 0 taken 224 times.
✗ Branch 1 not taken.
224 return first_index;
651 }
652 6509 }
653 // End of
654 // while (iter_index < polymer_sequence_monomers.size())
655
656 // qDebug() << "At call with start:" << index_start << "stop:" << index_stop
657 // << "now returning -1, with cleavage motif"
658 // << cleavage_motif.getMotif();
659
660 return -1;
661 250 }
662
663 /*!
664 \brief Accounts into the \a oligomer_sp for the \a cleavage_rule_sp.
665
666 Returns true if the accounting succeeded, false otherwise.
667 */
668 bool
669 12 Cleaver::accountCleavageRule(CleavageRuleSPtr cleavage_rule_sp,
670 OligomerSPtr oligomer_sp)
671 {
672
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if(cleavage_rule_sp == nullptr || cleavage_rule_sp.get() == nullptr)
673 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
674
675
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if(oligomer_sp == nullptr || oligomer_sp.get() == nullptr)
676 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
677
678 12 IsotopicDataCstSPtr isotopic_data_csp = oligomer_sp->getPolymerCstSPtr()
679 12 ->getPolChemDefCstSPtr()
680 12 ->getIsotopicDataCstSPtr();
681
682 // For each IndexRange element in the oligomer_sp, we have to
683 // ensure we apply the formula(s) that is/are required.
684 // The oligomer_sp might have multiple ranges if it has been crafted as
685 // a cross-linked oligomer.
686
687 // We need to check the validity of the CleavageRule for each range
688 // individually because each range corresponds to an Oligomer (for example if
689 // this oligomers_p is cross-linked to at least one other oligomer). And each
690 // oligomer might have an end corresponding to the CleavageRule member data
691 // (left or right end).
692
693
4/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 taken 12 times.
24 foreach(const IndexRange *item,
694 oligomer_sp->getIndexRangeCollectionCstRef().getRangesCstRef())
695 {
696
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
12 if(!cleavage_rule_sp->getLeftCode().isEmpty())
697 {
698 // The formula has to be there because the rule has a left code that
699 // is not empty.
700 Formula rule_formula = Formula(cleavage_rule_sp->getLeftFormula());
701
702 if(rule_formula.getActionFormula().isEmpty())
703 qFatalStream()
704 << "Programming error. The cleavage rule's left code is "
705 "non-empty and the left formula thus cannot be empty.";
706
707 // We are dealing with the cleavage rule's left code/formula, so
708 // check what is the Monomer at the left end of oligomer_sp ?
709 MonomerSPtr monomer_csp = oligomer_sp->getLeftEndMonomerCstSPtr();
710
711 if(monomer_csp->getCode() == cleavage_rule_sp->getLeftCode())
712 {
713 // qDebug() << "Matched left code:" <<
714 // cleavage_rule_sp->getLeftCode();
715
716 // But, this is not going to be real true, if the
717 // monomer_csp is actually the left-end monomer_csp of the
718 // whole polymer sequence: if this oligomer_sp is actually
719 // the Left-end oligomer (like N-terminal peptide) of the polymer
720 // after having been digested, and the left end monomer of this
721 // oligomer_sp has code equal to the cleavage rule's left code
722 // (which we assessed above),
723 // then the rule has not to be applied because there was no
724 // cleavage at the left end monomer of the polymer!
725
726 // Checking if the value of sequence_range.start == 0 tells us if
727 // the
728 // oligomer_sp is the left end oligomer of the polymer. If it is
729 // == 0, then we do not apply the cleavage rule because being
730 // the left end oligomer of the polymer, its left end has not
731 // been cleaved.
732
733 if(!item->m_start)
734 {
735 // The monomer_csp is not the left-end monomer_csp, so the
736 // match is real. Account for the formula !
737
738 bool ok = false;
739 rule_formula.accountMasses(
740 ok,
741 isotopic_data_csp,
742 oligomer_sp->getMassRef(Enums::MassType::MONO),
743 oligomer_sp->getMassRef(Enums::MassType::AVG));
744
745 if(!ok)
746 return false;
747
748 oligomer_sp->getFormulaRef().accountFormula(
749 rule_formula.getActionFormula(), isotopic_data_csp, 1, ok);
750 }
751 }
752 }
753
754
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
12 if(!cleavage_rule_sp->getRightCode().isEmpty())
755 {
756 // The formula has to be there because the rule has a right code that
757 // is not empty.
758
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 Formula rule_formula = Formula(cleavage_rule_sp->getRightFormula());
759
760 // qDebug() << "Right code formula:"
761 // << rule_formula.getActionFormula();
762
763
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
12 if(rule_formula.getActionFormula().isEmpty())
764 qFatalStream()
765 << "Programming error. The cleavage rule's right code is "
766 "non-empty and the right formula thus cannot be empty.";
767
768 // We are dealing with the cleavage rule's right code/formula, so
769 // check what is the Monomer at the right end of oligomer_sp ?
770
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 MonomerSPtr monomer_csp = oligomer_sp->getRightEndMonomerCstSPtr();
771
772
4/6
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 6 times.
12 if(monomer_csp->getCode() == cleavage_rule_sp->getRightCode())
773 {
774 // qDebug() << "Matched right code:"
775 // << cleavage_rule_sp->getRightCode();
776
777 // See above for the left end code for detailed explanations.
778
779
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 if(item->m_stop != (qsizetype)mcsp_polymer->size() - 1)
780 {
781 // The monomer_csp is not the right-end monomer_csp
782 // of the whole polymer sequence, so the match is real.
783 // Account for the formula !
784
785 // qDebug()
786 // << "Before accouting rule formula, mono mass:"
787 // << oligomer_sp->getMass(Enums::MassType::MONO);
788
789 6 bool ok = false;
790
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
6 rule_formula.accountMasses(
791 ok,
792 isotopic_data_csp,
793 oligomer_sp->getMassRef(Enums::MassType::MONO),
794 oligomer_sp->getMassRef(Enums::MassType::AVG));
795
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(!ok)
797 {
798 qDebug() << "Accounting masses set ok to false.";
799 return false;
800 }
801
802 // qDebug()
803 // << "After accouting rule formula, mono mass:"
804 // << oligomer_sp->getMass(Enums::MassType::MONO);
805
806 // This will modify the formula inside oligomer_sp.
807 // However, calling elementalComposition will not account
808 // for the cleavage rule !
809
2/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
18 oligomer_sp->getFormulaRef().accountFormula(
810
2/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
12 rule_formula.getActionFormula(), isotopic_data_csp, 1, ok);
811 }
812 }
813 12 }
814 }
815
816 12 return true;
817 12 }
818
819 /*!
820 \brief Performs a cleavage operation for partial cleavage \a partial_cleavage.
821
822 Returns -1 if an error occurred, the count of generated oligomers otherwise.
823 */
824 int
825 22 Cleaver::cleavePartial(int partial_cleavage)
826 {
827 22 bool is_oligomer_the_polymer = false;
828
829 22 std::size_t iter = 0;
830
831 22 static int left_index = 0;
832 22 static int right_index = 0;
833
834 22 Q_ASSERT(partial_cleavage >= 0);
835
836
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
44 OligomerCollection partial_oligomers;
837
838 22 IsotopicDataCstSPtr isotopic_data_csp =
839
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 mcsp_polChemDef->getIsotopicDataCstSPtr();
840
841 // The cleavage might be performed on only a selected portion of the polymer
842 // sequence, not necessarily on the whole polymer sequence.
843
844 22 const IndexRange *index_range_p =
845
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 m_calcOptions.getIndexRangeCollectionCstRef()
846
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 .mostInclusiveLeftRightIndexRange();
847
848 // qDebug() << "Most inclusive left right index range:"
849 // << index_range_p->indicesAsText();
850
851 22 qsizetype index_start = index_range_p->m_start;
852 22 qsizetype index_stop = index_range_p->m_stop;
853
854 22 delete index_range_p;
855
856 22 left_index = index_start;
857 22 right_index = 0;
858
859
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 CalcOptions calc_options(m_calcOptions);
860
861 // Iterate in the container of the indices where the cleavage should occur
862 // in the polymer sequence.
863
864
2/2
✓ Branch 0 taken 418 times.
✓ Branch 1 taken 10 times.
428 for(iter = 0; iter < m_doCleaveIndices.size(); ++iter)
865 {
866 // Make sure, if the partial cleavage is very large, for
867 // example, that it will not lead us to access the polymer
868 // sequence at a monomer index larger than its upper boundary.
869
870 // Imagine cutting a polymer containing only one Met with
871 // cyanogen bromide: m_cleaveIndices will contain a single
872 // element: the index at which the methionine occurs in the
873 // polymer sequence (and there is a single one). Now, imagine
874 // that we are asked to perform a cleavage with
875 // 'partial_cleavage' of 2. The way we do it is that we fetch the
876 // index in the list of cleavage indices (m_cleaveIndices) two
877 // cleavage positions farther than the position we are iterating into:
878
879 // int offset_partial_cleavage = iter + partial_cleavage;
880
881 // Now, if m_cleaveIndices contains a single element, asking
882 // for this m_cleaveIndices.at(iter + partial_cleavage) will
883 // go out of the boundaries of the list, since it has a single
884 // item and partial_cleavage is 2. This is what we are willing to
885 // avoid.
886
887 418 std::size_t offset_partial_cleavage = iter + partial_cleavage;
888
889
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 406 times.
418 if(offset_partial_cleavage >= m_doCleaveIndices.size())
890 {
891
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12 if(iter == 0)
892 2 is_oligomer_the_polymer = true;
893
894 12 break;
895 }
896
897
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 right_index = m_doCleaveIndices.at(offset_partial_cleavage);
898
899
2/4
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 406 times.
✗ Branch 5 not taken.
812 QString name = QString("%1#%2").arg(partial_cleavage).arg(iter + 1);
900
901 // Note how we pass Ionizer() below so that it is invalid
902 // because we are not ionizing Oligomer instances right now,
903 // that will come later.
904
905
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 calc_options.setIndexRange(left_index, right_index);
906
907 // qDebug() << "After setting these values to calc_options:"
908 // << calc_options.getIndexRangeCollectionCstRef()
909 // .getRangesCstRef()
910 // .front()
911 // ->indicesAsText();
912
913 406 OligomerSPtr oligomer_sp = std::make_shared<Oligomer>(
914
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 mcsp_polymer,
915 name,
916 m_cleavageConfig.getName(),
917
2/4
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 406 times.
✗ Branch 5 not taken.
406 mcsp_polymer->modifiedMonomerCount(
918
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 IndexRangeCollection(left_index, right_index)),
919
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
812 Ionizer(),
920
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
812 calc_options);
921
922 // qDebug() << "Allocated new oligomer with index range:"
923 // << left_index << "--" << right_index << "calculation options:"
924 // << oligomer_sp->getCalcOptionsCstRef().toString()
925 // << "and default ionizer:"
926 // << oligomer_sp->getIonizerCstRef().toString();
927
928
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 oligomer_sp->setPartialCleavage(partial_cleavage);
929
930
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
406 QString elemental_composition = oligomer_sp->elementalComposition();
931
932 // qDebug() << "The elemental composition of this new oligomer as "
933 // "calculated with its own calculation options:"
934 // << calc_options.toString() << "and with its own ionizer:"
935 // << oligomer_sp->getIonizerCstRef().toString()
936 // << "is:" << elemental_composition;
937
938 // And now use that elemental composition to set it in the Oligomer.
939
940 406 bool ok = false;
941
942
2/4
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 406 times.
✗ Branch 6 not taken.
406 oligomer_sp->getFormulaRef().accountFormula(
943 elemental_composition, isotopic_data_csp, 1, ok);
944
945
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 406 times.
406 if(!ok)
946 {
947 qWarning() << "Failed to account formula:" << elemental_composition;
948 oligomer_sp.reset();
949
950 return -1;
951 }
952
953 // qDebug() << "The elemental composition above was used to set "
954 // "the oligomer's internal formula, which is now:"
955 // << oligomer_sp->getFormulaRef().getActionFormula();
956
957 // At this point we can add the configured oligomer to the list.
958
2/4
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 406 times.
✗ Branch 5 not taken.
406 partial_oligomers.getOligomersRef().push_back(oligomer_sp);
959
960 // Increment the index for next oligomer.
961
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 406 times.
406 left_index = m_doCleaveIndices.at(iter) + 1;
962
1/2
✓ Branch 1 taken 406 times.
✗ Branch 2 not taken.
812 }
963 // End of
964 // for (int iter = 0; iter < m_cleaveIndices.size(); iter=+)
965
966 // At this point we have finished iterating in the cleave index list, but
967 // there was an oligomer cooking when we ended the looping. We should handle
968 // that stray oligomer exactly the same way we did for the others inside the
969 // loop.
970
971 // Indeed, this last oligomer that was cooking is the right-end oligomer! And
972 // be sure to determine what's its real left end index !
973
974 10 if(is_oligomer_the_polymer)
975 2 left_index = index_start;
976 else
977
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 left_index = m_doCleaveIndices.at(--iter) + 1;
978
979 // 'iter' is used to construct the name of the oligomer, so we have
980 // to increment it once because we did not have the opportunity to
981 // increment it between the last but one oligomer and this one.
982
983 22 ++iter;
984
985 22 right_index = index_stop;
986
987
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 QString name = QString("%1#%2").arg(partial_cleavage).arg(iter + 1);
988
989 // Note how we pass Ionizer() below so that it is invalid
990 // because we are not ionizing Oligomer instances right now,
991 // that will come later.
992
993
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 calc_options.setIndexRange(left_index, right_index);
994
995 // qDebug() << "Now creating the last cooking cleavage Oligomer:"
996 // << name << "with indices:" << left_index << "-" << right_index
997 // << "and calculation options:" << calc_options.toString();
998
999 22 OligomerSPtr oligomer_sp =
1000
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 std::make_shared<Oligomer>(mcsp_polymer,
1001 name,
1002 m_cleavageConfig.getName(),
1003
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 mcsp_polymer->modifiedMonomerCount(
1004
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 IndexRangeCollection(left_index, right_index)),
1005
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
44 Ionizer(),
1006
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
44 calc_options);
1007
1008
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 oligomer_sp->setPartialCleavage(partial_cleavage);
1009
1010 // qDebug()
1011 // << "After heap-allocation of Oligomer, its calculation options:"
1012 // << oligomer_sp->getCalcOptionsRef().toString();
1013
1014
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 QString elemental_composition = oligomer_sp->elementalComposition();
1015
1016 // qDebug() << "Elemental composition:" << elemental_composition;
1017
1018 // And now use that elemental composition to set it in the Oligomer.
1019
1020 22 bool ok = false;
1021
1022
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
22 oligomer_sp->getFormulaRef().accountFormula(
1023 elemental_composition, isotopic_data_csp, 1, ok);
1024
1025
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if(!ok)
1026 {
1027 qWarning() << "Failed to account formula:" << elemental_composition;
1028 oligomer_sp.reset();
1029 return -1;
1030 }
1031
1032 // qDebug() << "Oligomer has formula:"
1033 // << oligomer_sp->getFormulaCstRef().getActionFormula();
1034
1035 // At this point we can add the configured oligomer to the list.
1036
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 partial_oligomers.getOligomersRef().push_back(oligomer_sp);
1037
1038 // At this point all the skeleton oligomers have been computed for
1039 // the given partial_cleavage. We still have to perform the
1040 // cross-link analysis prior to both calculate the masses and
1041 // perform the ionization of all the generated oligomers. Note that
1042 // making cross-link analysis is only useful in case the cleavage is
1043 // full (that is, partial_cleavage == 0).
1044
1045
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 12 times.
22 if(!partial_cleavage)
1046 {
1047
3/4
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 6 times.
10 if(static_cast<int>(m_calcOptions.getMonomerEntities()) &
1048 static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
1049 {
1050
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if(analyzeCrossLinks(partial_oligomers) == -1)
1051 {
1052 return -1;
1053 }
1054 }
1055 }
1056
1057 // Finally, we can now perform the mass calculations and the
1058 // ionization. We will use each oligomer in the partial_oligomers
1059 // Oligomer container as a template for creating new oligomers (with different
1060 // z values) and all the new oligomers will be appended to another Oligomer
1061 // container: oligomer_buffer_container. Each time a template Oligomer will
1062 // have been used, it will be removed from partial_oligomers. Once all the
1063 // Oligomers in partial_oligomers will have been used, and thus removed, all
1064 // the newly allocated Oligomer objects in oligomer_buffer_container will be
1065 // moved to partial_oligomers.
1066
1067
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
44 OligomerCollection oligomer_buffer_container;
1068
1069 22 std::vector<OligomerSPtr>::iterator partial_oligomers_iterator =
1070
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 partial_oligomers.getOligomersRef().begin();
1071
1072 22 std::size_t partial_oligomers_count =
1073
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 partial_oligomers.getOligomersRef().size();
1074
1075 // qDebug() << "There are" << partial_oligomers_count
1076 // << "items in the container";
1077
1078 // The end iterator needs to be dynamic because we remove the
1079 // oligomer after iterating into it.
1080
3/4
✓ Branch 1 taken 431 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 409 times.
✓ Branch 4 taken 22 times.
431 while(partial_oligomers_iterator != partial_oligomers.getOligomersRef().end())
1081 {
1082 409 OligomerSPtr iter_oligomer_sp = (*partial_oligomers_iterator);
1083
1084 // qDebug() << "Iterating partial oligomer:" <<
1085 // iter_oligomer_sp->toString()
1086 // << "While there are still"
1087 // << partial_oligomers.getOligomersRef().size()
1088 // << "items in the container";
1089
1090 // We do not ask that the oligomer be ionized yet, because we
1091 // have to first account for potential cleavage rules! Thus we
1092 // pass an invalid ionizer with Ionizer(), which is interpreted by
1093 // the mass calculation function that ionization should not be
1094 // performed. This was a bug in the release versions up
1095 // to 1.6.1.
1096 // iter_oligomer_sp->calculateMasses(calc_options, Ionizer());
1097
1/2
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
409 iter_oligomer_sp->calculateMasses();
1098
1099 // At this point we should test if the oligomer has to be
1100 // processed using cleavage rules.
1101
1102 // qDebug() << "There are:"
1103 // << m_cleavageConfig.getRulesCstRef().size() << "cleavage
1104 // rules.";
1105
1106 421 for(const CleavageRuleSPtr &cleavage_rule_sp :
1107
3/4
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 409 times.
421 m_cleavageConfig.getRulesCstRef())
1108 {
1109 // qDebug() << "The CleavageRule name:"
1110 // << cleavage_rule_sp->getName()
1111 // << "Oligomer mono mass BEFORE accounting it:"
1112 // << iter_oligomer_sp->getMass(Enums::MassType::MONO)
1113 // << "and internal formula is:"
1114 // << iter_oligomer_sp->getFormulaCstRef().getActionFormula();
1115
1116 // Note that the accounting of the cleavage rule is
1117 // performed as if the oligomer was charged 1. This is why
1118 // we have to ionize the oligomer only after we have
1119 // completed the determination of its atomic composition.
1120
1121
4/10
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
36 if(!accountCleavageRule(cleavage_rule_sp, iter_oligomer_sp))
1122 return -1;
1123
1124 // qDebug() << "The CleavageRule name:"
1125 // << cleavage_rule_sp->getName()
1126 // << "Oligomer mono mass AFTER accounting it:"
1127 // << iter_oligomer_sp->getMass(Enums::MassType::MONO)
1128 // << "and internal formula has become:"
1129 // << iter_oligomer_sp->getFormulaCstRef().getActionFormula();
1130 }
1131
1132 // qDebug() << "Done iterating in the CleavageRule instances.";
1133
1134 // At this point we can finally ionize the oligomer ! Remember
1135 // that we have to ionize the oligomer as expected in the
1136 // cleavage options. Because the ionization changes the values
1137 // in the oligomer, and we need a new oligomer each time, we
1138 // duplicate the oligomer each time we need it.
1139
1140
1/2
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
409 int ionization_level = m_cleavageConfig.getStartIonizeLevel();
1141
1/2
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
409 int ionization_stop_level = m_cleavageConfig.getStopIonizeLevel();
1142
1143 409 qDebug() << "The Ionizer:" << m_ionizer.toString();
1144
1145 // Sanity checks
1146
2/4
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 409 times.
409 if(!m_ionizer.isValid())
1147 qFatalStream() << "Programming error. The Ionizer cannot be invalid.";
1148
2/4
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 409 times.
409 if(m_ionizer.isIonized())
1149 qFatalStream()
1150 << "Programming error. The Ionizer cannot have ionized status.";
1151
1152 // qDebug() << "BEFORE ionization:"
1153 // << "Oligomer mono mass:"
1154 // << iter_oligomer_sp->getMass(Enums::MassType::MONO)
1155 // << "and internal formula is:"
1156 // << iter_oligomer_sp->getFormulaCstRef().getActionFormula();
1157
1158
2/2
✓ Branch 0 taken 1111 times.
✓ Branch 1 taken 409 times.
1520 while(ionization_level <= ionization_stop_level)
1159 {
1160
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 Ionizer temp_ionizer(m_ionizer);
1161
1162
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 temp_ionizer.setLevel(ionization_level);
1163
1164
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 OligomerSPtr new_oligomer_sp =
1165
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 std::make_shared<Oligomer>(*iter_oligomer_sp);
1166
1167
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 new_oligomer_sp->setIonizer(temp_ionizer);
1168
1169
2/4
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1111 times.
1111 if(new_oligomer_sp->ionize() == Enums::IonizationOutcome::FAILED)
1170 {
1171 qCritical() << "Failed to ionize the oligomer.";
1172
1173 new_oligomer_sp.reset();
1174
1175 return -1;
1176 }
1177
1178 1111 bool ok = false;
1179
1180
2/4
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 1111 times.
✗ Branch 6 not taken.
3333 new_oligomer_sp->getFormulaRef().accountFormula(
1181
3/8
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1111 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1111 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
2222 temp_ionizer.getFormulaCstRef().getActionFormula(),
1182 isotopic_data_csp,
1183
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 temp_ionizer.getLevel(),
1184 ok);
1185
1186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1111 times.
1111 if(!ok)
1187 {
1188 qWarning() << "Failed to account the ionizer formula:"
1189 << temp_ionizer.getFormulaCstRef().getActionFormula();
1190 new_oligomer_sp.reset();
1191
1192 return -1;
1193 }
1194
1195 // qDebug() << "AFTER ionization level:" << ionization_level
1196 // << "Oligomer mono mass:"
1197 // << iter_oligomer_sp->getMass(Enums::MassType::MONO)
1198 // << "and internal formula has become:"
1199 // << iter_oligomer_sp->getFormulaCstRef().getActionFormula();
1200
1201 // The name was set already during the creation of the
1202 // template oligomer. All we have to add to the name is the
1203 // ionization level.
1204
1205
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
2222 QString name = iter_oligomer_sp->getName() +
1206
4/10
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1111 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1111 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1111 times.
✗ Branch 11 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
3333 QString("#z=%3").arg(temp_ionizer.charge());
1207
1208
1/2
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
1111 new_oligomer_sp->setName(name);
1209
1210
2/4
✓ Branch 1 taken 1111 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1111 times.
✗ Branch 5 not taken.
1111 oligomer_buffer_container.getOligomersRef().push_back(
1211 new_oligomer_sp);
1212
1213 1111 ++ionization_level;
1214
1/2
✓ Branch 0 taken 1111 times.
✗ Branch 1 not taken.
2222 }
1215
1216 // qDebug() << "Going to erase partial oligomer:"
1217 // << (*partial_oligomers_iterator)->toString();
1218
1219 // We can now remove the template oligomer.
1220 409 partial_oligomers_iterator =
1221
2/4
✓ Branch 1 taken 409 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 409 times.
✗ Branch 5 not taken.
409 partial_oligomers.getOligomersRef().erase(partial_oligomers_iterator);
1222
1223 // No need to increment the iterator because we got new iterator from the
1224 // erase() call
1225 // above. Since we call erase() at each iteration, the iterator gets
1226 // updated at each iteration.
1227 409 }
1228
1229 // qDebug() << "At this point, we had" << partial_oligomers_count
1230 // << "partial (uncharged) oligomers and we now have"
1231 // << oligomer_buffer_container.getOligomersCstRef().size()
1232 // << "buffer (charged) oligomers";
1233
1234 // Sanity check
1235 // There should be as many times more Oligomer in the buffer container
1236 // as there were charge levels to be performed, with respect to
1237 // the uncharged oligomers in the partial container.
1238 22 std::size_t buffer_oligomers_count =
1239
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 oligomer_buffer_container.getOligomersRef().size();
1240
1241 44 if(buffer_oligomers_count !=
1242
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 (partial_oligomers_count * (m_cleavageConfig.getStopIonizeLevel() -
1243
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
22 m_cleavageConfig.getStartIonizeLevel() + 1)))
1244 qFatalStream()
1245 << "Programming error. The counts of Oligomer instances does not match.";
1246
1247 // At this point we should transfer all the
1248 // oligomers from the oligomer_buffer_container to the initial
1249 // partial_oligomers.
1250
1251 // Version involving much copying...
1252 // for(const OligomerSPtr &iter_oligomer_sp :
1253 // oligomer_buffer_container.getOligomersRef())
1254 // partial_oligomers.getOligomersRef().push_back(iter_oligomer_sp);
1255 // oligomer_buffer_container.clear();
1256
1257 // Version involving a std::move operation.
1258 // std::size_t transferred_count =
1259
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 transferOligomers(oligomer_buffer_container, partial_oligomers);
1260
1261 // qDebug() << "The number of transferred Oligomers:" << transferred_count;
1262 // qDebug() << "Count of oligomers in the source container:"
1263 // << oligomer_buffer_container.getOligomersCstRef().size();
1264
1265 // Sanity check
1266
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
22 if(oligomer_buffer_container.size())
1267 qFatalStream()
1268 << "Programming error. The container cannot contain Oligomers anymore.";
1269
1270
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 std::size_t generated_oligomers_count = partial_oligomers.size();
1271
1272 // Finally transfer all the oligomers generated for this partial
1273 // cleavage to the container of ALL the oligomers. But before making
1274 // the transfer, compute the elemental composition and store it
1275 // as a property object.
1276
1277 // Old version involving much copying...
1278 // while(partial_oligomers.size())
1279 // {
1280 // // Crucial to make this pointer cast so that we transfer actual
1281 // // Oligomers!
1282 //
1283 // OligomerSPtr iter_oligomer_sp =
1284 // std::dynamic_pointer_cast<Oligomer>(
1285 // partial_oligomers.takeFirst());
1286 //
1287 // //// Elemental formula
1288 // // QString *text = new
1289 // QString(iter_oligomer_sp->elementalComposition());
1290 // // StringProp *prop =
1291 // // new StringProp("ELEMENTAL_COMPOSITION", text);
1292 // // iter_oligomer_sp->appendProp(static_cast<Prop *>(prop));
1293 //
1294 // mp_oligomerList->append(iter_oligomer_sp);
1295 // }
1296
1297 // New version still involving much copying...
1298 // for(const OligomerSPtr &iter_oligomer_sp :
1299 // partial_oligomers.getOligomersRef())
1300 // m_oligomers.getOligomersRef().push_back(iter_oligomer_sp);
1301 // partial_oligomers.clear();
1302
1303 // Version involving a std::move operation.
1304 // size_t transferred_count =
1305
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 transferOligomers(partial_oligomers, m_oligomers);
1306
1307 // qDebug() << "The number of transferred Oligomers:" << transferred_count;
1308
1309 // Sanity check
1310
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
22 if(partial_oligomers.size())
1311 qFatalStream()
1312 << "Programming error. The container cannot contain Oligomers anymore.";
1313
1314 22 return generated_oligomers_count;
1315
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
88 }
1316
1317 /*!
1318 \brief Analyzes the CrossLink instances that might be involved in the Oligomer
1319 instances in the \a oligomers collection.
1320
1321 The Oligomers that are found cross-linked are set in \a oligomers and the size
1322 of this collection is returned.
1323 */
1324 int
1325 4 Cleaver::analyzeCrossLinks(OligomerCollection &oligomers)
1326 {
1327
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 OligomerCollection cross_linked_oligomers;
1328
1329 // General overview:
1330
1331 // At the moment this function is called only with oligomers that
1332 // were obtained with a full cleavage (no partial cleavages).
1333 // Thus, any given Monomer of the Polymer sequence is necessarily
1334 // contained only one Oligomer (see below).
1335
1336 // Iterate in the polymer's list of cross-links and for each
1337 // cross-link find the one oligomer (because no partial cleavages) that
1338 // contains the first monomer involved in the cross-link. This first found
1339 // oligomer should serve as a bait to pull-down all the oligomers cross-linked
1340 // to it.
1341
1342 4 const std::vector<CrossLinkSPtr> &polymer_cross_links =
1343
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 mcsp_polymer->getCrossLinksCstRef();
1344
1345 4 std::vector<CrossLinkSPtr>::const_iterator cross_link_iterator_cst =
1346 4 polymer_cross_links.cbegin();
1347 4 std::vector<CrossLinkSPtr>::const_iterator cross_link_end_iterator_cst =
1348 4 polymer_cross_links.cend();
1349
1350
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 4 times.
32 while(cross_link_iterator_cst != cross_link_end_iterator_cst)
1351 {
1352 // Get the first monomer that is involved in the CrossLink.
1353 28 CrossLinkCstSPtr cross_link_sp = *cross_link_iterator_cst;
1354
1355
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 MonomerCstSPtr first_cross_linked_monomer_csp =
1356
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 cross_link_sp->getFirstMonomer();
1357
1358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if(first_cross_linked_monomer_csp == nullptr)
1359 qFatalStream()
1360 << "Programming error. Cannot be that the CrossLink has no "
1361 "Monomer in its container.";
1362
1363 // In the whole set of Oligomer instances passed as argument, find the ONE
1364 // oligomer that encompasses that first Monomer involved in the CrossLink
1365 // currently iterated into. That is, the question is: "what is the
1366 // Oligomer that happens to contain that Monomer that is involved in the
1367 // CrossLink ? ".
1368
1369 28 std::size_t oligomer_index_that_encompasses_monomer = 0;
1370
1371 28 OligomerSPtr first_cross_linked_oligomer_sp =
1372 oligomers.findOligomerEncompassing(
1373 first_cross_linked_monomer_csp,
1374
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 oligomer_index_that_encompasses_monomer);
1375
1376
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 15 times.
28 if(first_cross_linked_oligomer_sp != nullptr)
1377 {
1378 // At this point we should turn this oligomer into a
1379 // cross-linked oligomer, so that we can continue performing its
1380 // cross-link analysis. To do that we allocate a list of
1381 // oligomers for this cross-linked oligomer, were we'll store
1382 // this first oligomer and then all the "pulled-down" oligomers.
1383
1384 // Set the cross-linked oligomer apart.
1385
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 cross_linked_oligomers.getOligomersRef().push_back(
1386 first_cross_linked_oligomer_sp);
1387
1388 // Remove the cross-link from the main list of oligomers so
1389 // that we do not stumble upon it in the next analysis
1390 // steps.
1391
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
26 oligomers.getOligomersRef().erase(
1392 13 oligomers.getOligomersRef().begin() +
1393
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 oligomer_index_that_encompasses_monomer);
1394
1395 // Finally, deeply scrutinize that oligomer that is used as a bait
1396 // to pull down all the Oligomers that are cross-linked to it.
1397
1/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
26 analyzeCrossLinkedOligomer(first_cross_linked_oligomer_sp, oligomers);
1398 }
1399 else
1400 {
1401 // qDebug() << __FILE__ << __LINE__
1402 // << "Cross-link at index" << iter
1403 // << "did not find any oligomer for its first monomer "
1404 // "partner";
1405 }
1406
1407
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 15 times.
28 ++cross_link_iterator_cst;
1408
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
84 }
1409
1410 // At this point we have terminated analyzing all the oligomers
1411 // for the partial cleavage. All we have to do is move all the
1412 // crossLinked oligomers from the cross_linked_oligomers to
1413 // oligomers. While doing so make sure that the m_calcOptions
1414 // datum has correct IndexRangeCollection data, as these data will be
1415 // required later, typically to calculate the elemental formula of
1416 // the oligomer.
1417
1418 // Old version involving much copying.
1419
1420 // while(cross_linked_oligomers.size())
1421 // {
1422 // OligomerSPtr oligomer_sp = cross_linked_oligomers.takeAt(0);
1423 // oligomer_sp->updateCalcOptions();
1424 // oligomers->append(oligomer_sp);
1425 // }
1426 // cross_linked_oligomers.clear();
1427 //
1428
1429 // New more C++ modern version.
1430
3/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 4 times.
17 for(OligomerSPtr &oligomer_sp : cross_linked_oligomers.getOligomersRef())
1431 {
1432
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 oligomers.getOligomersRef().push_back(std::move(oligomer_sp));
1433 }
1434
1435 // Sanity check:
1436
3/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 4 times.
17 for(OligomerSPtr &oligomer_sp : cross_linked_oligomers.getOligomersRef())
1437 {
1438
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if(oligomer_sp != nullptr)
1439 qFatalStream() << "The oligomer was not moved.";
1440 }
1441
1442
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 cross_linked_oligomers.clear();
1443
1444 // Return the number of cross-linked/non-cross-linked oligomers
1445 // alltogether.
1446
1447
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
8 return oligomers.size();
1448 4 }
1449
1450 /*!
1451 \brief Pulls down all the Oligomer instances in \a oligomers that are found to
1452 be cross-linked to \a oligomer_sp.
1453
1454 Returns the count of IndexRange instances found in \a oligomer_sp, which is a
1455 reflection of the number of oligomers that are found to be cross-linked to it.
1456 */
1457 int
1458 13 Cleaver::analyzeCrossLinkedOligomer(OligomerSPtr oligomer_sp,
1459 OligomerCollection &oligomers)
1460 {
1461 // We get a cross-linked oligomer, previously found to contain the first
1462 // Monomer involved in a Polymer CrossLink. We want to
1463 // use that oligomer_sp as a bait to pull down all the other
1464 // oligomers that are cross-linked to it.
1465
1466 // For that, we iterate in the oligomer_sp's Monomer
1467 // instances one by one and for each Monomer we ask the
1468 // mcsp_polymer to fill-in a container of CrossLink indices
1469 // in that Polymer that involve the Monomer being iterated into.
1470
1471 // For each CrossLink at the indices reported above:
1472 // 1. its shared pointer is added to the oligomer_sp container of
1473 // cross-links.
1474 // 2. for each Monomer involved in the CrossLink, the list of
1475 // oligomers that is passed as argument is asked to return
1476 // an Oligomer that encompasses that Monomer.
1477
1478 // The Oligomer returned at point 2 above (found_oligomer_sp)
1479 // (if non-nullptr) is pushed back to
1480 // a container of Oligomer instances (clearance_oligomers). Then that returned
1481 // Oligomer is removed from the initial container of Oligomers passed as
1482 // argument to this function (oligomers).
1483
1484 // Finally, the original Oligomer (oligomer_sp) has its name chanaged to
1485 // indicate the cross-link between itself the the found oligomer.
1486
1487 // At this point we have one oligomer which we know is cross-linked
1488 // at least once (with another oligomer or the cross-link is between
1489 // two or more monomers in the same oligomer, think cyan fluorescent
1490 // protein). If monomers in that same oligomer were cross-linked to
1491 // other monomers in other oligomers, then these oligomers should by
1492 // now have been moved from the original list of oligomers
1493 // (oligomers_sp) to the clearance list of oligomers
1494 // (clearance_oligomers). We have to iterate in each oligomer of that
1495 // clearance list and for each of its monomers, check if it has a
1496 // cross-link to any oligomer still in the original oligomers
1497 // (this is what I call "pull-down" stuff). Found oligomers are
1498 // appended to the clearance_oligomers.
1499
1500
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if(oligomer_sp == nullptr || oligomer_sp.get() == nullptr)
1501 qFatalStream() << "Programming error. The pointer cannot be nullptr.";
1502
1503
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
26 OligomerCollection clearance_oligomers;
1504
1505 // 'oligomer_sp' is the first oligomer in the cross-link series of
1506 // oligomers. It is the "seeding" oligomer with which to pull-down
1507 // all the others. Prepend to its name the "cl-" string to let it
1508 // know it is cross-linked.
1509
1510
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 QString name = oligomer_sp->getName();
1511
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 name.prepend("cl-");
1512
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 oligomer_sp->setName(name);
1513
1514 // Iterate in the 'oligomer_sp' and for each monomer get any
1515 // cross-linked oligomer out of the list of cross-links.
1516
1517 13 bool ok = false;
1518
1519
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 std::size_t index_start = oligomer_sp->startIndex(ok);
1520
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if(!ok)
1521 return -1;
1522
1523
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 std::size_t index_stop = oligomer_sp->stopIndex(ok);
1524
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if(!ok)
1525 return -1;
1526
1527 // qDebug() << "Oligomer start:" << index_start << "stop:" << index_stop;
1528
1529 13 const std::vector<CrossLinkSPtr> &polymer_cross_links =
1530
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 mcsp_polymer->getCrossLinksCstRef();
1531
1532
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 13 times.
163 for(std::size_t iter = index_start; iter < index_stop + 1; ++iter)
1533 {
1534 // qDebug() << "iter:" << iter;
1535
1536 150 MonomerSPtr monomer_csp =
1537
2/4
✓ Branch 1 taken 150 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 150 times.
✗ Branch 5 not taken.
150 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
1538
1539 // What crossLinks do involve this monomer ?
1540
1541 150 std::vector<std::size_t> cross_link_indices =
1542
1/2
✓ Branch 1 taken 150 times.
✗ Branch 2 not taken.
150 mcsp_polymer->crossLinkIndicesInvolvingMonomer(monomer_csp.get());
1543
1544 // At least one cross-link involves the monomer currently
1545 // iterated into inside the oligomer being analysed.
1546
1547
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 150 times.
166 for(const std::size_t &index : cross_link_indices)
1548 {
1549
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 CrossLinkSPtr cross_link_sp = polymer_cross_links.at(index);
1550
1551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if(cross_link_sp == nullptr)
1552 qFatalStream() << "Programming error.";
1553
1554 // qDebug() << __FILE__ << __LINE__
1555 // << cross_link_sp->getName();
1556
1557 // First off, we can add the cross-link to the list of
1558 // cross-links of the oligomer (we'll need them to be
1559 // able to perform mass calculations). Note that this is
1560 // only copying the pointer to the actual cross-link in
1561 // the polymer's list of cross-links. Note also that a
1562 // cross-link might not be found more than once(the
1563 // call below first checks that the cross-link is not
1564 // already in the list).
1565
1566
2/4
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 if(!oligomer_sp->addCrossLink(cross_link_sp))
1567 {
1568 // qDebug() << "The cross-link:"
1569 // << cross_link_sp->getName()
1570 // << "was already in the"
1571 // << oligomer
1572 // << "oligomer's list of cross-links: "
1573 // "not duplicated.";
1574 }
1575 else
1576 {
1577 // qDebug() << "The cross-link:"
1578 // << cross_link_sp->getName()
1579 // << "was added to the"
1580 // << oligomer
1581 // << "oligomer's list of cross-links.";
1582 }
1583
1584
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 for(const MonomerCstSPtr &monomer_csp :
1585
3/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
✓ Branch 4 taken 16 times.
48 cross_link_sp->getMonomersCstRef())
1586 {
1587 // qDebug() << monomer_csp->getName();
1588
1589 32 std::size_t found_index = 0;
1590
1591 32 OligomerSPtr found_oligomer_sp =
1592
1/4
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
32 oligomers.findOligomerEncompassing(monomer_csp, found_index);
1593
1594
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 if(found_oligomer_sp != nullptr)
1595 {
1596 // qDebug() << found_oligomer_sp->getName() <<
1597 // found_index;
1598
1599 // One oligomer in the original oligomer list
1600 // encompasses a monomer that seems to be
1601 // cross-linked to the 'monomer' being iterated
1602 // in in the currently analyzed oligomer. Move
1603 // that oligomer to the clearance list of
1604 // oligomer that will need to be further
1605 // analyzed later.
1606
1607 // Old version
1608 // oligomers.removeAt(found_index);
1609
1610
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 clearance_oligomers.getOligomersRef().push_back(
1611 found_oligomer_sp);
1612
1613
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
32 oligomers.getOligomersRef().erase(
1614
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 oligomers.getOligomersRef().begin() + found_index);
1615
1616 // Update the name of the oligomer with the name
1617 // of the new found_oligomer_sp.
1618
1619
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 QString name = QString("%1+%2")
1620
2/6
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
32 .arg(oligomer_sp->getName())
1621
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
32 .arg(found_oligomer_sp->getName());
1622
1623
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 oligomer_sp->setName(name);
1624 16 }
1625 32 }
1626 16 }
1627 // End of
1628 // for(const std::size_t &index : cross_link_indices)
1629
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
300 }
1630 // End of
1631 // for(std::size_t iter = index_start; iter < index_stop + 1; ++iter)
1632
1633 // At this point we have one oligomer which we know is cross-linked
1634 // at least once (with another oligomer or the cross-link is between
1635 // two or more monomers in the same oligomer, think cyan fluorescent
1636 // protein). If monomers in that same oligomer were cross-linked to
1637 // other monomers in other oligomers, then these oligomers should by
1638 // now have been moved from the original list of oligomers
1639 // (oligomers_sp) to the clearance list of oligomers
1640 // (clearance_oligomers). We have to iterate in each oligomer of that
1641 // clearance list and for each of its monomers, check if it has a
1642 // cross-link to any oligomer still in the original oligomers
1643 // (this is what I call "pull-down" stuff). Found oligomers are
1644 // appended to the clearance_oligomers.
1645
1646
3/4
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 13 times.
32 while(clearance_oligomers.size())
1647 {
1648 19 OligomerSPtr iter_oligomer_sp =
1649
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 clearance_oligomers.getOligomersRef().front();
1650
1651 19 bool ok = false;
1652
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 std::size_t index_start = iter_oligomer_sp->startIndex(ok);
1653
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if(!ok)
1654 return -1;
1655
1656
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 std::size_t index_stop = iter_oligomer_sp->stopIndex(ok);
1657
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if(!ok)
1658 return -1;
1659
1660
2/2
✓ Branch 0 taken 261 times.
✓ Branch 1 taken 19 times.
280 for(std::size_t iter = index_start; iter <= index_stop; ++iter)
1661 {
1662 261 MonomerSPtr monomer_csp =
1663
2/4
✓ Branch 1 taken 261 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 261 times.
✗ Branch 5 not taken.
261 mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);
1664
1665 // qDebug() << __FILE__ << __LINE__
1666 // << monomer_sp->getName();
1667
1668 // What crossLinks do involve this monomer ?
1669
1670 261 std::vector<std::size_t> cross_link_indices =
1671
1/2
✓ Branch 1 taken 261 times.
✗ Branch 2 not taken.
261 mcsp_polymer->crossLinkIndicesInvolvingMonomer(monomer_csp.get());
1672
1673
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 233 times.
261 if(cross_link_indices.size())
1674 {
1675 // At least one cross-link involves the monomer currently
1676 // iterated in the iter_oligomer_sp being analysed.
1677
1678
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 28 times.
56 for(const std::size_t &index : cross_link_indices)
1679 {
1680
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 CrossLinkSPtr cross_link_sp = polymer_cross_links.at(index);
1681
1682
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if(cross_link_sp == nullptr)
1683 qFatalStream() << "Programming error.";
1684
1685 // qDebug() << __FILE__ << __LINE__
1686 // << cross_link_sp->getName();
1687
1688
1689 // First off, we can add the cross-link to the list of
1690 // cross-links of the oligomer(we'll need them to be
1691 // able to perform mass calculations). Note that this is
1692 // only copying the pointer to the actual cross-link in
1693 // the polymer's list of cross-links. Note also that a
1694 // cross-link might not be found more than once(the
1695 // call below first checks that the cross-link is not
1696 // already in the list).
1697
1698
2/4
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 if(!oligomer_sp->addCrossLink(cross_link_sp))
1699 {
1700 // qDebug() << __FILE__ << __LINE__
1701 // << "The cross-link:"
1702 // << cross_link_sp->getName()
1703 // << "was already in the"
1704 // << oligomer
1705 // << "oligomer's list of cross-links: "
1706 // "not duplicated.";
1707 }
1708 else
1709 {
1710 // qDebug() << __FILE__ << __LINE__
1711 // << "The cross-link:"
1712 // << cross_link_sp->getName()
1713 // << "was added to the"
1714 // << oligomer
1715 // << "oligomer's list of cross-links.";
1716 }
1717
1718
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 for(const MonomerCstSPtr &monomer_csp :
1719
3/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 56 times.
✓ Branch 4 taken 28 times.
84 cross_link_sp->getMonomersCstRef())
1720 {
1721 // qDebug() << monomer_csp->getName();
1722
1723 56 std::size_t found_index = 0;
1724
1725 56 OligomerSPtr found_oligomer_sp =
1726 oligomers.findOligomerEncompassing(monomer_csp,
1727
1/2
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
56 found_index);
1728
1729
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 53 times.
56 if(found_oligomer_sp)
1730 {
1731 // qDebug() << __FILE__ << __LINE__
1732 // << foundOligomer->name() << foundIndex;
1733
1734 // One oligomer in the original oligomer list
1735 // encompasses a monomer that seems to be
1736 // cross-linked to the 'monomer' being iterated
1737 // in in the currently analyzed oligomer. Move
1738 // that oligomer to the clearance list of
1739 // oligomer that will need to be further
1740 // analyzed later.
1741
1742 // Old version
1743 // oligomers.removeAt(foundIndex);
1744
1745
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 clearance_oligomers.getOligomersRef().push_back(
1746 found_oligomer_sp);
1747
1748
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 oligomers.getOligomersRef().erase(
1749
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 oligomers.getOligomersRef().begin() + found_index);
1750
1751 // Update the name of the oligomer with the name
1752 // of the new found_oligomer_sp.
1753
1754
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 QString name = QString("%1+%2")
1755
2/6
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
6 .arg(oligomer_sp->getName())
1756
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
6 .arg(found_oligomer_sp->getName());
1757
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 oligomer_sp->setName(name);
1758 3 }
1759 56 }
1760 28 }
1761 // End of
1762 // foreach(index, cross_link_indices)
1763 }
1764 // End of(ret) ie cross-links involved monomer
1765
1/2
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
522 }
1766 // End of
1767 // for (int iter = iter_oligomer_sp->index_start();
1768 // iter < iter_oligomer_sp->index_stop() + 1; ++iter)
1769
1770 // At this point this quarantinized oligomer might be removed
1771 // from the clearance_oligomers and its sequence_range
1772 // be appended to the 'oligomer' list of sequence_range. Then, the
1773 // quanrantinized oligomer might be destroyed.
1774
1775
3/6
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✗ Branch 8 not taken.
19 oligomer_sp->getIndexRangeCollectionRef().appendIndexRanges(
1776
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 iter_oligomer_sp->getIndexRangeCollectionRef());
1777
1778 // That oligomer was gotten with front(), so it is the
1779 // first Oligomer in the vector. The corresponding iterator
1780 // is thus begin().
1781
2/4
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
38 clearance_oligomers.getOligomersRef().erase(
1782
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 clearance_oligomers.getOligomersRef().begin());
1783 19 }
1784
1785 // At this point, all the oligomers in the clearance oligomer list
1786 // have all been dealt with, return the number of cross-linked
1787 // oligomers in this oligomer.
1788
1789
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 return oligomer_sp->getIndexRangeCollectionRef().size();
1790 13 }
1791
1792 //////////////// OPERATORS /////////////////////
1793
1794 /*!
1795 \brief Returns a reference to this Cleaver instance after initialization using \a other.
1796 */
1797 Cleaver &
1798 1 Cleaver::operator=(const Cleaver &other)
1799 {
1800
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(this == &other)
1801 return *this;
1802
1803 1 mcsp_polymer = other.mcsp_polymer;
1804 1 mcsp_polChemDef = other.mcsp_polChemDef;
1805
1806 1 m_cleavageConfig.initialize(other.m_cleavageConfig);
1807 1 m_calcOptions.initialize(other.m_calcOptions);
1808 1 m_ionizer = other.m_ionizer;
1809
1810 1 m_doCleaveIndices.assign(other.m_doCleaveIndices.begin(),
1811 other.m_doCleaveIndices.end());
1812 1 m_doNotCleaveIndices.assign(other.m_doNotCleaveIndices.begin(),
1813 other.m_doNotCleaveIndices.end());
1814
1815 1 m_oligomers = other.m_oligomers;
1816
1817 1 return *this;
1818 }
1819
1820 /*!
1821 \brief Returns true if this instance is identical to \a other, false otherwise.
1822 */
1823 bool
1824 1 Cleaver::operator==(const Cleaver &other) const
1825 {
1826
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(this == &other)
1827 return true;
1828
1829
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if(mcsp_polymer != other.mcsp_polymer ||
1830
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 mcsp_polChemDef != other.mcsp_polChemDef ||
1831
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 m_cleavageConfig != other.m_cleavageConfig ||
1832
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
4 m_calcOptions != other.m_calcOptions || m_ionizer != other.m_ionizer ||
1833 1 m_oligomers != other.m_oligomers)
1834 return false;
1835
1836 return true;
1837 }
1838
1839 /*!
1840 \brief Returns true if this instance is different than \a other, false
1841 otherwise.
1842 */
1843 bool
1844 Cleaver::operator!=(const Cleaver &other) const
1845 {
1846 return !operator==(other);
1847 }
1848
1849 } // namespace libXpertMassCore
1850 } // namespace MsXpS
1851