1 /* massXpert - the true massist's program.
2    --------------------------------------
3    Copyright(C) 2006,2007 Filippo Rusconi
4 
5    http://www.massxpert.org/massXpert
6 
7    This file is part of the massXpert project.
8 
9    The massxpert project is the successor to the "GNU polyxmass"
10    project that is an official GNU project package(see
11    www.gnu.org). The massXpert project is not endorsed by the GNU
12    project, although it is released ---in its entirety--- under the
13    GNU General Public License. A huge part of the code in massXpert
14    is actually a C++ rewrite of code in GNU polyxmass. As such
15    massXpert was started at the Centre National de la Recherche
16    Scientifique(FRANCE), that granted me the formal authorization to
17    publish it under this Free Software License.
18 
19    This software is free software; you can redistribute it and/or
20    modify it under the terms of the GNU  General Public
21    License version 3, as published by the Free Software Foundation.
22 
23 
24    This software is distributed in the hope that it will be useful,
25    but WITHOUT ANY WARRANTY; without even the implied warranty of
26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
27    General Public License for more details.
28 
29    You should have received a copy of the GNU General Public License
30    along with this software; if not, write to the
31 
32    Free Software Foundation, Inc.,
33 
34    51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
35 */
36 
37 
38 /////////////////////// Qt includes
39 #include <QDebug>
40 
41 
42 /////////////////////// Local includes
43 #include "fragmenter.hpp"
44 
45 
46 namespace massXpert
47 {
48 
Fragmenter(Polymer * polymer,const PolChemDef * polChemDef,const QList<FragOptions * > & fragOptionList,const CalcOptions & calcOptions,const IonizeRule & ionizeRule)49   Fragmenter::Fragmenter(Polymer *polymer,
50 			  const PolChemDef *polChemDef,
51 			  const QList<FragOptions *> &fragOptionList,
52 			  const CalcOptions &calcOptions,
53 			  const IonizeRule &ionizeRule)
54     : mp_polymer(polymer),
55       mp_polChemDef(polChemDef),
56       m_calcOptions(calcOptions),
57       m_ionizeRule(ionizeRule)
58   {
59     Q_ASSERT(mp_polymer && mp_polChemDef);
60 
61     for (int iter = 0; iter < fragOptionList.size(); ++iter)
62       {
63 	FragOptions *fragOptions = fragOptionList.at(iter);
64 	FragOptions *newFragOptions = new FragOptions(*fragOptions);
65 
66 	m_fragOptionList.append(newFragOptions);
67       }
68 
69     mp_oligomerList = 0;
70   }
71 
72 
Fragmenter(const Fragmenter & other)73   Fragmenter::Fragmenter(const Fragmenter &other)
74     : mp_polymer(other.mp_polymer),
75       mp_polChemDef(other.mp_polChemDef),
76       m_calcOptions(other.m_calcOptions),
77       m_ionizeRule(other.m_ionizeRule)
78   {
79     Q_ASSERT(mp_polymer && mp_polChemDef);
80 
81     for (int iter = 0; iter < other.m_fragOptionList.size(); ++iter)
82       {
83 	FragOptions *fragOptions = other.m_fragOptionList.at(iter);
84 	FragOptions *newFragOptions = new FragOptions(*fragOptions);
85 
86 	m_fragOptionList.append(newFragOptions);
87       }
88 
89     mp_oligomerList = other.mp_oligomerList;
90   }
91 
92 
~Fragmenter()93   Fragmenter::~Fragmenter()
94   {
95     // We are not owner of the oligomer list, do not free it!
96 
97     qDeleteAll(m_fragOptionList.begin(), m_fragOptionList.end());
98     m_fragOptionList.clear();
99   }
100 
101 
102   // Takes ownership of the parameter.
103   void
addFragOptions(FragOptions * fragOptions)104   Fragmenter::addFragOptions(FragOptions *fragOptions)
105   {
106     Q_ASSERT(fragOptions);
107 
108     m_fragOptionList.append(fragOptions);
109   }
110 
111 
112   void
setOligomerList(OligomerList * oligomerList)113   Fragmenter::setOligomerList(OligomerList *oligomerList)
114   {
115     Q_ASSERT(oligomerList);
116 
117     mp_oligomerList = oligomerList;
118   }
119 
120 
121   OligomerList *
oligomerList()122   Fragmenter::oligomerList()
123   {
124     return mp_oligomerList;
125   }
126 
127 
128   bool
fragment()129   Fragmenter::fragment()
130   {
131     // If the polymer sequence is empty, just return.
132     if (!mp_polymer->size())
133       return true;
134 
135     // Ensure that the list of fragmentation options is not empty.
136 
137     if (!m_fragOptionList.size())
138       {
139 	qDebug() << __FILE__ << __LINE__
140 		  << "List of fragmentation options is empty !";
141 
142 	return false;
143       }
144 
145     //   qDebug() << __FILE__ << __LINE__
146     //  	    << "number of fragmentation specifications:"
147     //  	    << m_fragOptionList.size();
148 
149     // Before starting the calculation we ought to know if there are
150     // cross-links in the oligomer to be fragmented and if the user
151     // has asked that these cross-linked be taken into account during
152     // the fragmentation.
153 
154     if (m_calcOptions.monomerEntities() & MXT_MONOMER_CHEMENT_CROSS_LINK)
155       {
156         // qDebug() << __FILE__ << __LINE__
157         //          << "Fragmentation calculations take "
158         //   "into account the cross-links";
159 
160         // Let's put one of the fragmentation options (the first) so that
161         // we can get the indices of the oligomer to fragment.
162 
163         FragOptions *fragOptions = m_fragOptionList.at(0);
164 
165         // If there are cross-links, then we have to deal with
166         // them. The strategy is to first get a list of all the
167         // monomer indices for the monomers in the oligomer (being
168         // fragmented) that are involved in cross-links.
169 
170         QList<int> crossLinkedMonomerIndexList;
171         int partials = 0;
172 
173         // qDebug() << __FILE__ << __LINE__
174         //          << "Fragmentation options:"
175         //          << fragOptions->startIndex() << fragOptions->endIndex();
176 
177         mp_polymer->crossLinkedMonomerIndexList(fragOptions->startIndex(),
178                                                 fragOptions->endIndex(),
179                                                 &crossLinkedMonomerIndexList,
180                                                 &partials);
181 
182         if(partials)
183           qDebug() << __FILE__ << __LINE__
184                    << "Fragmentation calculations do not\n"
185             "take into account partial cross-links.\n"
186             "These partial cross-links are ignored.";
187 
188         // Now that we have a list of the indices of all the monomers
189         // that are cross-linked, we can iterate in it and create a
190         // list of CrossLinkedRegion instances that will allow us to
191         // "segment" the to-fragment oligomer so as to ease the
192         // calculation of product ion masses.
193 
194         // Sort the indices.
195         qSort(crossLinkedMonomerIndexList.begin(),
196               crossLinkedMonomerIndexList.end());
197 
198         // qDebug() << __FILE__ << __LINE__
199         //          << "Indices:" << crossLinkedMonomerIndexList;
200 
201 
202         // Now find continuous regions and create a new region each
203         // time we find one.
204 
205         int first = 0;
206         int last = 0;
207 
208         int prev = 0;
209         int next = 0;
210 
211         for(int iter = 0, size = crossLinkedMonomerIndexList.size() - 1;
212             iter < size; ++iter)
213           {
214             // Seed the system only at the first iteration.
215             if(!iter)
216               {
217                 first = crossLinkedMonomerIndexList.at(iter);
218                 last = crossLinkedMonomerIndexList.at(iter + 1);
219 
220                 // qDebug() << __FILE__ << __LINE__
221                 //          << "Seeding with first and last:"
222                 //          << first << "--" << last;
223               }
224 
225             prev = crossLinkedMonomerIndexList.at(iter);
226             next = crossLinkedMonomerIndexList.at(iter + 1);
227 
228             if(next - prev == 1)
229               {
230                 // We are going on with a continuum. Fine.
231                 last = next;
232 
233                 // qDebug() << __FILE__ << __LINE__
234                 //          << "Elongating continuum:"
235                 //          << "[" << first << "-" << last << "]";
236 
237                 continue;
238               }
239             else
240               {
241                 // There is a gap. Close the previous continuum and
242                 // start another one.
243 
244                 last = prev;
245 
246                 // qDebug() << __FILE__ << __LINE__
247                 //          << "Closing continuum:"
248                 //          << "[" << first << "-" << last << "]";
249 
250                 CrossLinkedRegion *region = new CrossLinkedRegion(first, last);
251 
252                 // Get the cross-links for the region.
253                 QList<CrossLink *> crossLinkList;
254 
255                 partials = 0;
256 
257                 mp_polymer->crossLinkList(first, last,
258                                           &crossLinkList, &partials);
259 
260                 if(partials)
261                   qDebug() << __FILE__ << __LINE__
262                            << "Fragmentation calculations do not\n"
263                     "take into account partial cross-links.\n"
264                     "These partial cross-links are ignored.";
265 
266                 // Append the obtained cross-links to the region so
267                 // that we finalize its construction. Finally append
268                 // the new region to the list.
269 
270                 region->appendCrossLinks(crossLinkList);
271 
272                 m_crossLinkedRegionList.append(region);
273 
274                 // Now that we have closed a continuum, start seeding
275                 // a new one.
276                 first = next;
277               }
278           }
279         // End of
280         // for(int iter = 0, size = crossLinkedMonomerIndexList.size() - 1;
281         //    iter < size; ++iter)
282 
283         // We have to close the last continuum that we could not close
284         // because we ended off the for loop.
285 
286         last = next;
287 
288         // qDebug() << __FILE__ << __LINE__
289         //          << "Closing continuum:"
290         //          << "[" << first << "-" << last << "]";
291 
292         CrossLinkedRegion *region = new CrossLinkedRegion(first, last);
293 
294         // Get the cross-links for the region.
295         QList<CrossLink *> crossLinkList;
296 
297         partials = 0;
298 
299         mp_polymer->crossLinkList(first, last,
300                                   &crossLinkList, &partials);
301 
302         if(partials)
303           qDebug() << __FILE__ << __LINE__
304                    << "Fragmentation calculations do not\n"
305             "take into account partial cross-links.\n"
306             "These partial cross-links are ignored.";
307 
308         // Append the obtained cross-links to the region so
309         // that we finalize its construction. Finally append
310         // the new region to the list.
311 
312         region->appendCrossLinks(crossLinkList);
313 
314         m_crossLinkedRegionList.append(region);
315 
316         // qDebug() << __FILE__ << __LINE__
317         //          << "Having" << m_crossLinkedRegionList.size() << "regions";
318 
319         // for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
320         //   qDebug() << __FILE__ << __LINE__
321         //            << "Start:" << m_crossLinkedRegionList.at(iter)->startIndex()
322         //            << "End:" << m_crossLinkedRegionList.at(iter)->endIndex();
323       }
324     // End of
325     // if (m_calcOptions.monomerEntities() & MXT_MONOMER_CHEMENT_CROSS_LINK)
326 
327     // At this point we have a list of regions that we'll be able to
328     // use to compute the fragment masses.
329 
330     // For each fragmentation options instance in the list, perform the
331     // required fragmentation.
332 
333     for (int iter = 0; iter < m_fragOptionList.size(); ++iter)
334       {
335 	FragOptions *fragOptions = m_fragOptionList.at(iter);
336 
337 	if(fragOptions->fragEnd() == MXT_FRAG_END_NONE)
338 	  {
339 	    if (fragmentEndNone(*fragOptions) == -1)
340 	      return false;
341 	  }
342 	else if (fragOptions->fragEnd() == MXT_FRAG_END_LEFT)
343 	  {
344 	    if (fragmentEndLeft(*fragOptions) == -1)
345 	      return false;
346 	  }
347 	else if (fragOptions->fragEnd() == MXT_FRAG_END_RIGHT)
348 	  {
349 	    if (fragmentEndRight(*fragOptions) == -1)
350 	      return false;
351 	  }
352 	else
353 	  Q_ASSERT(0);
354       }
355 
356     return true;
357   }
358 
359 
360   int
fragmentEndNone(FragOptions & fragOptions)361   Fragmenter::fragmentEndNone(FragOptions &fragOptions)
362   {
363     // We are generating fragments that are made of a single monomer,
364     // like in the proteinaceous world we have the immonium ions.
365 
366     int count = 0;
367 
368     CalcOptions localOptions = m_calcOptions;
369 
370     for (int iter = fragOptions.startIndex();
371 	 iter < fragOptions.endIndex() + 1; ++iter)
372       {
373         bool fragRuleApplied = false;
374 
375 	// We create an oligomer which is not ionized(false) but that
376 	// bears the default ionization rule, because this oligomer
377 	// might be later used in places where the ionization rule has
378 	// to be valid. For example, one drag and drop operation might
379 	// copy this oligomer into a mzLab dialog window where its
380 	// ionization rule validity might be challenged. Because this
381 	// fragmentation oligomer will be a neutral species, we should
382 	// set the level member of the ionization to 0.
383 
384 	IonizeRule ionizeRule(m_ionizeRule);
385 	ionizeRule.setLevel(0);
386 
387 	Oligomer *oligomer1 =
388 	  new Oligomer(mp_polymer,
389                        "NOT_SET",
390                        fragOptions.name() /*fragSpec.m_name*/,
391                        Ponderable(),
392                        ionizeRule,
393                        m_calcOptions,
394                        false /*isIonized*/,
395                        iter /*startIndex*/, iter /*endIndex*/);
396 
397         // qDebug() << __FILE__ << __LINE__
398         //          << "right after creation with ionizerule but no charge:"
399         //          << oligomer1->mono();
400 
401 	localOptions.setCapping(MXT_CAP_NONE);
402 
403         // Since version 3.6.0, the formula of the fragmentation
404         // specification should yield a neutral molecular species,
405         // which is then ionized according to the current ionization
406         // rule in the editor window. The levels of this ionization
407         // rule are set by the user in the fragmentation dialog
408         // window, which default to a single level of ionization.
409 
410         // The first step is to calculate the masses of the fragment
411         // oligomer without taking into account the ionization,
412         // because we still have other things to account for that
413         // might interfere with the mass of the fragment. So we pass
414         // an invalid ionizeRule object(upon creation, an ionize rule
415         // is invalid).
416 	IonizeRule rule;
417 
418 	if(!oligomer1->calculateMasses(&localOptions, &rule))
419 	  {
420 	    delete oligomer1;
421 
422 	    return -1;
423 	  }
424 
425         // qDebug() << __FILE__ << __LINE__
426         //          << "right after calculateMasses with invalid ionizerule:"
427         //          << oligomer1->mono();
428 
429 	const QList<Atom *> &refList = mp_polChemDef->atomList();
430 
431 	if(!fragOptions.formula().isEmpty())
432 	  if (!fragOptions.Formula::accountMasses(refList, oligomer1))
433 	    {
434 	      delete oligomer1;
435 
436 	      return -1;
437 	    }
438 
439         // qDebug() << __FILE__ << __LINE__
440         //          << "right after accountMasses for fragSpec formula:"
441         //          << oligomer1->mono();
442 
443 	// At this moment, the new fragment might be challenged for
444 	// the fragmented monomer's contribution. For example, in
445 	// nucleic acids, it happens that during a fragmentation, the
446 	// base of the fragmented monomer is decomposed and goes
447 	// away. This is implemented in massXpert with the ability to
448 	// tell the fragmenter that upon fragmentation the mass of the
449 	// monomer is to be removed. The skeleton mass is then added
450 	// to the formula of the fragmentation pattern (FragSpec).
451 
452 	int monomerContrib = fragOptions.monomerContribution();
453 
454 	if(monomerContrib)
455 	  {
456 	    const Monomer *monomer = mp_polymer->at(iter);
457 
458 	    QString formula = monomer->formula();
459 
460 	    if (!monomer->accountMasses(&oligomer1->rmono(),
461 					 &oligomer1->ravg(),
462 					 monomerContrib))
463 	      {
464 		delete oligomer1;
465 
466 		return -1;
467 	      }
468 	  }
469 
470         // qDebug() << __FILE__ << __LINE__
471         //          << "right after accountMasses for monomer contribution:"
472         //          << oligomer1->mono();
473 
474 	// At this point we should check if the fragmentation
475 	// specification includes fragmentation rules that apply to this
476 	// fragment. FragOptions is derived from FragSpec.
477 
478 	for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
479 	  {
480 	    // The accounting of the fragrule is performed on a
481 	    // neutral oligomer, as defined by the fragmentation
482 	    // formula. Later, we'll have to take into account the
483 	    // fact that the user might want to calculate fragment m/z
484 	    // with z>1.
485 
486 	    FragRule *fragRule = fragOptions.ruleList().at(jter);
487 
488 	    if (!accountFragRule(fragRule, true, iter, MXT_FRAG_END_NONE, 0))
489 	      continue;
490 
491 	    // Each fragrule triggers the creation of a new oligomer.
492 
493 	    Oligomer *oligomer2 =
494 	      new Oligomer(*oligomer1);
495 
496 	    if (!accountFragRule(fragRule, false, iter,
497 				  MXT_FRAG_END_NONE, oligomer2))
498 	      {
499 		delete oligomer1;
500 		delete oligomer2;
501 
502 		return -1;
503 	      }
504 
505             // qDebug() << __FILE__ << __LINE__
506             //          << "right after accountFragRule:"
507             //          << oligomer1->mono();
508 
509 	    // At this point we have the fragment oligomer within a
510 	    // neutral state, because starting with version 3.6.0, the
511 	    // fragmentation specification should yield a neutral
512 	    // molecular species.
513 
514             Oligomer *newOligomer =
515 	      new Oligomer(*oligomer2);
516 
517             int charge = 0;
518 
519             // We can immediately set the name of template oligomer on which
520             // to base the creation of the derivative formula-based
521             // oligomers.
522             QString name = QString("%1#%2#(%3)")
523               .arg(fragOptions.name())
524               .arg(mp_polymer->at(iter)->code())
525               .arg(fragRule->name());
526 
527             // Set the name of this template oligomer, but with the
528             // charge in the form of "#z=1".
529             QString nameWithCharge = name;
530             nameWithCharge.append(QString("#z=%1").arg(charge));
531 
532             newOligomer->setName(nameWithCharge);
533 
534             // We should make a temporary list of oligomers to handle
535             // both formulas and charge state.
536 
537             OligomerList *formulaOligomerList =
538               new OligomerList(fragOptions.name(), mp_polymer);
539 
540             // Append the template oligomer to the temporary list so
541             // that the called function has it to base new oligomers
542             // on it.
543             formulaOligomerList->append(newOligomer);
544 
545             // Let the following steps know that we actually succeeded
546             // in preparing an oligonucleotide with a fragmentation
547             // rule applied.
548 
549             fragRuleApplied = true;
550 
551             // Now that the list has ONE template item, we can use
552             // that list to stuff in it all the other ones depending
553             // on the presence of any formula in fragOptions. Indeed,
554             // it might be that the user has checked the -H20 or -NH3
555             // checkboxes, asking that such formulas be accounted for
556             // in the generation of the fragment oligomers. Account
557             // for these potential formulas...
558 
559             //            int accountedFormulas =
560             accountFormulas(formulaOligomerList, fragOptions, name, charge);
561 
562             // We now have a list of oligomers (or only one if there
563             // was no formula to take into account). For each
564             // oligomer, we have to account for the charge levels
565             // asked by the user.
566 
567             OligomerList *ionizeLevelOligomerList =
568               accountIonizationLevels(formulaOligomerList, fragOptions);
569 
570               // First off, we can finally delete the grand template oligomer.
571               delete oligomer2;
572 
573               if(!ionizeLevelOligomerList)
574                 {
575                   qDebug() << __FILE__ << __LINE__
576                            << QObject::tr("massxpert - Fragmentation : "
577                                           "Failed to generate ionized "
578                                           "fragment oligomers.");
579 
580                   while(!formulaOligomerList->isEmpty())
581                     delete formulaOligomerList->takeFirst();
582 
583                   delete formulaOligomerList;
584 
585                   return -1;
586                 }
587 
588               // Note that during the work on ionizeLevels, the list
589               // of oligomers that was passed to that function as a
590               // parameter has gotten emptied. It is thus now time to
591               // delete it.
592               Q_ASSERT(formulaOligomerList->isEmpty());
593               delete formulaOligomerList;
594 
595               // At this point, we have to remove all the oligomers
596               // from the lastOligomerList and put them into the
597               // oligomerList.
598 
599               while(!ionizeLevelOligomerList->isEmpty())
600                 {
601                   mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
602                   ++count;
603                 }
604 
605               delete ionizeLevelOligomerList;
606           }
607 	// End of
608 	// for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
609 
610 	// We are here because of two reasons:
611 
612 	// 1. because the array of fragRules did not contain any
613 	// 1. fragRule, in which case we still have to validate and
614 	// 1. terminate the oligomer1 (fragRuleApplied is false);
615 
616 	// 2. because we finished dealing with fragRules, in which case
617 	// 2. we ONLY add oligomer1 to the list of fragments if none
618 	// 2. of the fragrules analyzed above gave a successfully
619 	// 2. generated fragment(fragRuleApplied is false).
620 
621 	if(!fragRuleApplied)
622 	  {
623 	    // At this point we have the fragment oligomer. However, do
624 	    // not forget that the user might ask for fragments that
625 	    // bear more than the single charge that was intrinsically
626 	    // computed within the formula of the fragmentation
627 	    // specification.
628 
629 	    // So, first create an oligomer with the "default"
630 	    // fragmentation specification-driven neutral state (that
631 	    // is, charge = 0).
632 
633 	    Oligomer *newOligomer =
634 	      new Oligomer(*oligomer1);
635 
636             int charge = 0;
637 
638             // We can immediately set the name of template oligomer on which
639             // to base the creation of the derivative formula-based
640             // oligomers.
641             QString name = QString("%1#%2")
642               .arg(fragOptions.name())
643               .arg(mp_polymer->at(iter)->code());
644 
645             // Set the name of this template oligomer, but with the
646             // charge in the form of "#z=1".
647             QString nameWithCharge = name;
648             nameWithCharge.append(QString("#z=%1").arg(charge));
649 
650             newOligomer->setName(nameWithCharge);
651 
652             // We should make a temporary list of oligomers to handle
653             // both formulas and charge state.
654 
655             OligomerList *formulaOligomerList =
656               new OligomerList(fragOptions.name(), mp_polymer);
657 
658             // Append the template oligomer to the temporary list so
659             // that the called function has it to base new oligomers
660             // on it.
661             formulaOligomerList->append(newOligomer);
662 
663             // Now that the list has ONE template item, we can use
664             // that list to stuff in it all the other ones depending
665             // on the presence of any formula in fragOptions. Indeed,
666             // it might be that the user has checked the -H20 or -NH3
667             // checkboxes, asking that such formulas be accounted for
668             // in the generation of the fragment oligomers. Account
669             // for these potential formulas...
670 
671             //            int accountedFormulas =
672             accountFormulas(formulaOligomerList, fragOptions, name, charge);
673 
674             // We now have a list of oligomers (or only one if there
675             // was no formula to take into account). For each
676             // oligomer, we have to account for the charge levels
677             // asked by the user.
678 
679             OligomerList *ionizeLevelOligomerList =
680               accountIonizationLevels(formulaOligomerList, fragOptions);
681 
682               // First off, we can finally delete the grand template
683               // oligomer (oligomer with no frag rules applied).
684               delete oligomer1;
685 
686               if(!ionizeLevelOligomerList)
687                 {
688                   qDebug() << __FILE__ << __LINE__
689                            << QObject::tr("massxpert - Fragmentation : "
690                                           "Failed to generate ionized "
691                                           "fragment oligomers.");
692 
693                   while(!formulaOligomerList->isEmpty())
694                     delete formulaOligomerList->takeFirst();
695 
696                   delete formulaOligomerList;
697 
698                   return -1;
699                 }
700 
701               // At this point, we have to remove all the oligomers
702               // from the lastOligomerList and put them into the
703               // oligomerList.
704 
705               while(!ionizeLevelOligomerList->isEmpty())
706                 {
707                   Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();
708 
709                   mp_oligomerList->append(iterOligomer);
710                   ++count;
711                 }
712 
713               delete ionizeLevelOligomerList;
714           }
715         // End of
716         // if(!fragRuleApplied)
717         else // (fragRuleApplied == true)
718           {
719             // There were fragmentation rule(s) that could be
720             // successfully applied. Thus we already have created the
721             // appropriate oligomers. Simply delete the template
722             // oligomer.
723                 delete oligomer1;
724           }
725       }
726     // End of
727     //   for (int iter = fragOptions.startIndex();
728     //   fragOptions.endIndex() + 1; ++iter)
729 
730     return count;
731   }
732 
733 
734   int
fragmentEndLeft(FragOptions & fragOptions)735   Fragmenter::fragmentEndLeft(FragOptions &fragOptions)
736   {
737     int count = 0;
738     int number = 0;
739 
740     static Ponderable ponderable;
741     ponderable.clearMasses();
742 
743     const QList<Atom *> &refList = mp_polChemDef->atomList();
744 
745     // If the crosslinks are to be taken into account, then make a
746     // local copy of the m_crossLinkedRegionList because we are going
747     // to remove items from it during the calculation of the fragments
748     // and we do not want to modify the contents of the original list
749     // (remember that this fragmenter might be created to perform more
750     // than one single fragmentation but a set of fragmentations).
751 
752     QList<CrossLinkedRegion *> crossLinkedRegionList;
753 
754     for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
755       {
756         CrossLinkedRegion *region =
757           new CrossLinkedRegion(*m_crossLinkedRegionList.at(iter));
758 
759         crossLinkedRegionList.append(region);
760       }
761 
762     // At this point we can start making the calculations of the
763     // fragments. Because we are generating fragments that contain the
764     // left part of the oligomer, we iterate in the
765     // fragOptions.startIndex() --> fragOptions.endIndex() direction.
766 
767     for (int iter = fragOptions.startIndex();
768 	 iter < fragOptions.endIndex(); ++iter, ++number)
769       {
770         bool fragRuleApplied = false;
771 
772 	const Monomer *monomer = mp_polymer->at(iter);
773 
774 	monomer->accountMasses(&ponderable, 1);
775 
776 
777         // If we are to take into account the cross-links, we ought to
778         // take them into account here *once* and then remove them
779         // from the crossLinkedRegionList so that we do not take them
780         // into account more than once.
781 
782         // Iterate in the crossLinkedRegionList (do that in reverse
783         // order because we'll have at some point to have to remove
784         // items) and...
785 
786         int jter = crossLinkedRegionList.size() - 1;
787 
788         while(jter >= 0)
789           {
790             // ... for each item in it ask if the region encompasses
791             // the current monomer index (value of iter)....
792 
793             CrossLinkedRegion *region = crossLinkedRegionList.at(jter);
794 
795             if(region->endIndex() == iter)
796               {
797                 // ... if so, iterate in the list of cross-links that
798                 // is stored in the CrossLinkedRegion...
799 
800                 const QList<CrossLink *> & crossLinkList =
801                   region->crossLinkList();
802 
803                 for(int kter = 0; kter < crossLinkList.size(); ++kter)
804                   {
805                     // ... and for each cross-link, account its mass
806                     // in the fragment (that is, ponderable)...
807 
808                     CrossLink *crossLink = crossLinkList.at(kter);
809 
810                     crossLink->accountMasses(&ponderable, 1);
811                   }
812 
813                 // ... and remove+delete the CrossLinkedRegion from
814                 // the list so that we are sure we do not take that
815                 // cross-link into account more than once.
816 
817                 delete crossLinkedRegionList.takeAt(jter);
818               }
819 
820             --jter;
821           }
822 
823 
824 	Ponderable ponderableTemp(ponderable);
825 
826 	if(!fragOptions.formula().isEmpty())
827 	  if (!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
828 	    {
829 	      return -1;
830 	    }
831 
832 	Formula formula = mp_polChemDef->leftCap();
833 
834 	formula.accountMasses(refList, &ponderableTemp, 1);
835 
836 	if(m_calcOptions.polymerEntities() &
837 	    MXT_POLYMER_CHEMENT_LEFT_END_MODIF && !fragOptions.startIndex())
838 	  Polymer::accountEndModifMasses
839 	   (mp_polymer,
840 	     MXT_POLYMER_CHEMENT_LEFT_END_MODIF,
841 	     &ponderableTemp);
842 
843         // As of version 3.6.0, the polymer chemistry definition
844         // should define a formula for the FragSpec that yields a
845         // fragment oligomer having no charge: in a neutral state.
846 
847 	// We create an oligomer which is not ionized(false) but that
848 	// bears the default ionization rule, because this oligomer
849 	// might be later used in places where the ionization rule has
850 	// to be valid. For example, one drag and drop operation might
851 	// copy this oligomer into a mzLab dialog window where its
852 	// ionization rule validity might be challenged. Because this
853 	// fragmentation oligomer will bear only its intrinsic 1
854 	// charge, we should set the level member of the ionization to
855 	// 0.
856 
857 	IonizeRule ionizeRule(m_ionizeRule);
858 	ionizeRule.setLevel(0);
859 
860 	Oligomer *oligomer1 =
861 	  new Oligomer(mp_polymer,
862                        "NOT_SET",
863                        fragOptions.name() /*fragSpec.m_name*/,
864                        ponderableTemp,
865                        ionizeRule,
866                        m_calcOptions,
867                        false /*isIonized*/,
868                        fragOptions.startIndex(), iter /*endIndex*/);
869 
870 	// At this moment, the new fragment might be challenged for the
871 	// fragmented monomer's side chain contribution. For example, in
872 	// nucleic acids, it happens that during a fragmentation, the
873 	// base of the fragmented monomer is decomposed and goes
874 	// away. This is implemented in massXpert with the ability to
875 	// tell the fragmenter that upon fragmentation the mass of the
876 	// monomer is to be removed. The skeleton mass is then added to
877 	// the formula of the fragmentation pattern.
878 
879 	int monomerContrib = fragOptions.monomerContribution();
880 
881 	if(monomerContrib)
882 	  {
883 	    const Monomer *monomer = mp_polymer->at(iter);
884 
885 	    QString formula = monomer->formula();
886 
887 	    if (!monomer->accountMasses(&oligomer1->rmono(),
888 					 &oligomer1->ravg(),
889 					 monomerContrib))
890 	      {
891 		delete oligomer1;
892 
893 		return -1;
894 	      }
895 	  }
896 
897 
898 	// At this point we should check if the fragmentation
899 	// specification includes fragmentation rules that apply to this
900 	// fragment.
901 
902 	for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
903 	  {
904 	    // The accounting of the fragrule is performed on a
905 	    // singly-charged oligomer, as defined by the fragmentation
906 	    // formula. Later, we'll have to take into account the fact
907 	    // that the user might want to calculate fragment m/z with
908 	    // z>1.
909 
910 	    FragRule *fragRule = fragOptions.ruleList().at(jter);
911 
912 	    if (!accountFragRule(fragRule, true, iter, MXT_FRAG_END_LEFT, 0))
913 	      continue;
914 
915 	    // Each fragrule triggers the creation of a new oligomer.
916 
917 	    Oligomer *oligomer2 =
918 	      new Oligomer(*oligomer1);
919 
920 	    if (!accountFragRule(fragRule, false, iter,
921 				  MXT_FRAG_END_LEFT, oligomer2))
922 	      {
923 		delete oligomer1;
924 		delete oligomer2;
925 
926 		return -1;
927 	      }
928 
929 	    // At this point we have the fragment oligomer within a
930 	    // neutral state, because starting with version 3.6.0, the
931 	    // fragmentation specification should yield a neutral
932 	    // molecular species.
933 
934 	    Oligomer *newOligomer =
935 	      new Oligomer(*oligomer2);
936 
937             int charge = 0;
938 
939             // We can immediately set the name of template oligomer on which
940             // to base the creation of the derivative formula-based
941             // oligomers.
942 	    QString name = QString("%1#%2#(%3)")
943 	      .arg(fragOptions.name())
944 	      .arg(number + 1)
945 	      .arg(fragRule->name());
946 
947             // Set the name of this template oligomer, but with the
948             // charge in the form of "#z=1".
949             QString nameWithCharge = name;
950             nameWithCharge.append(QString("#z=%1").arg(charge));
951 
952             newOligomer->setName(nameWithCharge);
953 
954             // We should make a temporary list of oligomers to handle
955             // both formulas and charge state.
956 
957             OligomerList *formulaOligomerList =
958               new OligomerList(fragOptions.name(), mp_polymer);
959 
960             // Append the template oligomer to the temporary list so
961             // that the called function has it to base new oligomers
962             // on it.
963             formulaOligomerList->append(newOligomer);
964 
965             // Let the following steps know that we actually succeeded
966             // in preparing an oligonucleotide with a fragmentation
967             // rule applied.
968 
969             fragRuleApplied = true;
970 
971             // Now that the list has ONE template item, we can use
972             // that list to stuff in it all the other ones depending
973             // on the presence of any formula in fragOptions. Indeed,
974             // it might be that the user has checked the -H20 or -NH3
975             // checkboxes, asking that such formulas be accounted for
976             // in the generation of the fragment oligomers. Account
977             // for these potential formulas...
978 
979             //            int accountedFormulas =
980             accountFormulas(formulaOligomerList, fragOptions, name, charge);
981 
982             // We now have a list of oligomers (or only one if there
983             // was no formula to take into account). For each
984             // oligomer, we have to account for the charge levels
985             // asked by the user.
986 
987             OligomerList *ionizeLevelOligomerList =
988               accountIonizationLevels(formulaOligomerList, fragOptions);
989 
990               // First off, we can finally delete the grand template oligomer.
991               delete oligomer2;
992 
993               if(!ionizeLevelOligomerList)
994                 {
995                   qDebug() << __FILE__ << __LINE__
996                            << QObject::tr("massxpert - Fragmentation : "
997                                           "Failed to generate ionized "
998                                           "fragment oligomers.");
999 
1000                   while(!formulaOligomerList->isEmpty())
1001                     delete formulaOligomerList->takeFirst();
1002 
1003                   delete formulaOligomerList;
1004 
1005                   return -1;
1006                 }
1007 
1008               // Note that during the work on ionizeLevels, the list
1009               // of oligomers that was passed to that function as a
1010               // parameter has gotten emptied. It is thus now time to
1011               // delete it.
1012               Q_ASSERT(formulaOligomerList->isEmpty());
1013               delete formulaOligomerList;
1014 
1015               // At this point, we have to remove all the oligomers
1016               // from the lastOligomerList and put them into the
1017               // oligomerList.
1018 
1019               while(!ionizeLevelOligomerList->isEmpty())
1020                 {
1021                   mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
1022                   ++count;
1023                 }
1024 
1025               delete ionizeLevelOligomerList;
1026           }
1027 	// End of
1028 	// for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
1029 
1030 	// We are here because of two reasons:
1031 
1032 	// 1. because the array of fragRules did not contain any
1033 	// 1. fragRule, in which case we still have to validate and
1034 	// 1. terminate the oligomer1 (fragRuleApplied is false);
1035 
1036 	// 2. because we finished dealing with fragRules, in which case
1037 	// 2. we ONLY add oligomer1 to the list of fragments if none
1038 	// 2. of the fragrules analyzed above gave a successfully
1039 	// 2. generated fragment(fragRuleApplied is false).
1040 
1041 	if(!fragRuleApplied)
1042 	  {
1043 	    // At this point we have the fragment oligomer. However, do
1044 	    // not forget that the user might ask for fragments that
1045 	    // bear more than the single charge that was intrinsically
1046 	    // computed within the formula of the fragmentation
1047 	    // specification.
1048 
1049 	    // So, first create an oligomer with the "default"
1050 	    // fragmentation specification-driven neutral state (that
1051 	    // is, charge = 0).
1052 
1053 	    Oligomer *newOligomer =
1054 	      new Oligomer(*oligomer1);
1055 
1056             int charge = 0;
1057 
1058             // We can immediately set the name of template oligomer on which
1059             // to base the creation of the derivative formula-based
1060             // oligomers.
1061 	    QString name = QString("%1#%2")
1062 	      .arg(fragOptions.name())
1063 	      .arg(number + 1);
1064 
1065             // Set the name of this template oligomer, but with the
1066             // charge in the form of "#z=1".
1067             QString nameWithCharge = name;
1068             nameWithCharge.append(QString("#z=%1").arg(charge));
1069 
1070             newOligomer->setName(nameWithCharge);
1071 
1072             // We should make a temporary list of oligomers to handle
1073             // both formulas and charge state.
1074 
1075             OligomerList *formulaOligomerList =
1076               new OligomerList(fragOptions.name(), mp_polymer);
1077 
1078             // Append the template oligomer to the temporary list so
1079             // that the called function has it to base new oligomers
1080             // on it.
1081             formulaOligomerList->append(newOligomer);
1082 
1083             // Now that the list has ONE template item, we can use
1084             // that list to stuff in it all the other ones depending
1085             // on the presence of any formula in fragOptions. Indeed,
1086             // it might be that the user has checked the -H20 or -NH3
1087             // checkboxes, asking that such formulas be accounted for
1088             // in the generation of the fragment oligomers. Account
1089             // for these potential formulas...
1090 
1091             //            int accountedFormulas =
1092             accountFormulas(formulaOligomerList, fragOptions, name, charge);
1093 
1094             // We now have a list of oligomers (or only one if there
1095             // was no formula to take into account). For each
1096             // oligomer, we have to account for the charge levels
1097             // asked by the user.
1098 
1099             OligomerList *ionizeLevelOligomerList =
1100               accountIonizationLevels(formulaOligomerList, fragOptions);
1101 
1102               // First off, we can finally delete the grand template
1103               // oligomer (oligomer with no frag rules applied).
1104               delete oligomer1;
1105 
1106               if(!ionizeLevelOligomerList)
1107                 {
1108                   qDebug() << __FILE__ << __LINE__
1109                            << QObject::tr("massxpert - Fragmentation : "
1110                                           "Failed to generate ionized "
1111                                           "fragment oligomers.");
1112 
1113                   while(!formulaOligomerList->isEmpty())
1114                     delete formulaOligomerList->takeFirst();
1115 
1116                   delete formulaOligomerList;
1117 
1118                   return -1;
1119                 }
1120 
1121               // At this point, we have to remove all the oligomers
1122               // from the lastOligomerList and put them into the
1123               // oligomerList.
1124 
1125               while(!ionizeLevelOligomerList->isEmpty())
1126                 {
1127                   Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();
1128 
1129                   mp_oligomerList->append(iterOligomer);
1130                   ++count;
1131                 }
1132 
1133               delete ionizeLevelOligomerList;
1134 	  }
1135         // End of
1136         // if(!fragRuleApplied)
1137         else // (fragRuleApplied == true)
1138           {
1139             // There were fragmentation rule(s) that could be
1140             // successfully applied. Thus we already have created the
1141             // appropriate oligomers. Simply delete the template
1142             // oligomer.
1143                 delete oligomer1;
1144           }
1145       }
1146     // End of
1147     //   for (int iter = fragOptions.startIndex();
1148     //   iter < fragOptions.endIndex() + 1; ++iter, ++count)
1149 
1150 
1151     return count;
1152   }
1153 
1154 
1155   int
fragmentEndRight(FragOptions & fragOptions)1156   Fragmenter::fragmentEndRight(FragOptions &fragOptions)
1157   {
1158     int count = 0;
1159     int number = 0;
1160 
1161     static Ponderable ponderable;
1162     ponderable.clearMasses();
1163 
1164     const QList<Atom *> &refList = mp_polChemDef->atomList();
1165 
1166     // If the crosslinks are to be taken into account, then make a
1167     // local copy of the m_crossLinkedRegionList because we are going
1168     // to remove items from it during the calculation of the fragments
1169     // and we do not want to modify the contents of the original list
1170     // (remember that this fragmenter might be created to perform more
1171     // than one single fragmentation but a set of fragmentations).
1172 
1173     QList<CrossLinkedRegion *> crossLinkedRegionList;
1174 
1175     for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
1176       {
1177         CrossLinkedRegion *region =
1178           new CrossLinkedRegion(*m_crossLinkedRegionList.at(iter));
1179 
1180         crossLinkedRegionList.append(region);
1181       }
1182 
1183     // At this point we can start making the calculations of the
1184     // fragments. Because we are generating fragments that contain the
1185     // right part of the oligomer, we iterate in the
1186     // fragOptions.endIndex() --> fragOptions.startIndex() direction.
1187 
1188     for (int iter = fragOptions.endIndex();
1189 	 iter > fragOptions.startIndex(); --iter, ++number)
1190       {
1191         bool fragRuleApplied = false;
1192 
1193 	const Monomer *monomer = mp_polymer->at(iter);
1194 
1195 	monomer->accountMasses(&ponderable, 1);
1196 
1197         // If we are to take into account the cross-links, we ought to
1198         // take them into account here *once* and then remove them
1199         // from the crossLinkedRegionList so that we do not take them
1200         // into account more than once.
1201 
1202         // Iterate in the crossLinkedRegionList (do that in reverse
1203         // order because we'll have at some point to have to remove
1204         // items) and...
1205 
1206         int jter = crossLinkedRegionList.size() - 1;
1207 
1208         while(jter >= 0)
1209           {
1210             // ... for each item in it ask if the region encompasses
1211             // the current monomer index (value of iter)....
1212 
1213             CrossLinkedRegion *region = crossLinkedRegionList.at(jter);
1214 
1215             if(region->startIndex() == iter)
1216               {
1217                 // ... if so, iterate in the list of cross-links that
1218                 // is stored in the CrossLinkedRegion...
1219 
1220                 const QList<CrossLink *> & crossLinkList =
1221                   region->crossLinkList();
1222 
1223                 for(int kter = 0; kter < crossLinkList.size(); ++kter)
1224                   {
1225                     // ... and for each cross-link, account its mass
1226                     // in the fragment (that is, ponderable)...
1227 
1228                     CrossLink *crossLink = crossLinkList.at(kter);
1229 
1230                     crossLink->accountMasses(&ponderable, 1);
1231                   }
1232 
1233                 // ... and remove+delete the CrossLinkedRegion from
1234                 // the list so that we are sure we do not take that
1235                 // cross-link into account more than once.
1236 
1237                 delete crossLinkedRegionList.takeAt(jter);
1238               }
1239 
1240             --jter;
1241           }
1242 
1243 
1244 	Ponderable ponderableTemp(ponderable);
1245 
1246 	if(!fragOptions.formula().isEmpty())
1247 	  if (!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
1248 	    {
1249 	      return -1;
1250 	    }
1251 
1252 	Formula formula = mp_polChemDef->rightCap();
1253 
1254 	formula.accountMasses(refList, &ponderableTemp, 1);
1255 
1256 	if(m_calcOptions.polymerEntities() &
1257 	    MXT_POLYMER_CHEMENT_RIGHT_END_MODIF &&
1258 	    fragOptions.endIndex() == mp_polymer->size() - 1)
1259 	  Polymer::accountEndModifMasses
1260 	   (mp_polymer,
1261 	     MXT_POLYMER_CHEMENT_RIGHT_END_MODIF,
1262 	     &ponderableTemp);
1263 
1264         // As of version 3.6.0, the polymer chemistry definition
1265         // should define a formula for the FragSpec that yields a
1266         // fragment oligomer having no charge: in a neutral state.
1267 
1268 	// We create an oligomer which is not ionized(false) but that
1269 	// bears the default ionization rule, because this oligomer
1270 	// might be later used in places where the ionization rule has
1271 	// to be valid. For example, one drag and drop operation might
1272 	// copy this oligomer into a mzLab dialog window where its
1273 	// ionization rule validity might be challenged. Because this
1274 	// fragmentation oligomer will bear only its intrinsic 1
1275 	// charge, we should set the level member of the ionization to
1276 	// 0.
1277 
1278 	IonizeRule ionizeRule(m_ionizeRule);
1279 	ionizeRule.setLevel(0);
1280 
1281 	Oligomer *oligomer1 =
1282 	  new Oligomer(mp_polymer,
1283                        "NOT_SET",
1284                        fragOptions.name() /*fragSpec.m_name*/,
1285                        ponderableTemp,
1286                        ionizeRule,
1287                        m_calcOptions,
1288                        false /*isIonized*/,
1289                        iter /*startIndex*/, fragOptions.endIndex() /*endIndex*/);
1290 
1291 	// At this moment, the new fragment might be challenged for the
1292 	// fragmented monomer's side chain contribution. For example, in
1293 	// nucleic acids, it happens that during a fragmentation, the
1294 	// base of the fragmented monomer is decomposed and goes
1295 	// away. This is implemented in massXpert with the ability to
1296 	// tell the fragmenter that upon fragmentation the mass of the
1297 	// monomer is to be removed. The skeleton mass is then added to
1298 	// the formula of the fragmentation pattern.
1299 
1300 	int monomerContrib = fragOptions.monomerContribution();
1301 
1302 	if(monomerContrib)
1303 	  {
1304 	    const Monomer *monomer = mp_polymer->at(iter);
1305 
1306 	    QString formula = monomer->formula();
1307 
1308 	    if (!monomer->accountMasses(&oligomer1->rmono(),
1309 					 &oligomer1->ravg(),
1310 					 monomerContrib))
1311 	      {
1312 		delete oligomer1;
1313 
1314 		return -1;
1315 	      }
1316 	  }
1317 
1318 	// At this point we should check if the fragmentation
1319 	// specification includes fragmentation rules that apply to this
1320 	// fragment.
1321 
1322 	for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
1323 	  {
1324 	    // The accounting of the fragrule is performed on a
1325 	    // singly-charged oligomer, as defined by the fragmentation
1326 	    // formula. Later, we'll have to take into account the fact
1327 	    // that the user might want to calculate fragment m/z with
1328 	    // z>1.
1329 
1330 	    FragRule *fragRule = fragOptions.ruleList().at(jter);
1331 
1332 	    if (!accountFragRule(fragRule, true, iter, MXT_FRAG_END_RIGHT, 0))
1333 	      continue;
1334 
1335 	    // Each fragrule triggers the creation of a new oligomer.
1336 
1337 	    Oligomer *oligomer2 =
1338 	      new Oligomer(*oligomer1);
1339 
1340 	    if (!accountFragRule(fragRule, false, iter,
1341 				  MXT_FRAG_END_RIGHT, oligomer2))
1342 	      {
1343 		delete oligomer1;
1344 		delete oligomer2;
1345 
1346 		return -1;
1347 	      }
1348 
1349 	    // At this point we have the fragment oligomer within a
1350 	    // neutral state, because starting with version 3.6.0, the
1351 	    // fragmentation specification should yield a neutral
1352 	    // molecular species.
1353 
1354 	    Oligomer *newOligomer =
1355 	      new Oligomer(*oligomer2);
1356 
1357             int charge = 0;
1358 
1359             // We can immediately set the name of template oligomer on which
1360             // to base the creation of the derivative formula-based
1361             // oligomers.
1362 	    QString name = QString("%1#%2#(%3)")
1363 	      .arg(fragOptions.name())
1364 	      .arg(number + 1)
1365 	      .arg(fragRule->name());
1366 
1367             // Set the name of this template oligomer, but with the
1368             // charge in the form of "#z=1".
1369             QString nameWithCharge = name;
1370             nameWithCharge.append(QString("#z=%1").arg(charge));
1371 
1372             newOligomer->setName(nameWithCharge);
1373 
1374             // We should make a temporary list of oligomers to handle
1375             // both formulas and charge state.
1376 
1377             OligomerList *formulaOligomerList =
1378               new OligomerList(fragOptions.name(), mp_polymer);
1379 
1380             // Append the template oligomer to the temporary list so
1381             // that the called function has it to base new oligomers
1382             // on it.
1383             formulaOligomerList->append(newOligomer);
1384 
1385             // Let the following steps know that we actually succeeded
1386             // in preparing an oligonucleotide with a fragmentation
1387             // rule applied.
1388 
1389             fragRuleApplied = true;
1390 
1391             // Now that the list has ONE template item, we can use
1392             // that list to stuff in it all the other ones depending
1393             // on the presence of any formula in fragOptions. Indeed,
1394             // it might be that the user has checked the -H20 or -NH3
1395             // checkboxes, asking that such formulas be accounted for
1396             // in the generation of the fragment oligomers. Account
1397             // for these potential formulas...
1398 
1399             //            int accountedFormulas =
1400             accountFormulas(formulaOligomerList, fragOptions, name, charge);
1401 
1402             // We now have a list of oligomers (or only one if there
1403             // was no formula to take into account). For each
1404             // oligomer, we have to account for the charge levels
1405             // asked by the user.
1406 
1407             OligomerList *ionizeLevelOligomerList =
1408               accountIonizationLevels(formulaOligomerList, fragOptions);
1409 
1410               // First off, we can finally delete the grand template oligomer.
1411               delete oligomer2;
1412 
1413               if(!ionizeLevelOligomerList)
1414                 {
1415                   qDebug() << __FILE__ << __LINE__
1416                            << QObject::tr("massxpert - Fragmentation : "
1417                                           "Failed to generate ionized "
1418                                           "fragment oligomers.");
1419 
1420                   while(!formulaOligomerList->isEmpty())
1421                     delete formulaOligomerList->takeFirst();
1422 
1423                   delete formulaOligomerList;
1424 
1425                   return -1;
1426                 }
1427 
1428               // Note that during the work on ionizeLevels, the list
1429               // of oligomers that was passed to that function as a
1430               // parameter has gotten emptied. It is thus now time to
1431               // delete it.
1432               Q_ASSERT(formulaOligomerList->isEmpty());
1433               delete formulaOligomerList;
1434 
1435               // At this point, we have to remove all the oligomers
1436               // from the lastOligomerList and put them into the
1437               // oligomerList.
1438 
1439               while(!ionizeLevelOligomerList->isEmpty())
1440                 {
1441                   mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
1442                   ++count;
1443                 }
1444 
1445               delete ionizeLevelOligomerList;
1446           }
1447 	// End of
1448 	// for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
1449 
1450 	// We are here because of two reasons:
1451 
1452 	// 1. because the array of fragRules did not contain any
1453 	// 1. fragRule, in which case we still have to validate and
1454 	// 1. terminate the oligomer1 (fragRuleApplied is false);
1455 
1456 	// 2. because we finished dealing with fragRules, in which case
1457 	// 2. we ONLY add oligomer1 to the list of fragments if none
1458 	// 2. of the fragrules analyzed above gave a successfully
1459 	// 2. generated fragment(fragRuleApplied is false).
1460 
1461 	if(!fragRuleApplied)
1462 	  {
1463 	    // At this point we have the fragment oligomer. However, do
1464 	    // not forget that the user might ask for fragments that
1465 	    // bear more than the single charge that was intrinsically
1466 	    // computed within the formula of the fragmentation
1467 	    // specification.
1468 
1469 	    // So, first create an oligomer with the "default"
1470 	    // fragmentation specification-driven neutral state (that
1471 	    // is, charge = 0).
1472 
1473 	    Oligomer *newOligomer =
1474 	      new Oligomer(*oligomer1);
1475 
1476             int charge = 0;
1477 
1478             // We can immediately set the name of template oligomer on which
1479             // to base the creation of the derivative formula-based
1480             // oligomers.
1481 	    QString name = QString("%1#%2")
1482 	      .arg(fragOptions.name())
1483 	      .arg(number + 1);
1484 
1485             // Set the name of this template oligomer, but with the
1486             // charge in the form of "#z=1".
1487             QString nameWithCharge = name;
1488             nameWithCharge.append(QString("#z=%1").arg(charge));
1489 
1490             newOligomer->setName(nameWithCharge);
1491 
1492             // We should make a temporary list of oligomers to handle
1493             // both formulas and charge state.
1494 
1495             OligomerList *formulaOligomerList =
1496               new OligomerList(fragOptions.name(), mp_polymer);
1497 
1498             // Append the template oligomer to the temporary list so
1499             // that the called function has it to base new oligomers
1500             // on it.
1501             formulaOligomerList->append(newOligomer);
1502 
1503             // Now that the list has ONE template item, we can use
1504             // that list to stuff in it all the other ones depending
1505             // on the presence of any formula in fragOptions. Indeed,
1506             // it might be that the user has checked the -H20 or -NH3
1507             // checkboxes, asking that such formulas be accounted for
1508             // in the generation of the fragment oligomers. Account
1509             // for these potential formulas...
1510 
1511             //            int accountedFormulas =
1512             accountFormulas(formulaOligomerList, fragOptions, name, charge);
1513 
1514             // We now have a list of oligomers (or only one if there
1515             // was no formula to take into account). For each
1516             // oligomer, we have to account for the charge levels
1517             // asked by the user.
1518 
1519             OligomerList *ionizeLevelOligomerList =
1520               accountIonizationLevels(formulaOligomerList, fragOptions);
1521 
1522               // First off, we can finally delete the grand template
1523               // oligomer (oligomer with no frag rules applied).
1524               delete oligomer1;
1525 
1526               if(!ionizeLevelOligomerList)
1527                 {
1528                   qDebug() << __FILE__ << __LINE__
1529                            << QObject::tr("massxpert - Fragmentation : "
1530                                           "Failed to generate ionized "
1531                                           "fragment oligomers.");
1532 
1533                   while(!formulaOligomerList->isEmpty())
1534                     delete formulaOligomerList->takeFirst();
1535 
1536                   delete formulaOligomerList;
1537 
1538                   return -1;
1539                 }
1540 
1541               // At this point, we have to remove all the oligomers
1542               // from the lastOligomerList and put them into the
1543               // oligomerList.
1544 
1545               while(!ionizeLevelOligomerList->isEmpty())
1546                 {
1547                   Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();
1548 
1549                   mp_oligomerList->append(iterOligomer);
1550                   ++count;
1551                 }
1552 
1553               delete ionizeLevelOligomerList;
1554 	  }
1555         // End of
1556         // if(!fragRuleApplied)
1557         else // (fragRuleApplied == true)
1558           {
1559             // There were fragmentation rule(s) that could be
1560             // successfully applied. Thus we already have created the
1561             // appropriate oligomers. Simply delete the template
1562             // oligomer.
1563                 delete oligomer1;
1564           }
1565       }
1566     // End of
1567     //  for (int iter = fragOptions.endIndex();
1568     //  iter > fragOptions.endIndex() - 1; --iter, ++number)
1569 
1570     return count;
1571   }
1572 
1573 
1574   bool
accountFragRule(FragRule * fragRule,bool onlyCheck,int index,int fragEnd,Ponderable * ponderable)1575   Fragmenter::accountFragRule(FragRule *fragRule, bool onlyCheck,
1576 			       int index, int fragEnd,
1577 			       Ponderable *ponderable)
1578   {
1579     const Monomer *prevMonomer = 0;
1580     const Monomer *nextMonomer = 0;
1581 
1582     QList<Atom *> refList = mp_polChemDef->atomList();
1583 
1584     Q_ASSERT(fragRule);
1585 
1586     if (!onlyCheck)
1587       Q_ASSERT(ponderable);
1588 
1589 
1590     const Monomer *monomer = mp_polymer->at(index);
1591 
1592     if (!fragRule->currCode().isEmpty())
1593       if (fragRule->currCode() != monomer->code())
1594 	return false;
1595 
1596     if (!fragRule->prevCode().isEmpty() &&
1597 	!fragRule->nextCode().isEmpty())
1598       {
1599 	if(fragEnd & MXT_FRAG_END_LEFT || fragEnd & MXT_FRAG_END_NONE)
1600 	  {
1601 	    if (!index)
1602 	      // There cannot be any prevCode since we are at index ==
1603 	      // 0, at the first monomer of the fragmentation
1604 	      // series. That means that we can return immediately.
1605 	      return false;
1606 
1607 	    // Since we know that we are either in LEFT or NONE end
1608 	    // mode, we know that previous is at index 'index' - 1. Thus
1609 	    // get the monomer out of the sequence for this index.
1610 
1611 	    prevMonomer = mp_polymer->at(index - 1);
1612 
1613 	    if (index == mp_polymer->size() - 1)
1614 	      // There cannot be any next code since we are already at
1615 	      // the last monomer in the fragmentation series.
1616 	      return false;
1617 
1618 	    nextMonomer = mp_polymer->at(index + 1);
1619 	  }
1620 	else if (fragEnd & MXT_FRAG_END_RIGHT)
1621 	  {
1622 	    if (!index)
1623 	      // There cannot be any nextCode since currCode is the last
1624 	      // monomer in the fragmentation series.
1625 	      return false;
1626 
1627 	    nextMonomer = mp_polymer->at(index - 1);
1628 
1629 	    if (index == mp_polymer->size() - 1)
1630 	      // There cannot be any previous code since currCode is the
1631 	      // first in the fragmentation series.
1632 	      return false;
1633 
1634 	    prevMonomer = mp_polymer->at(index + 1);
1635 	  }
1636 	else
1637 	  return false;
1638 
1639 	// Now that the prevCode and nextCode have been correctly
1640 	// identified, we can go on and check if some conditions are
1641 	// met.
1642 
1643 	if(fragRule->prevCode() == prevMonomer->code() &&
1644 	    fragRule->nextCode() == nextMonomer->code())
1645 	  {
1646 	    if (onlyCheck)
1647 	      return true;
1648 
1649 	    // The fragmentation rule condition is met, we can apply its
1650 	    // formula.
1651 
1652 	    if (!fragRule->Formula::accountMasses(refList,
1653 						   ponderable))
1654 	      {
1655 		qDebug() << __FILE__ << __LINE__
1656 			  << "Failed to account fragmentation rule";
1657 
1658 		return false;
1659 	      }
1660 
1661 	    return true;
1662 	  }
1663 	else
1664 	  {
1665 	    if (onlyCheck)
1666 	      return false;
1667 	    else
1668 	      return true;
1669 	  }
1670       }
1671     // End of
1672     //   if (!fragRule->prevCode().isEmpty() &&
1673     //   !fragRule->nextCode().isEmpty())
1674     else if (!fragRule->prevCode().isEmpty())
1675       {
1676 	if(fragEnd & MXT_FRAG_END_LEFT || fragEnd & MXT_FRAG_END_NONE)
1677 	  {
1678 	    if (!index)
1679 	      // There cannot be any prevCode since currCode is already
1680 	      // the first of the fragmentation series.
1681 	      return false;
1682 
1683 	    // Since we know that fragEnd is either LEFT or NONE end, we
1684 	    // know what index has the prevCode:
1685 
1686 	    prevMonomer = mp_polymer->at(index - 1);
1687 	  }
1688 	else if (fragEnd & MXT_FRAG_END_RIGHT)
1689 	  {
1690 	    if (index == mp_polymer->size() - 1)
1691 	      // There cannot be any prevCode since currCode is already
1692 	      // the first of the fragmentation series.
1693 	      return false;
1694 
1695 	    prevMonomer = mp_polymer->at(index + 1);
1696 	  }
1697 	else
1698 	  return false;
1699 
1700 	// Now that we have correctly identified the prevCode, we can go
1701 	// on and check if some conditions are met.
1702 
1703 	if(fragRule->prevCode() == prevMonomer->code())
1704 	  {
1705 	    if (onlyCheck)
1706 	      return true;
1707 
1708 	    // The fragmentation rule condition is met, we can apply its
1709 	    // formula.
1710 
1711 	    if (!fragRule->Formula::accountMasses(refList, ponderable))
1712 	      {
1713 		qDebug() << __FILE__ << __LINE__
1714 			  << "Failed to account fragmentation rule";
1715 
1716 		return false;
1717 	      }
1718 
1719 	    return true;
1720 	  }
1721 	else
1722 	  {
1723 	    if (onlyCheck)
1724 	      return false;
1725 	    else
1726 	      return true;
1727 	  }
1728       }
1729     // End of
1730     // else if (!fragRule->prevCode().isEmpty())
1731     else if (!fragRule->nextCode().isEmpty())
1732       {
1733 	if(fragEnd & MXT_FRAG_END_LEFT || fragEnd & MXT_FRAG_END_NONE)
1734 	  {
1735 	    if (index == mp_polymer->size() - 1)
1736 	      // There cannot be any nextCode since currCode is already
1737 	      // the last of the fragmentation series.
1738 	      return false;
1739 
1740 	    // Since we know that fragEnd is either LEFT or NONE end, we
1741 	    // know what index has the prevCode:
1742 
1743 	    nextMonomer = mp_polymer->at(index + 1);
1744 	  }
1745 	else if (fragEnd & MXT_FRAG_END_RIGHT)
1746 	  {
1747 	    if (!index)
1748 	      // There cannot be any prevCode since currCode is already
1749 	      // the last of the fragmentation series.
1750 	      return false;
1751 
1752 	    nextMonomer = mp_polymer->at(index - 1);
1753 	  }
1754 	else
1755 	  return false;
1756 
1757 	// Now that we have correctly identified the nextCode, we can go
1758 	// on and check if some conditions are met.
1759 
1760 	if(fragRule->nextCode() == nextMonomer->code())
1761 	  {
1762 	    if (onlyCheck)
1763 	      return true;
1764 
1765 	    // The fragmentation rule condition is met, we can apply its
1766 	    // formula.
1767 
1768 	    if (!fragRule->Formula::accountMasses(refList,
1769 						   ponderable))
1770 	      {
1771 		qDebug() << __FILE__ << __LINE__
1772 			  << "Failed to account fragmentation rule";
1773 
1774 		return false;
1775 	      }
1776 
1777 	    return true;
1778 	  }
1779 	else
1780 	  {
1781 	    if (onlyCheck)
1782 	      return false;
1783 	    else
1784 	      return true;
1785 	  }
1786       }
1787     // End of
1788     // else if (!fragRule->nextCode().isEmpty())
1789     else
1790       {
1791 	// All the prev and next codes are empty, which means that we
1792 	// consider the conditions verified.
1793 	if(onlyCheck)
1794 	  return true;
1795 
1796 	if(!fragRule->Formula::accountMasses(refList,
1797 					       ponderable))
1798 	  {
1799 	    qDebug() << __FILE__ << __LINE__
1800 		      << "Failed to account fragmentation rule";
1801 
1802 	    return false;
1803 	  }
1804 
1805 	return true;
1806       }
1807 
1808     // We should never reach this point !
1809     Q_ASSERT(0);
1810 
1811     return false;
1812   }
1813 
1814 
1815   int
accountFormulas(OligomerList * oligomerList,FragOptions & fragOptions,QString name,int charge)1816   Fragmenter::accountFormulas(OligomerList *oligomerList,
1817                               FragOptions &fragOptions,
1818                               QString name, int charge)
1819   {
1820     Q_ASSERT(oligomerList);
1821 
1822     const QList <Atom *> &atomRefList = fragOptions.polChemDef()->atomList();
1823     int count = 0;
1824 
1825     // The oligomer that we get as parameter is the template on which
1826     // to base the derivatives on the basis of the formulas.
1827     Oligomer *templateOligomer = oligomerList->first();
1828 
1829     // At this point check if the fragOptions.m_formulaList has items
1830     // in it.
1831 
1832     const QList<Formula *>& formulaList = fragOptions.formulaList();
1833 
1834     for(int iter = 0; iter < formulaList.size(); ++iter)
1835       {
1836         Formula *formula = formulaList.at(iter);
1837 
1838         // We will apply the formula to a copy of the template oligomer
1839         Oligomer *newOligomer =
1840           new Oligomer(*templateOligomer);
1841 
1842         if(!formula->accountMasses(atomRefList, newOligomer))
1843           {
1844             qDebug() << __FILE__ << __LINE__
1845                      << "Failed to account formula";
1846 
1847             delete newOligomer;
1848             continue;
1849           }
1850 
1851         // qDebug() << __FILE__ << __LINE__
1852         //          << "right after accountFormulaIteration:"
1853         //          << newOligomer->mono();
1854 
1855         // The new oligomer could be generated correctly. Append the
1856         // formula to its name, so that we'll be able to recognize it.
1857 
1858         QString newName = name;
1859 
1860         newName.append(QString("#%1#z=%2")
1861                        .arg(formula->text())
1862                        .arg(charge));
1863 
1864         newOligomer->setName(newName);
1865 
1866         // At this point append the new oligomer to the list.
1867         oligomerList->append(newOligomer);
1868 
1869         ++count;
1870       }
1871 
1872     return count;
1873   }
1874 
1875 
1876   OligomerList *
accountIonizationLevels(OligomerList * oligomerList,FragOptions & fragOptions)1877   Fragmenter::accountIonizationLevels(OligomerList *oligomerList,
1878                                       FragOptions &fragOptions)
1879   {
1880     Q_ASSERT(oligomerList);
1881 
1882     bool wasFailure = false;
1883 
1884     // We ge a list of oligomers (or only one, in fact, if no
1885     // -H2O/-NH3 formulas were checked by the user in the graphical
1886     // user interface), and we have for each to compute the required
1887     // ionisation levels. Indeed, the user might ask for fragments
1888     // that bear more than the single charge that was intrinsically
1889     // computed within the formula of the fragmentation
1890     // specification. Thus create as many new oligomers as needed for
1891     // the different charge levels asked by the user.  Because the
1892     // ionization changes the values in the oligomer, and we need a
1893     // new oligomer each time, we duplicate the oligomer each time we
1894     // need it.
1895 
1896     int startIonizeLevel = fragOptions.startIonizeLevel();
1897     int endIonizeLevel = fragOptions.endIonizeLevel();
1898 
1899     // We have to perform the operation for each oligomer in
1900     // oligomerList. We populate a new oligomerList that we return
1901     // filled with at least the same oligomers that were in
1902     // oligomerList passed as parameter.
1903 
1904     OligomerList * newOligomerList =
1905       new OligomerList(fragOptions.name(), mp_polymer);
1906 
1907     while(!oligomerList->isEmpty())
1908       {
1909         Oligomer *curOligomer = oligomerList->takeFirst();
1910 
1911         // First of remove the currently iterated oligomer from the
1912         // list, we won't need it as it is charged 0. But we need to
1913         // make a copy of it before deleting it at the end of the
1914         // ionization loop below.
1915 
1916         // At this point use that oligomer as a template for the
1917         // ionization level stuff.
1918 
1919         for (int kter = startIonizeLevel; kter < endIonizeLevel + 1; ++kter)
1920           {
1921             IonizeRule ionizeRule(m_ionizeRule);
1922             ionizeRule.setLevel(kter);
1923 
1924             Oligomer *newOligomer = new Oligomer(*curOligomer);
1925 
1926             // qDebug() << __FILE__ << __LINE__
1927             //          << "right before ionizing with ionizerule:"
1928             //          << newOligomer->mono()
1929             //          << "with ionize level =" << kter
1930             //          << " charge of the oligomer: " << newOligomer->charge();
1931 
1932             // If the result of the call below is -1, then that
1933             // means that there was an error and we should return
1934             // immediately. If it is 0, then that means that no
1935             // error was encountered, but that no actual ionization
1936             // took place, so we need not take into account the
1937             // oligomer.
1938 
1939             int res = newOligomer->ionize(ionizeRule);
1940 
1941             if(res == -1)
1942               {
1943                 delete newOligomer;
1944 
1945                 wasFailure = true;
1946 
1947                 break;
1948               }
1949             else if (res == 0)
1950               {
1951                 delete newOligomer;
1952 
1953                 continue;
1954               }
1955 
1956             // qDebug() << __FILE__ << __LINE__
1957             //          << "right after ionizing with ionizerule:"
1958             //          << newOligomer->mono()
1959             //          << "with ionize level =" << kter
1960             //          << " charge of the oligomer: " << newOligomer->charge();
1961 
1962             // At this point the ionization did indeed perform
1963             // something interesting, craft the name of the resulting
1964             // oligomer and set it. We must of the name of the
1965             // oligomer, but simply replace the value substring
1966             // "#z=xx" with "z=yy".
1967 
1968             QString name = newOligomer->name();
1969             QString chargeLevel = QString("z=%1").arg(newOligomer->charge());
1970 
1971             name.replace(QRegExp("z=\\d+$"), chargeLevel);
1972 
1973             newOligomer->setName(name);
1974 
1975             // qDebug() << __FILE__ << __LINE__
1976             //          << "newOligomer charge: " << newOligomer->charge()
1977             //          << "name:" << newOligomer->name();
1978 
1979             newOligomerList->append(newOligomer);
1980           }
1981         // End of
1982         // for (int kter = startIonizeLevel; kter < endIonizeLevel; ++kter)
1983 
1984         // We can now delete the oligomer that was used as a template,
1985         // and which had a charge of 0.
1986         delete(curOligomer);
1987 
1988         // If there was a single failure, we get here with wasFailure
1989         // set to true. In that case, free the newOligomerList and
1990         // return NULL.
1991 
1992         if(wasFailure)
1993           {
1994             // Empty the new oligomer list and delete it.
1995             while(!newOligomerList->isEmpty())
1996               delete newOligomerList->takeFirst();
1997 
1998             delete newOligomerList;
1999 
2000             // Also empty the oligomer list passed as parameter, as
2001             // the caller expects all of its item to be transferred to
2002             // the new oligomer list and will delete the initial list.
2003 
2004             while(!oligomerList->isEmpty())
2005               delete oligomerList->takeFirst();
2006 
2007             // At this point we freed all the allocated data, we can return.
2008             return NULL;
2009           }
2010       }
2011     // End of
2012     // while(!oligomerList.isEmpty())
2013 
2014     // At this point, we can Q_ASSERT that oligomerList is empty !
2015     Q_ASSERT(oligomerList->isEmpty());
2016 
2017     return newOligomerList;
2018   }
2019 
2020 
2021   void
emptyOligomerList()2022   Fragmenter::emptyOligomerList()
2023   {
2024     while (mp_oligomerList->size())
2025       {
2026 	delete mp_oligomerList->takeFirst();
2027       }
2028   }
2029 
2030 } // namespace massXpert
2031