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