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 /////////////////////// Std includes
39 #include <cmath>
40 
41 
42 /////////////////////// Local includes
43 #include "pkaPhPi.hpp"
44 
45 
46 namespace massXpert
47 {
48 
PkaPhPi(Polymer & polymer,CalcOptions & calcOptions,QList<Monomer * > * monomerList,QList<Modif * > * modifList)49   PkaPhPi::PkaPhPi(Polymer &polymer,
50 		    CalcOptions &calcOptions,
51 		    QList<Monomer *> *monomerList,
52 		    QList<Modif *> *modifList)
53     : m_polymer(polymer),
54       m_calcOptions(calcOptions),
55       mpa_monomerList(monomerList),
56       mpa_modifList(modifList)
57   {
58     m_ph = 7;
59     m_pi = 7;
60     m_positiveCharges = 0;
61     m_negativeCharges = 0;
62 
63     mp_aborted = 0;
64     mp_progress = 0;
65   }
66 
67 
~PkaPhPi()68   PkaPhPi::~PkaPhPi()
69   {
70     if (mpa_monomerList)
71       {
72 	while(!mpa_monomerList->isEmpty())
73 	  delete mpa_monomerList->takeFirst();
74 
75 	delete mpa_monomerList;
76 	mpa_monomerList = 0;
77       }
78 
79     if (mpa_modifList)
80       {
81 	while(!mpa_modifList->isEmpty())
82 	  delete mpa_modifList->takeFirst();
83 
84 	delete mpa_modifList;
85 
86 	mpa_modifList = 0;
87       }
88 
89   }
90 
91 
92   void
setPh(double ph)93   PkaPhPi::setPh(double ph)
94   {
95     Q_ASSERT(ph > 0 && ph < 14);
96 
97     m_ph = ph;
98   }
99 
100 
101   double
ph()102   PkaPhPi::ph()
103   {
104     return m_ph;
105   }
106 
107 
108   double
pi()109   PkaPhPi::pi()
110   {
111     return m_pi;
112   }
113 
114 
115   double
positiveCharges()116   PkaPhPi::positiveCharges()
117   {
118     return m_positiveCharges;
119   }
120 
121 
122   double
negativeCharges()123   PkaPhPi::negativeCharges()
124   {
125     return m_negativeCharges;
126   }
127 
128 
129   void
setCalcOptions(const CalcOptions & calcOptions)130   PkaPhPi::setCalcOptions(const CalcOptions &calcOptions)
131   {
132     m_calcOptions = calcOptions;
133   }
134 
135 
136   void
setMonomerList(QList<Monomer * > * monomerList)137   PkaPhPi::setMonomerList(QList<Monomer *> *monomerList)
138   {
139     Q_ASSERT(monomerList);
140 
141     mpa_monomerList = monomerList;
142   }
143 
144 
145   void
setModifList(QList<Modif * > * modifList)146   PkaPhPi::setModifList(QList<Modif *> *modifList)
147   {
148     Q_ASSERT(modifList);
149 
150     mpa_modifList = modifList;
151   }
152 
153 
154   int
calculateCharges()155   PkaPhPi::calculateCharges()
156   {
157     int processedChemicalGroups = 0;
158 
159     m_positiveCharges = 0;
160     m_negativeCharges = 0;
161 
162     // We of course need monomers ! Instead, we may not need modifs.
163     if (!mpa_monomerList)
164       return -1;
165 
166     int polymerSize = m_polymer.size();
167 
168     // The general scheme is :
169 
170     // Get the list of the coordinates of the different region
171     // selections. For each first monomer and end monomer of a given
172     // region selection, check if the the region is an oligomer or a
173     // residual chain(m_selectionType of CalcOptions); act
174     // accordingly. Also, check for each selection region if it
175     // encompasses the polymer left/right end. If the left/right end
176     // modifications are to be taken into account, act accordingly.
177 
178     const CoordinateList &coordinateList = m_calcOptions.coordinateList();
179 
180     for (int iter = 0; iter < coordinateList.size(); ++iter)
181       {
182 	Coordinates *coordinates = coordinateList.at(iter);
183 
184 	int startIndex = coordinates->start();
185 	int endIndex = coordinates->end();
186 
187 	bool leftMostCoordinates =
188 	  coordinateList.isLeftMostCoordinates(coordinates);
189 	bool rightMostCoordinates =
190 	  coordinateList.isRightMostCoordinates(coordinates);
191 
192 	for(int jter = startIndex; jter < endIndex + 1; ++jter)
193 	  {
194 	    const Monomer *seqMonomer = m_polymer.at(jter);
195 
196 // 	    qDebug() << __FILE__ << __LINE__
197 // 		      << "-- Monomer:" << seqMonomer->name()
198 // 		      << "position:" << jter + 1;
199 
200 	    // Find a monomer by the same code in our list of monomers
201 	    // that have been fed with chemical group data. Note that
202 	    // all the monomers in a given sequence must not
203 	    // necessarily have a counterpart in the local list of
204 	    // monoemers. For example, there might be cases in which a
205 	    // given monomer might not bring any charge whatsoever.
206 
207 	    int index = Monomer::isCodeInList(seqMonomer->code(),
208 					       *mpa_monomerList);
209 	    if (index == -1)
210 	      return -1;
211 
212 	    const Monomer *monomer = mpa_monomerList->at(index);
213 	    Q_ASSERT(monomer);
214 
215 	    // A monomer can have multiple such "CHEMICAL_GROUP"
216 	    // properties. Indeed, for example for proteins, a monomer
217 	    // might have three such chemical groups(and thus three
218 	    // Prop objects): one for the alpha NH2, one for the alpha
219 	    // COOH and one for a residual chain chemical group, like
220 	    // epsilon NH2 for lysine.
221 
222 	    for (int kter = 0; kter < monomer->propList().size(); ++kter)
223 	      {
224 		Prop *prop = monomer->propList().at(kter);
225 
226 		if(prop->name() != "CHEMICAL_GROUP")
227 		  continue;
228 
229 // 		qDebug() << __FILE__ << __LINE__
230 // 			  << "Monomer has property CHEMICAL_GROUP...";
231 
232 		// Get the chemical group out of the property.
233 
234 		ChemicalGroup *chemicalGroup =
235 		  static_cast<ChemicalGroup *>(prop->data());
236 
237 		if(chemicalGroup->polRule() & MXP_CHEMGROUP_LEFT_TRAPPED)
238 		  {
239 // 		    qDebug() << __FILE__ << __LINE__
240 // 			      << "... that is MXP_CHEMGROUP_LEFT_TRAPPED";
241 
242 		    // The chemical group we are dealing with is trapped
243 		    // when the monomer is polymerized on the left end, that
244 		    // is if the monomer is not the left end monomer of the
245 		    // sequence being analyzed.
246 
247 		    // Thus we only can take it into account if one of
248 		    // two conditions are met:
249 
250 		    // 1. The monomer is the left end monomer of the
251 		    // whole polymer sequence.
252 
253 		    // 2. The monomer is the left end monomer of the
254 		    // region selection AND the selection type is
255 		    // oligomers(thus it does not get polymerized to
256 		    // the previous selection region).
257 
258 		    if (jter > 0)
259 		      {
260 			// Clearly we are not dealing with the left
261 			// end of the polymer, so check if we have to
262 			// account for this chemical group or not.
263 
264 			if(!leftMostCoordinates)
265 			  {
266 			    // The current Coordinates is not the
267 			    // left-most Coordinates in the
268 			    // CoordinateList, thus we cannot consider
269 			    // it to be the "left end coordinates" of
270 			    // the CoordinateList. Just continue
271 			    // without exploring any more.
272 			    continue;
273 			  }
274 			if(jter == startIndex)
275 			  {
276 			    // The current monomer is the first
277 			    // monomer of Coordinates. We only take
278 			    // into account the chemical group if each
279 			    // Coordinates is to be considered an
280 			    // oligomer.
281 
282 			    if (m_calcOptions.selectionType() !=
283 				SELECTION_TYPE_OLIGOMERS)
284 			      continue;
285 			  }
286 		      }
287 		  }
288 
289 		if(chemicalGroup->polRule() & MXP_CHEMGROUP_RIGHT_TRAPPED)
290 		  {
291 // 		    qDebug() << __FILE__ << __LINE__
292 // 			      << "... that is MXP_CHEMGROUP_RIGHT_TRAPPED";
293 
294 		    // See explanations above.
295 
296 		    if (jter < polymerSize - 1)
297 		      {
298 			// Clearly, we are not dealing with the right
299 			// end of the polymer.
300 
301 			if(!rightMostCoordinates)
302 			  {
303 			    // The current Coordinates is not the
304 			    // right-most Coordinates of the
305 			    // CoordinateList, thus we cannot consider
306 			    // it to be the "right end coordinates" of
307 			    // the CoordinateList. Just continue
308 			    // without exploring anymore.
309 			    continue;
310 			  }
311 			if(jter == endIndex)
312 			  {
313 			    // The current monomer is the last monomer
314 			    // of Coordinates. We only take into
315 			    // account the chemical group if each
316 			    // Coordinates is to be considered an
317 			    // oligomer(and not a residual chain).
318 
319 			    if (m_calcOptions.selectionType() !=
320 				SELECTION_TYPE_OLIGOMERS)
321 			      continue;
322 			  }
323 		      }
324 		  }
325 
326 		if(iter == 0 &&
327 		    m_calcOptions.polymerEntities() &
328 		    MXT_POLYMER_CHEMENT_LEFT_END_MODIF)
329 		  {
330 		    // We are iterating in the monomer that is at the
331 		    // beginning of the polymer sequence. We should
332 		    // test if the chemical group we are dealing with
333 		    // right now has a special rule for the left end
334 		    // of the polymer sequence region.
335 
336 		    int ret =
337 		      accountPolymerEndModif(MXT_POLYMER_CHEMENT_LEFT_END_MODIF,
338 					      *chemicalGroup);
339 		    if (ret >= 0)
340 		      {
341 // 			qDebug() << __FILE__ << __LINE__
342 // 				  << "Accounted for left end modif.";
343 
344 			processedChemicalGroups += ret;
345 			continue;
346 		      }
347 		  }
348 
349 		if(iter == polymerSize -1 &&
350 		    m_calcOptions.polymerEntities() &
351 		    MXT_POLYMER_CHEMENT_RIGHT_END_MODIF)
352 		  {
353 		    int ret =
354 		      accountPolymerEndModif(MXT_POLYMER_CHEMENT_RIGHT_END_MODIF,
355 					      *chemicalGroup);
356 		    if (ret >= 0)
357 		      {
358 // 			qDebug() << __FILE__ << __LINE__
359 // 				  << "Accounted for right end modif.";
360 
361 			processedChemicalGroups += ret;
362 			continue;
363 		      }
364 		  }
365 
366 		if(m_calcOptions.monomerEntities() &
367 		    MXT_MONOMER_CHEMENT_MODIF &&
368 		    seqMonomer->isModified())
369 		  {
370 		    int ret = accountMonomerModif(*seqMonomer, *chemicalGroup);
371 		    if (ret >= 0)
372 		      {
373 // 			qDebug() << __FILE__ << __LINE__
374 // 				  << "Accounted for monomer modif.";
375 
376 			processedChemicalGroups += ret;
377 			continue;
378 		      }
379 		  }
380 
381 		double charge =
382 		  calculateChargeRatio(chemicalGroup->pka(),
383 					chemicalGroup->isAcidCharged());
384 
385 // 		qDebug() << __FILE__ << __LINE__
386 // 			  << "Charge:" << charge;
387 
388 		if(charge < 0)
389 		  m_negativeCharges += charge;
390 		else if (charge > 0)
391 		  m_positiveCharges += charge;
392 
393 // 		qDebug() << __FILE__ << __LINE__
394 // 			  << "Pos =" << m_positiveCharges
395 // 			  << "Neg = " << m_negativeCharges;
396 
397 		++processedChemicalGroups;
398 	      }
399 	    // End of
400 	    // for (int kter = 0; kter < monomer->propList().size(); ++kter)
401 
402 // 	    qDebug() << __FILE__ << __LINE__
403 // 		      << "End dealing with Monomer:" << seqMonomer->name()
404 // 		      << "position:" << jter + 1;
405 	  }
406 	// End of
407 	// for (int jter = startIndex; jter < endIndex + 1; ++jter)
408 
409 // 	qDebug() << __FILE__ << __LINE__
410 // 		  << "End dealing with Coordinates";
411       }
412     // End of
413     // for (int iter = 0; iter < coordinateList.size(); ++iter)
414 
415     // We have finished processing all the Coordinates in the list.
416 
417     return processedChemicalGroups;
418   }
419 
420 
421   int
calculatePi()422   PkaPhPi::calculatePi()
423   {
424     int processedChemicalGroups = 0;
425     int iteration = 0;
426 
427     double netCharge = 0;
428     double firstCharge = 0;
429     double thirdCharge = 0;
430 
431     // We of course need monomers ! Instead, we may not need modifs.
432     if (!mpa_monomerList)
433       {
434 	m_pi = 0;
435 	m_ph= 0;
436 
437 	return -1;
438       }
439 
440     m_ph = 0;
441 
442     while(true)
443       {
444 	//       qDebug() << "Current pH being tested:" << m_ph;
445 
446 	processedChemicalGroups = calculateCharges();
447 
448 	if(processedChemicalGroups == -1)
449 	  {
450 	    qDebug() << "Failed to calculate net charge for pH" << m_ph;
451 
452 	    m_pi = 0;
453 	    m_ph= 0;
454 
455 	    return -1;
456 	  }
457 
458 	netCharge = m_positiveCharges + m_negativeCharges;
459 
460 	// Note that if the 0.01 tested_ph step is enough to switch the
461 	// net charge from one excess value to another excess value in
462 	// the opposite direction, we'll enter an infinite loop.
463 	//
464 	// The evidence for such loop is that every other two measures,
465 	// the net charge of the polymer sequence will be the same.
466 	//
467 	// Here we test this so that we can break the loop.
468 
469 
470 	++iteration;
471 
472 	if(iteration == 1)
473 	  {
474 	    firstCharge = netCharge;
475 	  }
476 	else if (iteration == 3)
477 	  {
478 	    thirdCharge = netCharge;
479 
480 	    if (firstCharge == thirdCharge)
481 	      break;
482 
483 	    iteration = 0;
484 
485 	    firstCharge = netCharge;
486 	  }
487 
488 	// At this point we have to test the net charge:
489 
490 	if(netCharge >= -0.1 && netCharge <= 0.1)
491 	  {
492 	    // 	  qDebug() << "Breaking loop with netCharge:" << netCharge;
493 
494 	    break;
495 	  }
496 
497 	if(netCharge > 0)
498 	  {
499 	    // The test ph is too low.
500 
501 	    m_ph += 0.01;
502 	    // 	  qDebug() << "Set new pH m_ph += 0.01:" << m_ph
503 	    // 		    << "netCharge:" << netCharge;
504 
505 	    continue;
506 	  }
507 
508 	if(netCharge < 0)
509 	  {
510 	    // The test ph is too high.
511 
512 	    m_ph -= 0.01;
513 	    // 	  qDebug() << "Set new pH m_ph -= 0.01:" << m_ph
514 	    // 		    << "netCharge:" << netCharge;
515 
516 	    continue;
517 	  }
518       }
519     // End of
520     // while(true)
521 
522     // At this point m_pi is m_ph.
523 
524     m_pi = m_ph;
525     //   qDebug() << "pi is:" << m_pi;
526 
527 
528     return processedChemicalGroups;
529   }
530 
531 
532   double
calculateChargeRatio(double pka,bool acidCharged)533   PkaPhPi::calculateChargeRatio(double pka, bool acidCharged)
534   {
535     double aOverAh = 0;
536 
537     if (pka < 0)
538       return 0;
539     if (pka > 14)
540       return 0;
541 
542     if (m_ph < 0)
543       return 0;
544     if (m_ph > 14)
545       return 0;
546 
547 
548     // Example with pKa = 4.25(Glu) ; pH = 4.16, thus we are more
549     // acidic than pKa, we expect AH to be greater than A by a small
550     // margin.
551 
552     aOverAh =(double) pow(10,(m_ph - pka));
553     // aOverAh =  0.81283051616409951(confirmed manually)
554 
555     if (aOverAh < 1)
556       {
557 	/* The solution contains more acid forms(AH) than basic forms
558 	  (A).
559 	*/
560 	if(acidCharged)
561 	  return(1 - aOverAh);
562 	else
563 	  // The acid is not charged, that is, it is a COOH.
564 	  // AH = 1 - A
565 	  // A = aOverAh.AH
566 	  // A = aOverAh.(1-A)
567 	  // A = aOverAh - aOverAh.A
568 	  // A(1+aOverAh) = aOverAh
569 	  // A = aOverAh /(1+aOverAh), for us this is
570 	  // A = 0.81283 / 1.81283 = 0.448
571 
572 	  // And not - aOverAh, that is - aOverAh.
573 
574 	  // Below seems faulty(20071204)
575 	  // return(- aOverAh);
576 
577 	  // Tentative correction(20071204)
578 	  return(-(aOverAh /(1 + aOverAh)));
579       }
580 
581     else if (aOverAh > 1)
582       {
583 	/* The solution contains more basic forms(A) than acid forms
584 	  (AH).
585 	*/
586 	if(acidCharged)
587 	  return(1 / aOverAh);
588 	else
589 	  return(-(1 -(1 / aOverAh)));
590       }
591     else if (aOverAh == 1)
592       {
593 	/* The solution contains as many acid forms(AH) as basic forms
594 	  (H).
595 	*/
596 	if(acidCharged)
597 	  return(aOverAh / 2);
598 	else
599 	  return(- aOverAh / 2);
600       }
601     else
602       Q_ASSERT(0);
603 
604     return 0;
605   }
606 
607 
608   int
accountPolymerEndModif(int endModif,const ChemicalGroup & group)609   PkaPhPi::accountPolymerEndModif(int endModif,
610 				   const ChemicalGroup &group)
611   {
612     QString modifName;
613     ChemicalGroupRule *rule = 0;
614 
615     int count = 0;
616 
617     // Get the name of the modification of the polymer(if any) and get
618     // the rule dealing with that polymer modification(if any).
619 
620     if (endModif == MXT_POLYMER_CHEMENT_LEFT_END_MODIF)
621       {
622 	modifName = m_polymer.leftEndModif().name();
623 
624 	rule = group.findRule("LE_PLM_MODIF", modifName);
625 
626 	// Remember a chemical group is defined like this:
627 
628 	//       <mnmchemgroup>
629 	//         <name>N-term NH2</name>
630 	// 	<pka>9.6</pka>
631 	// 	<acidcharged>TRUE</acidcharged>
632 	// 	<polrule>left_trapped</polrule>
633 	// 	<chemgrouprule>
634 	// 	  <entity>LE_PLM_MODIF</entity>
635 	// 	  <name>Acetylation</name>
636 	// 	  <outcome>LOST</outcome>
637 	// 	</chemgrouprule>
638 	//       </mnmchemgroup>
639 
640       }
641     else if (endModif == MXT_POLYMER_CHEMENT_RIGHT_END_MODIF)
642       {
643 	modifName = m_polymer.rightEndModif().name();
644 
645 	rule = group.findRule("RE_PLM_MODIF", modifName);
646       }
647     else
648       Q_ASSERT(0);
649 
650 
651     // The polymer might not be modified, and also the chemical group
652     // passed as parameter might not contain any rule about any polymer
653     // modification. In that case we just have nothing to do.
654 
655     if (modifName.isEmpty())
656       {
657 	if(rule)
658 	  {
659 	    double charge = calculateChargeRatio(group.pka(),
660 						  group.isAcidCharged());
661 	    if (charge < 0)
662 	      m_negativeCharges += charge;
663 	    else if (charge > 0)
664 	      m_positiveCharges += charge;
665 
666 	    return ++count;
667 	  }
668 	else
669 	  {
670 	    // The polymer end was NOT modified and the chemical group
671 	    // was NOT eligible for a polymer end modification. This
672 	    // means that we do not have to process it, and we return -1
673 	    // so that the caller function knows we did not do anything
674 	    // and that this chemical group should continue to undergo
675 	    // analysis without skipping it.
676 
677 	    return -1;
678 	  }
679       }
680     // End of
681     // if (modifName.isEmpty())
682 
683     if (!rule)
684       {
685 	// This chemical group was not "designed" to receive any polymer
686 	// end modification, so we have nothing to do with it and we
687 	// return -1 so that the caller function knows we did not do
688 	// anything and that this chemical group should continue to
689 	// undergo analysis without skipping it.
690 
691 	return -1;
692       }
693 
694     // At this point we know that the chemical group 'group' we are
695     // analyzing is eligible for a polymer left end modification and
696     // that it is indeed modified with a correcct modification. So we
697     // have a rule for it. Let's continue the analysis.
698 
699     // Apparently the rule has data matching the ones we are looking
700     // for. At this point we should now what action to take for this
701     // group.
702 
703     if (rule->outcome() == MXP_CHEMGROUP_RULE_LOST)
704       {
705 	// We do not use the current chemical group 'group' because the
706 	// polymer end's modification has abolished it.
707       }
708     else if (rule->outcome() == MXP_CHEMGROUP_RULE_PRESERVED)
709       {
710 	double charge = calculateChargeRatio(group.pka(),
711 					      group.isAcidCharged());
712 	if(charge < 0)
713 	  m_negativeCharges += charge;
714 	else if (charge > 0)
715 	  m_positiveCharges += charge;
716 
717 	return ++count;
718       }
719     else
720       Q_ASSERT(0);
721 
722     // Whatever we should do with the left/right end monomer's chemgroup,
723     // we should take into account the modification itself that might
724     // have brought chemgroup(s) worth calculating their intrinsic
725     // charges!
726 
727     //  Find a modif object in the local list of modif objects, that has
728     // the same name as the modification with which the left/right end
729     // of the polymer is modified. We'll see what chemgroup(s) this
730     // modification brings to the polymer sequence.
731 
732     int index = Modif::isNameInList(modifName, *mpa_modifList);
733 
734     if (index == -1)
735       {
736 	//       qDebug() << __FILE__ << __LINE__
737 	// 		<< "Information: following modif not in local list:"
738 	// 		<< modifName;
739 
740 	return count;
741       }
742 
743     const Modif *modif = mpa_modifList->at(index);
744     Q_ASSERT(modif);
745 
746     for (int jter = 0; jter < modif->propList().size(); ++jter)
747       {
748 	Prop *prop = modif->propList().at(jter);
749 
750 	if(prop->name() != "CHEMICAL_GROUP")
751 	  continue;
752 
753 	// Get the chemical group out of the property.
754 
755 	const ChemicalGroup *chemicalGroup =
756 	  static_cast<const ChemicalGroup *>(prop->data());
757 
758 	double charge =
759 	  calculateChargeRatio(chemicalGroup->pka(),
760 				chemicalGroup->isAcidCharged());
761 	if(charge < 0)
762 	  m_negativeCharges += charge;
763 	else if (charge > 0)
764 	  m_positiveCharges += charge;
765 
766 	++count;
767       }
768 
769     return count;
770   }
771 
772 
773   int
accountMonomerModif(const Monomer & monomer,ChemicalGroup & group)774   PkaPhPi::accountMonomerModif(const Monomer &monomer,
775 				ChemicalGroup &group)
776   {
777     QString modifName;
778     ChemicalGroupRule *rule = 0;
779 
780     int count = 0;
781 
782     // For each modification in the monomer, make the accounting work.
783 
784     Q_ASSERT(mpa_modifList);
785     Q_ASSERT(mpa_modifList->size());
786 
787     for (int iter = 0; iter < monomer.modifList()->size(); ++iter)
788       {
789 	Modif *iterModif = monomer.modifList()->at(iter);
790 
791 	// Get the name of the modification of the monomer(if any) and get
792 	// the rule dealing with that monomer modification(if any).
793 
794 	modifName = iterModif->name();
795 
796 	rule = group.findRule("MONOMER_MODIF", modifName);
797 
798 	if(modifName.isEmpty())
799 	  {
800 	    // The monomer does not seem to be modified. However, we still
801 	    // have to make sure that the chemgroup that we were parsing is
802 	    // actually a chemgroup suitable for a modif.  If this chemgroup
803 	    // was actually suitable for a monomer modif, but it is not
804 	    // effectively modified, that means that we have to calculate
805 	    // the charge for the non-modified chemgroup...
806 
807 	    if (rule)
808 	      {
809 		double charge = calculateChargeRatio(group.pka(),
810 						      group.isAcidCharged());
811 		if(charge < 0)
812 		  m_negativeCharges += charge;
813 		else if (charge > 0)
814 		  m_positiveCharges += charge;
815 
816 		return ++count;
817 	      }
818 	    else
819 	      {
820 		// The current monomer was NOT modified, and the chemgroup
821 		// was NOT eligible for a monomer modification. This means
822 		// that we do not have to process it, and we return -1 so
823 		// that the caller function knows we did not do anything and
824 		// that this chemgroup should continue to undergo analysis
825 		// without skipping it.
826 
827 		return -1;
828 	      }
829 	  }
830 	// End of
831 	// if (modifName.isEmpty())
832 
833 	if(!rule)
834 	  {
835 	    // This chemgroup was not "designed" to receive any
836 	    // modification, so we have nothing to do with it, and we return
837 	    // -1 to let the caller know that its processing should be
838 	    // continued in the caller's function space.
839 
840 	    return -1;
841 	  }
842 
843 	// At this point, we know that the chemgroup we are analyzing is
844 	// eligible for a modification and that we have a rule for it. Let's
845 	// continue the analysis:
846 
847 	// Apparently, a rule object has member data matching the ones we
848 	// were looking for. At this point we should know what action to
849 	// take for this chemgroup.
850 
851 	if(rule->outcome() == MXP_CHEMGROUP_RULE_LOST)
852 	  {
853 	    // We do not use the current chemical group 'group' because the
854 	    // monomer modification has abolished it.
855 	  }
856 	else if (rule->outcome() == MXP_CHEMGROUP_RULE_PRESERVED)
857 	  {
858 	    double charge = calculateChargeRatio(group.pka(),
859 						  group.isAcidCharged());
860 	    if (charge < 0)
861 	      m_negativeCharges += charge;
862 	    else if (charge > 0)
863 	      m_positiveCharges += charge;
864 
865 	    return ++count;
866 	  }
867 	else
868 	  Q_ASSERT(0);
869 
870 	// Whatever we should do with this monomer's chemgroup, we should
871 	// take into account the modification itself that might have brought
872 	// chemgroup(s) worth calculating their intrinsic charges!
873 
874 	// Find a modif object in the local list of modif objects, that has
875 	// the same name as the modification with which the monomer is
876 	// modified. We'll see what chemgroup(s) this modification brings to
877 	// the polymer sequence.
878 
879 	int index = Modif::isNameInList(modifName, *mpa_modifList);
880 
881 	if(index == -1)
882 	  {
883 	    //       qDebug() << __FILE__ << __LINE__
884 	    // 		<< "Information: following modif not in local list:"
885 	    // 		<< modifName;
886 
887 	    return count;
888 	  }
889 
890 	Modif *modif = mpa_modifList->at(index);
891 	Q_ASSERT(modif);
892 
893 	for(int jter = 0; jter < modif->propList().size(); ++jter)
894 	  {
895 	    Prop *prop = modif->propList().at(jter);
896 
897 	    if (prop->name() != "CHEMICAL_GROUP")
898 	      continue;
899 
900 	    // Get the chemical group out of the property.
901 
902 	    const ChemicalGroup *chemicalGroup =
903 	      static_cast<const ChemicalGroup *>(prop->data());
904 
905 	    double charge =
906 	      calculateChargeRatio(chemicalGroup->pka(),
907 				    chemicalGroup->isAcidCharged());
908 	    if (charge < 0)
909 	      m_negativeCharges += charge;
910 	    else if (charge > 0)
911 	      m_positiveCharges += charge;
912 
913 	    ++count;
914 	  }
915       }
916 
917     return count;
918   }
919 
920 } // namespace massXpert
921