corems.molecular_formula.factory.MolecularFormulaFactory
1import re 2 3from corems.encapsulation.constant import Atoms, Labels 4from corems.molecular_formula.calc.MolecularFormulaCalc import MolecularFormulaCalc 5 6__author__ = "Yuri E. Corilo" 7__date__ = "Jun 24, 2019" 8 9 10class MolecularFormulaBase(MolecularFormulaCalc): 11 """Base class for representing a molecular formula. 12 13 Parameters 14 ---------- 15 molecular_formula : dict, list, str 16 The molecular formula. 17 ion_charge : int 18 The ion charge. 19 ion_type : str, optional 20 The ion type. Defaults to None. 21 adduct_atom : str, optional 22 The adduct atom. Defaults to None. 23 mspeak_parent : _MSPeak, optional 24 The parent mass spectrum peak object instance. Defaults to None. 25 external_mz : float, optional 26 The external m/z value. Defaults to None. 27 28 Raises 29 ------ 30 TypeError 31 If the ion type is not 'DE_OR_PROTONATED', 'RADICAL' or 'ADDUCT'. 32 33 Attributes 34 ---------- 35 isotopologue_count_percentile : float 36 The isotopologue count percentile. 37 O_C : float 38 The O/C ratio. 39 H_C : float 40 The H/C ratio. 41 dbe : float 42 The double bond equivalent. 43 mz_nominal_calc : int 44 The nominal m/z value. 45 mz_error : float 46 The m/z error. 47 mz_calc : float 48 The m/z value. 49 protonated_mz : float 50 The protonated or deprotonated m/z value. 51 radical_mz : float 52 The radical m/z value. 53 neutral_mass : float 54 The neutral mass. 55 ion_type : str 56 The ion type. 57 ion_charge : int 58 The ion charge. 59 atoms : list 60 The atoms in the molecular formula. 61 confidence_score : float 62 Composite assignment confidence score in about [0, 1] (higher is 63 better). Weighted sum of ``average_mz_error_score`` and 64 ``isotopologue_similarity`` using 65 ``mz_error_score_weight`` / ``isotopologue_score_weight`` from the 66 parent spectrum's molecular search settings. See the 67 ``corems.molecular_formula`` package documentation for the full 68 formulation and literature reference. 69 isotopologue_similarity : float 70 Similarity between expected and observed isotopologue abundance 71 patterns (about [0, 1]). Zero when no isotopologues are expected or 72 none were computed during search. 73 average_mz_error_score : float 74 Mean Gaussian mass-accuracy score over the monoisotopic peak and 75 expected isotopologue peaks. Missing expected isotopologues 76 contribute 0. 77 mz_error_score : float 78 Gaussian mass-accuracy score for this formula on its parent peak 79 only (ppm error vs ``predicted_std`` or a 1.66 ppm fallback). 80 kmd : float 81 The Kendrick mass defect (KMD). 82 kendrick_mass : float 83 The Kendrick mass. 84 knm : float 85 The nominal Kendrick mass. 86 string : str 87 The molecular formula string. 88 string_formated : str 89 The molecular formula string formated with subscripts and superscripts. 90 class_label : str 91 The class label. 92 class_dict : dict 93 The class dictionary. 94 95 Methods 96 ------- 97 * change_kendrick_base(kendrick_dict_base). 98 Change the Kendrick base. 99 * isotopologues(min_abundance, current_mono_abundance, dynamic_range). 100 Calculate the isotopologues. 101 * atoms_qnt(atom). 102 Get the atom quantity. 103 * atoms_symbol(atom). 104 Get the atom symbol without the mass number. 105 * to_dict(). 106 Get the molecular formula as a dictionary. 107 * to_list(). 108 Get the molecular formula as a list. 109 """ 110 111 def __init__( 112 self, 113 molecular_formula, 114 ion_charge, 115 ion_type=None, 116 adduct_atom=None, 117 mspeak_parent=None, 118 external_mz=None, 119 ): 120 # clear dictionary of atoms with 0 value 121 if type(molecular_formula) is dict: 122 self._from_dict(molecular_formula, ion_type, adduct_atom) 123 124 elif type(molecular_formula) is list: 125 self._from_list(molecular_formula, ion_type, adduct_atom) 126 127 elif type(molecular_formula) is str: 128 self._from_str(molecular_formula, ion_type, adduct_atom) 129 130 self._ion_charge = ion_charge 131 self._external_mz = external_mz 132 self._confidence_score = None 133 self._isotopologue_similarity = None 134 self._mz_error_score = None 135 self._mass_error_average_score = None 136 137 self.is_isotopologue = False 138 139 # parent mass spectrum peak obj instance 140 self._mspeak_parent = mspeak_parent 141 142 self.expected_isotopologues = [] 143 self.mspeak_mf_isotopologues_indexes = [] 144 145 if self._mspeak_parent: 146 kendrick_dict_base = ( 147 self._mspeak_parent._ms_parent.mspeaks_settings.kendrick_base 148 ) 149 else: 150 kendrick_dict_base = {"C": 1, "H": 2} 151 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 152 kendrick_dict_base 153 ) 154 155 def __repr__(self): 156 return "MolecularFormula({0},{1},ion type = {2}".format( 157 self._d_molecular_formula, self.ion_charge, self.ion_type 158 ) 159 160 def __str__(self): 161 return "MolecularFormula {0}, ion_charge:{1}, ion type:{2}, m/z:{3} ".format( 162 self.string, self.ion_charge, self.ion_type, self.mz_calc 163 ) 164 165 def __len__(self): 166 # crash if keys are not ordered 167 return len(self._d_molecular_formula.keys()) 168 169 def __getitem__(self, atom): 170 # atom = list(self._d_molecular_formula.keys())[position] 171 if atom in self._d_molecular_formula.keys(): 172 return self._d_molecular_formula[atom] 173 else: 174 return 0 175 176 def get(self, atom): 177 """Get the atom quantity of a specific atom. 178 179 Parameters 180 ---------- 181 atom : str 182 The atom symbol. 183 184 Returns 185 ------- 186 int 187 The atom quantity. 188 """ 189 # atom = list(self._d_molecular_formula.keys())[position] 190 if atom in self._d_molecular_formula.keys(): 191 return self._d_molecular_formula[atom] 192 else: 193 return 0 194 195 def _from_dict(self, molecular_formula, ion_type, adduct_atom): 196 self._d_molecular_formula = { 197 key: val for key, val in molecular_formula.items() if val != 0 198 } 199 200 if ion_type is not None: 201 self._d_molecular_formula[Labels.ion_type] = ion_type 202 203 if adduct_atom: 204 if adduct_atom in self._d_molecular_formula: 205 self._d_molecular_formula[adduct_atom] += 1 206 else: 207 self._d_molecular_formula[adduct_atom] = 1 208 self.adduct_atom = adduct_atom 209 210 def _from_list(self, molecular_formula_list, ion_type, adduct_atom): 211 # list has to be in the format 212 # ['C', 10, 'H', 21, '13C', 1, 'Cl', 1, etc] 213 self._d_molecular_formula = {} 214 for each in range(0, len(molecular_formula_list), 2): 215 atoms_label = molecular_formula_list[each] 216 atoms_count = int(molecular_formula_list[each + 1]) 217 218 if atoms_count > 0: 219 self._d_molecular_formula[atoms_label] = int(atoms_count) 220 221 self._d_molecular_formula[Labels.ion_type] = ion_type 222 if adduct_atom: 223 self.adduct_atom = adduct_atom 224 if adduct_atom in self._d_molecular_formula: 225 self._d_molecular_formula[adduct_atom] += 1 226 else: 227 self._d_molecular_formula[adduct_atom] = 1 228 else: 229 self.adduct_atom = None 230 231 def _from_str(self, molecular_formula_str, ion_type, adduct_atom): 232 # string has to be in the format 233 #'C10 H21 13C1 Cl1 37Cl1 etc' 234 # Check if there are spaces in the string 235 if " " not in molecular_formula_str: 236 raise ValueError( 237 "The molecular formula string should have spaces, input: %s" 238 % molecular_formula_str 239 ) 240 241 # Split the string by spaces 242 # Grab the text before a digit for each element after splitting on spaces (atoms) 243 elements = [re.sub(r"\d+$", "", x) for x in molecular_formula_str.split()] 244 # Grab the digits at the end of each element after splitting on spaces (counts) 245 counts = [re.findall(r"\d+$", x)[0] for x in molecular_formula_str.split()] 246 # Check that the number of elements and counts are the same 247 if len(elements) != len(counts): 248 raise ValueError( 249 "The number of elements and counts do not match, input: %s" 250 % molecular_formula_str 251 ) 252 253 # Create a dictionary from the elements and counts and add it to the molecular formula 254 dict_ = dict(zip(elements, counts)) 255 # Cast counts to integers 256 dict_ = {key: int(val) for key, val in dict_.items()} 257 self._from_dict(dict_, ion_type, adduct_atom) 258 259 def split(self, delimiters, string, maxsplit=0): # pragma: no cover 260 """Splits the molecular formula string. 261 262 Parameters 263 ---------- 264 delimiters : list 265 The list of delimiters. 266 string : str 267 The molecular formula string. 268 maxsplit : int, optional 269 The maximum number of splits. Defaults to 0. 270 271 Returns 272 ------- 273 list 274 The molecular formula list. 275 276 Notes 277 ----- 278 Does not work when formula has atoms with same characters in a row that below to different atoms, i.e. C10H21NNa. 279 """ 280 regexPattern = "|".join(map(re.escape, delimiters)) # pragma: no cover 281 isotopes = re.findall(regexPattern, string) # pragma: no cover 282 counts = re.split(regexPattern, string, maxsplit) # pragma: no cover 283 284 return [isotopes[0], int(counts[1])] 285 286 @property 287 def isotopologue_count_percentile( 288 self, 289 ): 290 if not len(self.expected_isotopologues) == 0: 291 return ( 292 len(self.mspeak_mf_isotopologues_indexes) 293 / len(self.expected_isotopologues) 294 ) * 100 295 else: 296 return 100 297 298 @property 299 def O_C(self): 300 if "O" in self._d_molecular_formula.keys(): 301 # gather all the Os and Hs, regardless of the isotopic composition 302 Os = sum( 303 [ 304 self._d_molecular_formula.get(key) 305 for key in ["O"] + Atoms.isotopes["O"][1] 306 if key in self._d_molecular_formula.keys() 307 ] 308 ) 309 Cs = sum( 310 [ 311 self._d_molecular_formula.get(key) 312 for key in ["C"] + Atoms.isotopes["C"][1] 313 if key in self._d_molecular_formula.keys() 314 ] 315 ) 316 return Os / Cs 317 else: 318 return 0 319 320 @property 321 def H_C(self): 322 # gather all the Cs and Hs, regardless of the isotopic composition 323 Cs = sum( 324 [ 325 self._d_molecular_formula.get(key) 326 for key in ["C"] + Atoms.isotopes["C"][1] 327 if key in self._d_molecular_formula.keys() 328 ] 329 ) 330 Hs = sum( 331 [ 332 self._d_molecular_formula.get(key) 333 for key in ["H"] + Atoms.isotopes["H"][1] 334 if key in self._d_molecular_formula.keys() 335 ] 336 ) 337 return Hs / Cs 338 339 @property 340 def A_I(self): 341 """Aromaticity index""" 342 return self._calc_aromaticity_index() 343 344 @property 345 def A_I_mod(self): 346 """Modified aromaticity index""" 347 return self._calc_aromaticity_index_mod() 348 349 @property 350 def nosc(self): 351 """Nominal oxidation state of carbon""" 352 return self._calc_nosc() 353 354 @property 355 def dbe(self): 356 return self._calc_dbe() 357 358 @property 359 def mz_nominal_calc(self): 360 return int(self._calc_mz()) 361 362 @property 363 def mz_error(self): 364 return self._calc_assignment_mass_error() 365 366 @property 367 def mz_calc(self): 368 return self._calc_mz() 369 370 @property 371 def protonated_mz(self): 372 return self._protonated_mz(self.ion_charge) 373 374 @property 375 def radical_mz(self): 376 return self._radical_mz(self.ion_charge) 377 378 @property 379 def neutral_mass(self): 380 return self._neutral_mass() 381 382 def adduct_mz(self, adduct_atom): 383 """Get m/z of an adducted ion version of the molecular formula. 384 385 Parameters 386 ---------- 387 adduct_atom : str 388 The adduct atom. 389 390 Returns 391 ------- 392 float 393 The m/z value of the adducted ion version of the molecular formula. 394 """ 395 return self._adduct_mz(adduct_atom, self.ion_charge) 396 397 @property 398 def ion_type(self): 399 ion_type = self._d_molecular_formula.get(Labels.ion_type) 400 if ion_type == Labels.protonated_de_ion: 401 if self.ion_charge > 0: 402 return Labels.protonated 403 else: 404 return Labels.de_protonated 405 else: 406 return ion_type 407 408 @ion_type.setter 409 def ion_type(self, ion_type): 410 if ion_type in [ 411 Labels.protonated_de_ion, 412 Labels.adduct_ion, 413 Labels.radical_ion, 414 ]: 415 self._d_molecular_formula[Labels.ion_type] = ion_type 416 else: 417 raise TypeError( 418 "Ion type can only be: 'DE_OR_PROTONATED', 'RADICAL' or 'ADDUCT', not %s" 419 % ion_type 420 ) 421 422 @property 423 def ion_charge(self): 424 return self._ion_charge 425 426 @property 427 def atoms(self): 428 """Get the atoms in the molecular formula.""" 429 # if there is an adduct_atom, them reduce it from the atoms list 430 if self.adduct_atom is None: 431 return [ 432 key 433 for key in self._d_molecular_formula.keys() 434 if key != Labels.ion_type 435 ] 436 else: 437 temp_dict = self._d_molecular_formula.copy() 438 temp_dict[self.adduct_atom] -= 1 439 return [ 440 key 441 for key, val in temp_dict.items() 442 if key != Labels.ion_type and val > 0 443 ] 444 445 @property 446 def confidence_score(self): 447 """Composite molecular formula assignment confidence score. 448 449 Combines formula-level mass accuracy and isotopologue pattern 450 agreement: 451 452 ``` 453 CS = (w_err * m_err) + (w_iso * m_iso) 454 ``` 455 456 where ``m_err`` is :attr:`average_mz_error_score`, ``m_iso`` is 457 :attr:`isotopologue_similarity`, and the weights are 458 ``molecular_search_settings.mz_error_score_weight`` (default 0.6) 459 and ``isotopologue_score_weight`` (default 0.4). 460 461 Returns 462 ------- 463 float 464 Score typically in about [0, 1]; higher values indicate better 465 agreement with mass error and (when available) isotopologue 466 evidence. Used for ranking when ``score_method`` is 467 ``\"prob_score\"``. 468 469 Notes 470 ----- 471 Requires an associated parent mass spectral peak. Isotopologue 472 terms are only informative when search was run with isotopologue 473 detection enabled. See the ``corems.molecular_formula`` package 474 documentation and Dewey et al., *Anal. Chem.* 2025, 97, 13031–13039 475 (https://doi.org/10.1021/acs.analchem.4c06826). 476 """ 477 if not self._confidence_score: 478 self._confidence_score = self._calc_confidence_score() 479 480 return self._confidence_score 481 482 @property 483 def isotopologue_similarity(self): 484 """Isotopologue abundance pattern similarity for this formula. 485 486 Compares expected natural-abundance isotopologue intensities 487 (from dynamic-range-limited expansion) to abundances of peaks 488 assigned as those partners. Implemented as a normalized Manhattan 489 distance mapped to a similarity in about [0, 1] 490 (higher is better). 491 492 Returns 493 ------- 494 float 495 Similarity score. Returns 0.0 when no isotopologues are 496 expected for this monoisotopic formula (or none were computed). 497 498 Notes 499 ----- 500 This is the ``m_iso`` term in :attr:`confidence_score`. See the 501 ``corems.molecular_formula`` package documentation for details. 502 """ 503 if not self._isotopologue_similarity: 504 self._isotopologue_similarity = self._calc_isotopologue_confidence() 505 506 return self._isotopologue_similarity 507 508 @property 509 def average_mz_error_score(self): 510 """Mean mass-accuracy score over mono and expected isotopologues. 511 512 Averages :attr:`mz_error_score` for the monoisotopic assignment and 513 for each expected isotopologue formula that has a parent peak. 514 Expected isotopologues without a matched peak contribute 0. 515 516 Returns 517 ------- 518 float 519 Mean Gaussian mass-error score in about [0, 1]. 520 521 Notes 522 ----- 523 This is the ``m_err`` term in :attr:`confidence_score`. 524 """ 525 # includes the isotopologues 526 527 if not self._mass_error_average_score: 528 self._mass_error_average_score = self._calc_average_mz_score() 529 530 return self._mass_error_average_score 531 532 @property 533 def mz_error_score(self): 534 """Gaussian mass-accuracy score for this formula on its parent peak. 535 536 Uses the assignment mass error ``delta`` (ppm) and a width ``sigma`` 537 equal to the parent peak's ``predicted_std`` when set, otherwise a 538 fallback of 1.66 ppm: 539 540 ``` 541 m = exp( -((delta - mu) ** 2) / (2 * (sigma ** 2)) ) 542 ``` 543 544 with mean ``mu = 0`` (calibrated spectrum with near-zero mean error 545 assumed). 546 547 Returns 548 ------- 549 float 550 Score typically in about 0 to 1; exact mass match is 1, 551 and larger ppm error yields values closer to 0. 552 553 Notes 554 ----- 555 Formula-level ranking uses :attr:`average_mz_error_score`, which 556 folds in expected isotopologue peaks as well. 557 """ 558 if not self._mz_error_score: 559 self._mz_error_score = self._calc_mz_confidence() 560 561 return self._mz_error_score 562 563 @property 564 def kmd(self): 565 return self._kmd 566 567 @property 568 def kendrick_mass(self): 569 return self._kendrick_mass 570 571 @property 572 def knm(self): 573 return self._nominal_km 574 575 def change_kendrick_base(self, kendrick_dict_base): 576 """Change the Kendrick base. 577 578 Parameters 579 ---------- 580 kendrick_dict_base : dict 581 The Kendrick base dictionary. Ex: {"C": 1, "H": 2} 582 """ 583 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 584 kendrick_dict_base 585 ) 586 587 def isotopologues(self, min_abundance, current_mono_abundance, dynamic_range): 588 """Calculate the isotopologues for a given molecular formula. 589 590 Parameters 591 ---------- 592 min_abundance : float 593 The minimum abundance. 594 current_mono_abundance : float 595 The current monoisotopic abundance. 596 dynamic_range : float 597 The dynamic range. 598 599 Yields 600 ------ 601 MolecularFormulaIsotopologue 602 The molecular formula isotopologue. 603 604 Notes 605 ----- 606 This calculation ignores the hydrogen isotopes. 607 """ 608 isotopologues = [] 609 for mf in self._cal_isotopologues( 610 self._d_molecular_formula, 611 min_abundance, 612 current_mono_abundance, 613 dynamic_range, 614 ): 615 isotopologues.append(mf) 616 617 # To account for differences in how the isotopologue outputs are sorted between IsoSpec versions. 618 sorted_isotopologues = sorted(isotopologues, key=lambda mf: mf[1], reverse=True) 619 620 for mf in sorted_isotopologues: 621 yield MolecularFormulaIsotopologue( 622 *mf, 623 current_mono_abundance, 624 self.ion_charge, 625 ion_type=self.ion_type, 626 adduct_atom=self.adduct_atom, 627 ) 628 629 def atoms_qnt(self, atom): 630 """Get the atom quantity of a specific atom in the molecular formula.""" 631 if atom in self._d_molecular_formula: 632 return self._d_molecular_formula.get(atom) 633 else: 634 raise Warning( 635 "Could not find %s in this Molecular Formula object" % str(atom) 636 ) 637 638 def atoms_symbol(self, atom): 639 """Get the atom symbol without the mass number.""" 640 return "".join([i for i in atom if not i.isdigit()]) 641 642 @property 643 def string(self): 644 """Returns the molecular formula as a string.""" 645 if self._d_molecular_formula: 646 if self.adduct_atom is None: 647 mol_form_dict = self._d_molecular_formula 648 else: 649 mol_form_dict = self._d_molecular_formula.copy() 650 if self.adduct_atom not in mol_form_dict.keys(): 651 raise Exception("Adduct atom not found in molecular formula dict") 652 mol_form_dict[self.adduct_atom] -= 1 653 mol_form_dict = { 654 key: val for key, val in mol_form_dict.items() if val != 0 655 } 656 formula_srt = "" 657 for atom in Atoms.atoms_order: 658 if atom in mol_form_dict.keys(): 659 formula_srt += atom + str(int(mol_form_dict.get(atom))) + " " 660 return formula_srt.strip() 661 662 else: 663 raise Exception("Molecular formula identification not performed yet") 664 665 @property 666 def string_formated(self): 667 SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") 668 SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹") 669 670 if self._d_molecular_formula: 671 formula_srt = "" 672 for atom in Atoms.atoms_order: 673 if atom in self.to_dict().keys(): 674 formula_srt += atom.translate(SUP) + str( 675 int(self.to_dict().get(atom)) 676 ).translate(SUB) 677 return formula_srt 678 679 else: 680 raise Exception("Molecular formula identification not performed yet") 681 682 def to_dict(self): 683 """Returns the molecular formula as a dictionary. 684 685 Returns 686 ------- 687 dict 688 The molecular formula as a dictionary. 689 """ 690 return self._d_molecular_formula 691 692 def to_list(self): 693 """Returns the molecular formula as a list. 694 695 Returns 696 ------- 697 list 698 The molecular formula as a list. 699 700 Raises 701 ------ 702 Exception 703 If the molecular formula identification was not performed yet. 704 """ 705 # TODO ensure self._d_molecular_formula is a orderedDict 706 707 if self._d_molecular_formula: 708 formula_list = [] 709 710 for atom, atom_number in self._d_molecular_formula.items(): 711 if atom != Labels.ion_type: 712 formula_list.append(atom) 713 formula_list.append(atom_number) 714 715 return formula_list 716 else: 717 raise Exception("Molecular formula identification not performed yet") 718 719 @property 720 def class_label(self): 721 if self._d_molecular_formula: 722 formulalist = self.to_list() 723 classstring = "" 724 725 for each in range(0, len(formulalist), 2): 726 if ( 727 formulalist[each] != "C" 728 and formulalist[each] != "H" 729 and formulalist[each] != "HC" 730 ): 731 classstring = ( 732 classstring 733 + str(formulalist[each]) 734 + str(formulalist[each + 1]) 735 + " " 736 ) 737 738 if classstring == "": 739 classstring = "HC" 740 741 classstring = classstring.strip() 742 743 if self._d_molecular_formula.get(Labels.ion_type) == Labels.radical_ion: 744 return classstring + " -R" 745 746 # elif self._d_molecular_formula.get(Labels.ion_type) == Labels.adduct_ion: 747 748 # return classstring + ' -A' 749 750 else: 751 return classstring 752 753 #'dict, tuple or string' 754 755 else: 756 raise Exception("Molecular formula identification not performed yet") 757 758 @property 759 def class_dict(self): 760 if self._d_molecular_formula: 761 class_dict = {} 762 763 for atom, qnt in self._d_molecular_formula.items(): 764 if atom != Labels.ion_type and atom != "C" and atom != "H": 765 class_dict[atom] = qnt 766 767 return class_dict 768 769 raise Exception("Molecular formula identification not performed yet") 770 771 772class MolecularFormulaIsotopologue(MolecularFormulaBase): 773 """Class for representing a molecular formula isotopologue. 774 775 Parameters 776 ---------- 777 _d_molecular_formula : dict 778 The molecular formula as a dictionary. 779 prob_ratio : float 780 The probability ratio. 781 mono_abundance : float 782 The monoisotopic abundance. 783 ion_charge : int 784 The ion charge. 785 mspeak_parent : object, optional 786 The parent mass spectrum peak object instance. Defaults to None. 787 ion_type : str, optional 788 The ion type. Defaults to None. 789 adduct_atom : str, optional 790 The adduct atom. Defaults to None. 791 792 Attributes 793 ---------- 794 prob_ratio : float 795 The probability ratio. 796 abundance_calc : float 797 The calculated abundance. 798 area_error : float 799 The area error. 800 abundance_error : float 801 The abundance error. 802 is_isotopologue : bool 803 The isotopologue flag. Defaults to True. 804 mspeak_index_mono_isotopic : int 805 The index of the monoisotopic peak in the mass spectrum peak list. Defaults to None. 806 mono_isotopic_formula_index : int 807 The index of the monoisotopic formula in the molecular formula list. Defaults to None. 808 """ 809 810 def __init__( 811 self, 812 _d_molecular_formula, 813 prob_ratio, 814 mono_abundance, 815 ion_charge, 816 mspeak_parent=None, 817 ion_type=None, 818 adduct_atom=None, 819 ): 820 if ion_type is None: 821 # check if ion type or adduct_atom is in the molecular formula dict 822 if Labels.ion_type in _d_molecular_formula: 823 ion_type = _d_molecular_formula.get(Labels.ion_type) 824 else: 825 ion_type = None 826 else: 827 ion_type = Labels.ion_type_translate.get(ion_type) 828 829 if ion_type == Labels.adduct_ion: 830 adduct_atom_int = None 831 if adduct_atom in _d_molecular_formula.keys(): 832 adduct_atom_int = adduct_atom 833 else: 834 # Check to see if adduct_atom should actually be an isotope of the adduct atom 835 for adduct_iso in Atoms.isotopes.get(adduct_atom)[1]: 836 if adduct_iso in _d_molecular_formula.keys(): 837 adduct_atom_int = adduct_iso 838 adduct_atom = adduct_atom_int 839 if adduct_atom is None: 840 raise Exception("adduct_atom is required for adduct ion") 841 _d_molecular_formula[adduct_atom] -= 1 842 _d_molecular_formula = { 843 key: val for key, val in _d_molecular_formula.items() if val != 0 844 } 845 846 super().__init__( 847 molecular_formula=_d_molecular_formula, 848 ion_charge=ion_charge, 849 ion_type=ion_type, 850 adduct_atom=adduct_atom, 851 ) 852 # prob_ratio is relative to the monoisotopic peak p_isotopologue/p_mono_isotopic 853 854 self.prob_ratio = prob_ratio 855 856 self.abundance_calc = mono_abundance * prob_ratio 857 858 self.is_isotopologue = True 859 860 self.mspeak_index_mono_isotopic = None 861 862 self.mono_isotopic_formula_index = None 863 # parent mass spectrum peak obj instance 864 self._mspeak_parent = mspeak_parent 865 866 @property 867 def area_error(self): 868 return self._calc_area_error() 869 870 @property 871 def abundance_error(self): 872 return self._calc_abundance_error() 873 874 875class LCMSLibRefMolecularFormula: 876 """Removed LC-MS library reference molecular formula type. 877 878 This class is no longer supported. Canonical LC-MS MS1 formula assignment 879 uses :class:`MolecularFormula` via combinatorial / SQL search. Construction 880 raises :class:`NotImplementedError`. The name is retained as a stub until 881 the next major release. 882 883 Parameters 884 ---------- 885 *args 886 Ignored; retained only for call-site compatibility. 887 **kwargs 888 Ignored; retained only for call-site compatibility. 889 """ 890 891 def __init__(self, *args, **kwargs) -> None: 892 raise NotImplementedError( 893 "LCMSLibRefMolecularFormula is no longer supported. " 894 "Use MolecularFormula for formula objects. " 895 "This stub will be removed in the next major release." 896 ) 897 898 899class MolecularFormula(MolecularFormulaBase): 900 """General class for representing a molecular formula. 901 902 Parameters 903 ---------- 904 molecular_formula : dict, list, str 905 The molecular formula. 906 ion_charge : int 907 The ion charge. 908 ion_type : str, optional 909 The ion type. Defaults to None. 910 adduct_atom : str, optional 911 The adduct atom. Defaults to None. 912 mspeak_parent : object, optional 913 The parent mass spectrum peak object instance. Defaults to None. 914 external_mz : float, optional 915 The external m/z value. Defaults to False. 916 """ 917 918 def __init__( 919 self, 920 molecular_formula, 921 ion_charge, 922 ion_type=None, 923 adduct_atom=None, 924 mspeak_parent=None, 925 external_mz=False, 926 ): 927 super().__init__( 928 molecular_formula, 929 ion_charge, 930 ion_type=ion_type, 931 adduct_atom=adduct_atom, 932 mspeak_parent=mspeak_parent, 933 external_mz=external_mz, 934 )
11class MolecularFormulaBase(MolecularFormulaCalc): 12 """Base class for representing a molecular formula. 13 14 Parameters 15 ---------- 16 molecular_formula : dict, list, str 17 The molecular formula. 18 ion_charge : int 19 The ion charge. 20 ion_type : str, optional 21 The ion type. Defaults to None. 22 adduct_atom : str, optional 23 The adduct atom. Defaults to None. 24 mspeak_parent : _MSPeak, optional 25 The parent mass spectrum peak object instance. Defaults to None. 26 external_mz : float, optional 27 The external m/z value. Defaults to None. 28 29 Raises 30 ------ 31 TypeError 32 If the ion type is not 'DE_OR_PROTONATED', 'RADICAL' or 'ADDUCT'. 33 34 Attributes 35 ---------- 36 isotopologue_count_percentile : float 37 The isotopologue count percentile. 38 O_C : float 39 The O/C ratio. 40 H_C : float 41 The H/C ratio. 42 dbe : float 43 The double bond equivalent. 44 mz_nominal_calc : int 45 The nominal m/z value. 46 mz_error : float 47 The m/z error. 48 mz_calc : float 49 The m/z value. 50 protonated_mz : float 51 The protonated or deprotonated m/z value. 52 radical_mz : float 53 The radical m/z value. 54 neutral_mass : float 55 The neutral mass. 56 ion_type : str 57 The ion type. 58 ion_charge : int 59 The ion charge. 60 atoms : list 61 The atoms in the molecular formula. 62 confidence_score : float 63 Composite assignment confidence score in about [0, 1] (higher is 64 better). Weighted sum of ``average_mz_error_score`` and 65 ``isotopologue_similarity`` using 66 ``mz_error_score_weight`` / ``isotopologue_score_weight`` from the 67 parent spectrum's molecular search settings. See the 68 ``corems.molecular_formula`` package documentation for the full 69 formulation and literature reference. 70 isotopologue_similarity : float 71 Similarity between expected and observed isotopologue abundance 72 patterns (about [0, 1]). Zero when no isotopologues are expected or 73 none were computed during search. 74 average_mz_error_score : float 75 Mean Gaussian mass-accuracy score over the monoisotopic peak and 76 expected isotopologue peaks. Missing expected isotopologues 77 contribute 0. 78 mz_error_score : float 79 Gaussian mass-accuracy score for this formula on its parent peak 80 only (ppm error vs ``predicted_std`` or a 1.66 ppm fallback). 81 kmd : float 82 The Kendrick mass defect (KMD). 83 kendrick_mass : float 84 The Kendrick mass. 85 knm : float 86 The nominal Kendrick mass. 87 string : str 88 The molecular formula string. 89 string_formated : str 90 The molecular formula string formated with subscripts and superscripts. 91 class_label : str 92 The class label. 93 class_dict : dict 94 The class dictionary. 95 96 Methods 97 ------- 98 * change_kendrick_base(kendrick_dict_base). 99 Change the Kendrick base. 100 * isotopologues(min_abundance, current_mono_abundance, dynamic_range). 101 Calculate the isotopologues. 102 * atoms_qnt(atom). 103 Get the atom quantity. 104 * atoms_symbol(atom). 105 Get the atom symbol without the mass number. 106 * to_dict(). 107 Get the molecular formula as a dictionary. 108 * to_list(). 109 Get the molecular formula as a list. 110 """ 111 112 def __init__( 113 self, 114 molecular_formula, 115 ion_charge, 116 ion_type=None, 117 adduct_atom=None, 118 mspeak_parent=None, 119 external_mz=None, 120 ): 121 # clear dictionary of atoms with 0 value 122 if type(molecular_formula) is dict: 123 self._from_dict(molecular_formula, ion_type, adduct_atom) 124 125 elif type(molecular_formula) is list: 126 self._from_list(molecular_formula, ion_type, adduct_atom) 127 128 elif type(molecular_formula) is str: 129 self._from_str(molecular_formula, ion_type, adduct_atom) 130 131 self._ion_charge = ion_charge 132 self._external_mz = external_mz 133 self._confidence_score = None 134 self._isotopologue_similarity = None 135 self._mz_error_score = None 136 self._mass_error_average_score = None 137 138 self.is_isotopologue = False 139 140 # parent mass spectrum peak obj instance 141 self._mspeak_parent = mspeak_parent 142 143 self.expected_isotopologues = [] 144 self.mspeak_mf_isotopologues_indexes = [] 145 146 if self._mspeak_parent: 147 kendrick_dict_base = ( 148 self._mspeak_parent._ms_parent.mspeaks_settings.kendrick_base 149 ) 150 else: 151 kendrick_dict_base = {"C": 1, "H": 2} 152 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 153 kendrick_dict_base 154 ) 155 156 def __repr__(self): 157 return "MolecularFormula({0},{1},ion type = {2}".format( 158 self._d_molecular_formula, self.ion_charge, self.ion_type 159 ) 160 161 def __str__(self): 162 return "MolecularFormula {0}, ion_charge:{1}, ion type:{2}, m/z:{3} ".format( 163 self.string, self.ion_charge, self.ion_type, self.mz_calc 164 ) 165 166 def __len__(self): 167 # crash if keys are not ordered 168 return len(self._d_molecular_formula.keys()) 169 170 def __getitem__(self, atom): 171 # atom = list(self._d_molecular_formula.keys())[position] 172 if atom in self._d_molecular_formula.keys(): 173 return self._d_molecular_formula[atom] 174 else: 175 return 0 176 177 def get(self, atom): 178 """Get the atom quantity of a specific atom. 179 180 Parameters 181 ---------- 182 atom : str 183 The atom symbol. 184 185 Returns 186 ------- 187 int 188 The atom quantity. 189 """ 190 # atom = list(self._d_molecular_formula.keys())[position] 191 if atom in self._d_molecular_formula.keys(): 192 return self._d_molecular_formula[atom] 193 else: 194 return 0 195 196 def _from_dict(self, molecular_formula, ion_type, adduct_atom): 197 self._d_molecular_formula = { 198 key: val for key, val in molecular_formula.items() if val != 0 199 } 200 201 if ion_type is not None: 202 self._d_molecular_formula[Labels.ion_type] = ion_type 203 204 if adduct_atom: 205 if adduct_atom in self._d_molecular_formula: 206 self._d_molecular_formula[adduct_atom] += 1 207 else: 208 self._d_molecular_formula[adduct_atom] = 1 209 self.adduct_atom = adduct_atom 210 211 def _from_list(self, molecular_formula_list, ion_type, adduct_atom): 212 # list has to be in the format 213 # ['C', 10, 'H', 21, '13C', 1, 'Cl', 1, etc] 214 self._d_molecular_formula = {} 215 for each in range(0, len(molecular_formula_list), 2): 216 atoms_label = molecular_formula_list[each] 217 atoms_count = int(molecular_formula_list[each + 1]) 218 219 if atoms_count > 0: 220 self._d_molecular_formula[atoms_label] = int(atoms_count) 221 222 self._d_molecular_formula[Labels.ion_type] = ion_type 223 if adduct_atom: 224 self.adduct_atom = adduct_atom 225 if adduct_atom in self._d_molecular_formula: 226 self._d_molecular_formula[adduct_atom] += 1 227 else: 228 self._d_molecular_formula[adduct_atom] = 1 229 else: 230 self.adduct_atom = None 231 232 def _from_str(self, molecular_formula_str, ion_type, adduct_atom): 233 # string has to be in the format 234 #'C10 H21 13C1 Cl1 37Cl1 etc' 235 # Check if there are spaces in the string 236 if " " not in molecular_formula_str: 237 raise ValueError( 238 "The molecular formula string should have spaces, input: %s" 239 % molecular_formula_str 240 ) 241 242 # Split the string by spaces 243 # Grab the text before a digit for each element after splitting on spaces (atoms) 244 elements = [re.sub(r"\d+$", "", x) for x in molecular_formula_str.split()] 245 # Grab the digits at the end of each element after splitting on spaces (counts) 246 counts = [re.findall(r"\d+$", x)[0] for x in molecular_formula_str.split()] 247 # Check that the number of elements and counts are the same 248 if len(elements) != len(counts): 249 raise ValueError( 250 "The number of elements and counts do not match, input: %s" 251 % molecular_formula_str 252 ) 253 254 # Create a dictionary from the elements and counts and add it to the molecular formula 255 dict_ = dict(zip(elements, counts)) 256 # Cast counts to integers 257 dict_ = {key: int(val) for key, val in dict_.items()} 258 self._from_dict(dict_, ion_type, adduct_atom) 259 260 def split(self, delimiters, string, maxsplit=0): # pragma: no cover 261 """Splits the molecular formula string. 262 263 Parameters 264 ---------- 265 delimiters : list 266 The list of delimiters. 267 string : str 268 The molecular formula string. 269 maxsplit : int, optional 270 The maximum number of splits. Defaults to 0. 271 272 Returns 273 ------- 274 list 275 The molecular formula list. 276 277 Notes 278 ----- 279 Does not work when formula has atoms with same characters in a row that below to different atoms, i.e. C10H21NNa. 280 """ 281 regexPattern = "|".join(map(re.escape, delimiters)) # pragma: no cover 282 isotopes = re.findall(regexPattern, string) # pragma: no cover 283 counts = re.split(regexPattern, string, maxsplit) # pragma: no cover 284 285 return [isotopes[0], int(counts[1])] 286 287 @property 288 def isotopologue_count_percentile( 289 self, 290 ): 291 if not len(self.expected_isotopologues) == 0: 292 return ( 293 len(self.mspeak_mf_isotopologues_indexes) 294 / len(self.expected_isotopologues) 295 ) * 100 296 else: 297 return 100 298 299 @property 300 def O_C(self): 301 if "O" in self._d_molecular_formula.keys(): 302 # gather all the Os and Hs, regardless of the isotopic composition 303 Os = sum( 304 [ 305 self._d_molecular_formula.get(key) 306 for key in ["O"] + Atoms.isotopes["O"][1] 307 if key in self._d_molecular_formula.keys() 308 ] 309 ) 310 Cs = sum( 311 [ 312 self._d_molecular_formula.get(key) 313 for key in ["C"] + Atoms.isotopes["C"][1] 314 if key in self._d_molecular_formula.keys() 315 ] 316 ) 317 return Os / Cs 318 else: 319 return 0 320 321 @property 322 def H_C(self): 323 # gather all the Cs and Hs, regardless of the isotopic composition 324 Cs = sum( 325 [ 326 self._d_molecular_formula.get(key) 327 for key in ["C"] + Atoms.isotopes["C"][1] 328 if key in self._d_molecular_formula.keys() 329 ] 330 ) 331 Hs = sum( 332 [ 333 self._d_molecular_formula.get(key) 334 for key in ["H"] + Atoms.isotopes["H"][1] 335 if key in self._d_molecular_formula.keys() 336 ] 337 ) 338 return Hs / Cs 339 340 @property 341 def A_I(self): 342 """Aromaticity index""" 343 return self._calc_aromaticity_index() 344 345 @property 346 def A_I_mod(self): 347 """Modified aromaticity index""" 348 return self._calc_aromaticity_index_mod() 349 350 @property 351 def nosc(self): 352 """Nominal oxidation state of carbon""" 353 return self._calc_nosc() 354 355 @property 356 def dbe(self): 357 return self._calc_dbe() 358 359 @property 360 def mz_nominal_calc(self): 361 return int(self._calc_mz()) 362 363 @property 364 def mz_error(self): 365 return self._calc_assignment_mass_error() 366 367 @property 368 def mz_calc(self): 369 return self._calc_mz() 370 371 @property 372 def protonated_mz(self): 373 return self._protonated_mz(self.ion_charge) 374 375 @property 376 def radical_mz(self): 377 return self._radical_mz(self.ion_charge) 378 379 @property 380 def neutral_mass(self): 381 return self._neutral_mass() 382 383 def adduct_mz(self, adduct_atom): 384 """Get m/z of an adducted ion version of the molecular formula. 385 386 Parameters 387 ---------- 388 adduct_atom : str 389 The adduct atom. 390 391 Returns 392 ------- 393 float 394 The m/z value of the adducted ion version of the molecular formula. 395 """ 396 return self._adduct_mz(adduct_atom, self.ion_charge) 397 398 @property 399 def ion_type(self): 400 ion_type = self._d_molecular_formula.get(Labels.ion_type) 401 if ion_type == Labels.protonated_de_ion: 402 if self.ion_charge > 0: 403 return Labels.protonated 404 else: 405 return Labels.de_protonated 406 else: 407 return ion_type 408 409 @ion_type.setter 410 def ion_type(self, ion_type): 411 if ion_type in [ 412 Labels.protonated_de_ion, 413 Labels.adduct_ion, 414 Labels.radical_ion, 415 ]: 416 self._d_molecular_formula[Labels.ion_type] = ion_type 417 else: 418 raise TypeError( 419 "Ion type can only be: 'DE_OR_PROTONATED', 'RADICAL' or 'ADDUCT', not %s" 420 % ion_type 421 ) 422 423 @property 424 def ion_charge(self): 425 return self._ion_charge 426 427 @property 428 def atoms(self): 429 """Get the atoms in the molecular formula.""" 430 # if there is an adduct_atom, them reduce it from the atoms list 431 if self.adduct_atom is None: 432 return [ 433 key 434 for key in self._d_molecular_formula.keys() 435 if key != Labels.ion_type 436 ] 437 else: 438 temp_dict = self._d_molecular_formula.copy() 439 temp_dict[self.adduct_atom] -= 1 440 return [ 441 key 442 for key, val in temp_dict.items() 443 if key != Labels.ion_type and val > 0 444 ] 445 446 @property 447 def confidence_score(self): 448 """Composite molecular formula assignment confidence score. 449 450 Combines formula-level mass accuracy and isotopologue pattern 451 agreement: 452 453 ``` 454 CS = (w_err * m_err) + (w_iso * m_iso) 455 ``` 456 457 where ``m_err`` is :attr:`average_mz_error_score`, ``m_iso`` is 458 :attr:`isotopologue_similarity`, and the weights are 459 ``molecular_search_settings.mz_error_score_weight`` (default 0.6) 460 and ``isotopologue_score_weight`` (default 0.4). 461 462 Returns 463 ------- 464 float 465 Score typically in about [0, 1]; higher values indicate better 466 agreement with mass error and (when available) isotopologue 467 evidence. Used for ranking when ``score_method`` is 468 ``\"prob_score\"``. 469 470 Notes 471 ----- 472 Requires an associated parent mass spectral peak. Isotopologue 473 terms are only informative when search was run with isotopologue 474 detection enabled. See the ``corems.molecular_formula`` package 475 documentation and Dewey et al., *Anal. Chem.* 2025, 97, 13031–13039 476 (https://doi.org/10.1021/acs.analchem.4c06826). 477 """ 478 if not self._confidence_score: 479 self._confidence_score = self._calc_confidence_score() 480 481 return self._confidence_score 482 483 @property 484 def isotopologue_similarity(self): 485 """Isotopologue abundance pattern similarity for this formula. 486 487 Compares expected natural-abundance isotopologue intensities 488 (from dynamic-range-limited expansion) to abundances of peaks 489 assigned as those partners. Implemented as a normalized Manhattan 490 distance mapped to a similarity in about [0, 1] 491 (higher is better). 492 493 Returns 494 ------- 495 float 496 Similarity score. Returns 0.0 when no isotopologues are 497 expected for this monoisotopic formula (or none were computed). 498 499 Notes 500 ----- 501 This is the ``m_iso`` term in :attr:`confidence_score`. See the 502 ``corems.molecular_formula`` package documentation for details. 503 """ 504 if not self._isotopologue_similarity: 505 self._isotopologue_similarity = self._calc_isotopologue_confidence() 506 507 return self._isotopologue_similarity 508 509 @property 510 def average_mz_error_score(self): 511 """Mean mass-accuracy score over mono and expected isotopologues. 512 513 Averages :attr:`mz_error_score` for the monoisotopic assignment and 514 for each expected isotopologue formula that has a parent peak. 515 Expected isotopologues without a matched peak contribute 0. 516 517 Returns 518 ------- 519 float 520 Mean Gaussian mass-error score in about [0, 1]. 521 522 Notes 523 ----- 524 This is the ``m_err`` term in :attr:`confidence_score`. 525 """ 526 # includes the isotopologues 527 528 if not self._mass_error_average_score: 529 self._mass_error_average_score = self._calc_average_mz_score() 530 531 return self._mass_error_average_score 532 533 @property 534 def mz_error_score(self): 535 """Gaussian mass-accuracy score for this formula on its parent peak. 536 537 Uses the assignment mass error ``delta`` (ppm) and a width ``sigma`` 538 equal to the parent peak's ``predicted_std`` when set, otherwise a 539 fallback of 1.66 ppm: 540 541 ``` 542 m = exp( -((delta - mu) ** 2) / (2 * (sigma ** 2)) ) 543 ``` 544 545 with mean ``mu = 0`` (calibrated spectrum with near-zero mean error 546 assumed). 547 548 Returns 549 ------- 550 float 551 Score typically in about 0 to 1; exact mass match is 1, 552 and larger ppm error yields values closer to 0. 553 554 Notes 555 ----- 556 Formula-level ranking uses :attr:`average_mz_error_score`, which 557 folds in expected isotopologue peaks as well. 558 """ 559 if not self._mz_error_score: 560 self._mz_error_score = self._calc_mz_confidence() 561 562 return self._mz_error_score 563 564 @property 565 def kmd(self): 566 return self._kmd 567 568 @property 569 def kendrick_mass(self): 570 return self._kendrick_mass 571 572 @property 573 def knm(self): 574 return self._nominal_km 575 576 def change_kendrick_base(self, kendrick_dict_base): 577 """Change the Kendrick base. 578 579 Parameters 580 ---------- 581 kendrick_dict_base : dict 582 The Kendrick base dictionary. Ex: {"C": 1, "H": 2} 583 """ 584 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 585 kendrick_dict_base 586 ) 587 588 def isotopologues(self, min_abundance, current_mono_abundance, dynamic_range): 589 """Calculate the isotopologues for a given molecular formula. 590 591 Parameters 592 ---------- 593 min_abundance : float 594 The minimum abundance. 595 current_mono_abundance : float 596 The current monoisotopic abundance. 597 dynamic_range : float 598 The dynamic range. 599 600 Yields 601 ------ 602 MolecularFormulaIsotopologue 603 The molecular formula isotopologue. 604 605 Notes 606 ----- 607 This calculation ignores the hydrogen isotopes. 608 """ 609 isotopologues = [] 610 for mf in self._cal_isotopologues( 611 self._d_molecular_formula, 612 min_abundance, 613 current_mono_abundance, 614 dynamic_range, 615 ): 616 isotopologues.append(mf) 617 618 # To account for differences in how the isotopologue outputs are sorted between IsoSpec versions. 619 sorted_isotopologues = sorted(isotopologues, key=lambda mf: mf[1], reverse=True) 620 621 for mf in sorted_isotopologues: 622 yield MolecularFormulaIsotopologue( 623 *mf, 624 current_mono_abundance, 625 self.ion_charge, 626 ion_type=self.ion_type, 627 adduct_atom=self.adduct_atom, 628 ) 629 630 def atoms_qnt(self, atom): 631 """Get the atom quantity of a specific atom in the molecular formula.""" 632 if atom in self._d_molecular_formula: 633 return self._d_molecular_formula.get(atom) 634 else: 635 raise Warning( 636 "Could not find %s in this Molecular Formula object" % str(atom) 637 ) 638 639 def atoms_symbol(self, atom): 640 """Get the atom symbol without the mass number.""" 641 return "".join([i for i in atom if not i.isdigit()]) 642 643 @property 644 def string(self): 645 """Returns the molecular formula as a string.""" 646 if self._d_molecular_formula: 647 if self.adduct_atom is None: 648 mol_form_dict = self._d_molecular_formula 649 else: 650 mol_form_dict = self._d_molecular_formula.copy() 651 if self.adduct_atom not in mol_form_dict.keys(): 652 raise Exception("Adduct atom not found in molecular formula dict") 653 mol_form_dict[self.adduct_atom] -= 1 654 mol_form_dict = { 655 key: val for key, val in mol_form_dict.items() if val != 0 656 } 657 formula_srt = "" 658 for atom in Atoms.atoms_order: 659 if atom in mol_form_dict.keys(): 660 formula_srt += atom + str(int(mol_form_dict.get(atom))) + " " 661 return formula_srt.strip() 662 663 else: 664 raise Exception("Molecular formula identification not performed yet") 665 666 @property 667 def string_formated(self): 668 SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") 669 SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹") 670 671 if self._d_molecular_formula: 672 formula_srt = "" 673 for atom in Atoms.atoms_order: 674 if atom in self.to_dict().keys(): 675 formula_srt += atom.translate(SUP) + str( 676 int(self.to_dict().get(atom)) 677 ).translate(SUB) 678 return formula_srt 679 680 else: 681 raise Exception("Molecular formula identification not performed yet") 682 683 def to_dict(self): 684 """Returns the molecular formula as a dictionary. 685 686 Returns 687 ------- 688 dict 689 The molecular formula as a dictionary. 690 """ 691 return self._d_molecular_formula 692 693 def to_list(self): 694 """Returns the molecular formula as a list. 695 696 Returns 697 ------- 698 list 699 The molecular formula as a list. 700 701 Raises 702 ------ 703 Exception 704 If the molecular formula identification was not performed yet. 705 """ 706 # TODO ensure self._d_molecular_formula is a orderedDict 707 708 if self._d_molecular_formula: 709 formula_list = [] 710 711 for atom, atom_number in self._d_molecular_formula.items(): 712 if atom != Labels.ion_type: 713 formula_list.append(atom) 714 formula_list.append(atom_number) 715 716 return formula_list 717 else: 718 raise Exception("Molecular formula identification not performed yet") 719 720 @property 721 def class_label(self): 722 if self._d_molecular_formula: 723 formulalist = self.to_list() 724 classstring = "" 725 726 for each in range(0, len(formulalist), 2): 727 if ( 728 formulalist[each] != "C" 729 and formulalist[each] != "H" 730 and formulalist[each] != "HC" 731 ): 732 classstring = ( 733 classstring 734 + str(formulalist[each]) 735 + str(formulalist[each + 1]) 736 + " " 737 ) 738 739 if classstring == "": 740 classstring = "HC" 741 742 classstring = classstring.strip() 743 744 if self._d_molecular_formula.get(Labels.ion_type) == Labels.radical_ion: 745 return classstring + " -R" 746 747 # elif self._d_molecular_formula.get(Labels.ion_type) == Labels.adduct_ion: 748 749 # return classstring + ' -A' 750 751 else: 752 return classstring 753 754 #'dict, tuple or string' 755 756 else: 757 raise Exception("Molecular formula identification not performed yet") 758 759 @property 760 def class_dict(self): 761 if self._d_molecular_formula: 762 class_dict = {} 763 764 for atom, qnt in self._d_molecular_formula.items(): 765 if atom != Labels.ion_type and atom != "C" and atom != "H": 766 class_dict[atom] = qnt 767 768 return class_dict 769 770 raise Exception("Molecular formula identification not performed yet")
Base class for representing a molecular formula.
Parameters
- molecular_formula (dict, list, str): The molecular formula.
- ion_charge (int): The ion charge.
- ion_type (str, optional): The ion type. Defaults to None.
- adduct_atom (str, optional): The adduct atom. Defaults to None.
- mspeak_parent (_MSPeak, optional): The parent mass spectrum peak object instance. Defaults to None.
- external_mz (float, optional): The external m/z value. Defaults to None.
Raises
- TypeError: If the ion type is not 'DE_OR_PROTONATED', 'RADICAL' or 'ADDUCT'.
Attributes
- isotopologue_count_percentile (float): The isotopologue count percentile.
- O_C (float): The O/C ratio.
- H_C (float): The H/C ratio.
- dbe (float): The double bond equivalent.
- mz_nominal_calc (int): The nominal m/z value.
- mz_error (float): The m/z error.
- mz_calc (float): The m/z value.
- protonated_mz (float): The protonated or deprotonated m/z value.
- radical_mz (float): The radical m/z value.
- neutral_mass (float): The neutral mass.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge.
- atoms (list): The atoms in the molecular formula.
- confidence_score (float):
Composite assignment confidence score in about [0, 1] (higher is
better). Weighted sum of
average_mz_error_scoreandisotopologue_similarityusingmz_error_score_weight/isotopologue_score_weightfrom the parent spectrum's molecular search settings. See thecorems.molecular_formulapackage documentation for the full formulation and literature reference. - isotopologue_similarity (float): Similarity between expected and observed isotopologue abundance patterns (about [0, 1]). Zero when no isotopologues are expected or none were computed during search.
- average_mz_error_score (float): Mean Gaussian mass-accuracy score over the monoisotopic peak and expected isotopologue peaks. Missing expected isotopologues contribute 0.
- mz_error_score (float):
Gaussian mass-accuracy score for this formula on its parent peak
only (ppm error vs
predicted_stdor a 1.66 ppm fallback). - kmd (float): The Kendrick mass defect (KMD).
- kendrick_mass (float): The Kendrick mass.
- knm (float): The nominal Kendrick mass.
- string (str): The molecular formula string.
- string_formated (str): The molecular formula string formated with subscripts and superscripts.
- class_label (str): The class label.
- class_dict (dict): The class dictionary.
Methods
- change_kendrick_base(kendrick_dict_base). Change the Kendrick base.
- isotopologues(min_abundance, current_mono_abundance, dynamic_range). Calculate the isotopologues.
- atoms_qnt(atom). Get the atom quantity.
- atoms_symbol(atom). Get the atom symbol without the mass number.
- to_dict(). Get the molecular formula as a dictionary.
- to_list(). Get the molecular formula as a list.
112 def __init__( 113 self, 114 molecular_formula, 115 ion_charge, 116 ion_type=None, 117 adduct_atom=None, 118 mspeak_parent=None, 119 external_mz=None, 120 ): 121 # clear dictionary of atoms with 0 value 122 if type(molecular_formula) is dict: 123 self._from_dict(molecular_formula, ion_type, adduct_atom) 124 125 elif type(molecular_formula) is list: 126 self._from_list(molecular_formula, ion_type, adduct_atom) 127 128 elif type(molecular_formula) is str: 129 self._from_str(molecular_formula, ion_type, adduct_atom) 130 131 self._ion_charge = ion_charge 132 self._external_mz = external_mz 133 self._confidence_score = None 134 self._isotopologue_similarity = None 135 self._mz_error_score = None 136 self._mass_error_average_score = None 137 138 self.is_isotopologue = False 139 140 # parent mass spectrum peak obj instance 141 self._mspeak_parent = mspeak_parent 142 143 self.expected_isotopologues = [] 144 self.mspeak_mf_isotopologues_indexes = [] 145 146 if self._mspeak_parent: 147 kendrick_dict_base = ( 148 self._mspeak_parent._ms_parent.mspeaks_settings.kendrick_base 149 ) 150 else: 151 kendrick_dict_base = {"C": 1, "H": 2} 152 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 153 kendrick_dict_base 154 )
177 def get(self, atom): 178 """Get the atom quantity of a specific atom. 179 180 Parameters 181 ---------- 182 atom : str 183 The atom symbol. 184 185 Returns 186 ------- 187 int 188 The atom quantity. 189 """ 190 # atom = list(self._d_molecular_formula.keys())[position] 191 if atom in self._d_molecular_formula.keys(): 192 return self._d_molecular_formula[atom] 193 else: 194 return 0
Get the atom quantity of a specific atom.
Parameters
- atom (str): The atom symbol.
Returns
- int: The atom quantity.
260 def split(self, delimiters, string, maxsplit=0): # pragma: no cover 261 """Splits the molecular formula string. 262 263 Parameters 264 ---------- 265 delimiters : list 266 The list of delimiters. 267 string : str 268 The molecular formula string. 269 maxsplit : int, optional 270 The maximum number of splits. Defaults to 0. 271 272 Returns 273 ------- 274 list 275 The molecular formula list. 276 277 Notes 278 ----- 279 Does not work when formula has atoms with same characters in a row that below to different atoms, i.e. C10H21NNa. 280 """ 281 regexPattern = "|".join(map(re.escape, delimiters)) # pragma: no cover 282 isotopes = re.findall(regexPattern, string) # pragma: no cover 283 counts = re.split(regexPattern, string, maxsplit) # pragma: no cover 284 285 return [isotopes[0], int(counts[1])]
Splits the molecular formula string.
Parameters
- delimiters (list): The list of delimiters.
- string (str): The molecular formula string.
- maxsplit (int, optional): The maximum number of splits. Defaults to 0.
Returns
- list: The molecular formula list.
Notes
Does not work when formula has atoms with same characters in a row that below to different atoms, i.e. C10H21NNa.
299 @property 300 def O_C(self): 301 if "O" in self._d_molecular_formula.keys(): 302 # gather all the Os and Hs, regardless of the isotopic composition 303 Os = sum( 304 [ 305 self._d_molecular_formula.get(key) 306 for key in ["O"] + Atoms.isotopes["O"][1] 307 if key in self._d_molecular_formula.keys() 308 ] 309 ) 310 Cs = sum( 311 [ 312 self._d_molecular_formula.get(key) 313 for key in ["C"] + Atoms.isotopes["C"][1] 314 if key in self._d_molecular_formula.keys() 315 ] 316 ) 317 return Os / Cs 318 else: 319 return 0
321 @property 322 def H_C(self): 323 # gather all the Cs and Hs, regardless of the isotopic composition 324 Cs = sum( 325 [ 326 self._d_molecular_formula.get(key) 327 for key in ["C"] + Atoms.isotopes["C"][1] 328 if key in self._d_molecular_formula.keys() 329 ] 330 ) 331 Hs = sum( 332 [ 333 self._d_molecular_formula.get(key) 334 for key in ["H"] + Atoms.isotopes["H"][1] 335 if key in self._d_molecular_formula.keys() 336 ] 337 ) 338 return Hs / Cs
340 @property 341 def A_I(self): 342 """Aromaticity index""" 343 return self._calc_aromaticity_index()
Aromaticity index
345 @property 346 def A_I_mod(self): 347 """Modified aromaticity index""" 348 return self._calc_aromaticity_index_mod()
Modified aromaticity index
350 @property 351 def nosc(self): 352 """Nominal oxidation state of carbon""" 353 return self._calc_nosc()
Nominal oxidation state of carbon
383 def adduct_mz(self, adduct_atom): 384 """Get m/z of an adducted ion version of the molecular formula. 385 386 Parameters 387 ---------- 388 adduct_atom : str 389 The adduct atom. 390 391 Returns 392 ------- 393 float 394 The m/z value of the adducted ion version of the molecular formula. 395 """ 396 return self._adduct_mz(adduct_atom, self.ion_charge)
Get m/z of an adducted ion version of the molecular formula.
Parameters
- adduct_atom (str): The adduct atom.
Returns
- float: The m/z value of the adducted ion version of the molecular formula.
427 @property 428 def atoms(self): 429 """Get the atoms in the molecular formula.""" 430 # if there is an adduct_atom, them reduce it from the atoms list 431 if self.adduct_atom is None: 432 return [ 433 key 434 for key in self._d_molecular_formula.keys() 435 if key != Labels.ion_type 436 ] 437 else: 438 temp_dict = self._d_molecular_formula.copy() 439 temp_dict[self.adduct_atom] -= 1 440 return [ 441 key 442 for key, val in temp_dict.items() 443 if key != Labels.ion_type and val > 0 444 ]
Get the atoms in the molecular formula.
446 @property 447 def confidence_score(self): 448 """Composite molecular formula assignment confidence score. 449 450 Combines formula-level mass accuracy and isotopologue pattern 451 agreement: 452 453 ``` 454 CS = (w_err * m_err) + (w_iso * m_iso) 455 ``` 456 457 where ``m_err`` is :attr:`average_mz_error_score`, ``m_iso`` is 458 :attr:`isotopologue_similarity`, and the weights are 459 ``molecular_search_settings.mz_error_score_weight`` (default 0.6) 460 and ``isotopologue_score_weight`` (default 0.4). 461 462 Returns 463 ------- 464 float 465 Score typically in about [0, 1]; higher values indicate better 466 agreement with mass error and (when available) isotopologue 467 evidence. Used for ranking when ``score_method`` is 468 ``\"prob_score\"``. 469 470 Notes 471 ----- 472 Requires an associated parent mass spectral peak. Isotopologue 473 terms are only informative when search was run with isotopologue 474 detection enabled. See the ``corems.molecular_formula`` package 475 documentation and Dewey et al., *Anal. Chem.* 2025, 97, 13031–13039 476 (https://doi.org/10.1021/acs.analchem.4c06826). 477 """ 478 if not self._confidence_score: 479 self._confidence_score = self._calc_confidence_score() 480 481 return self._confidence_score
Composite molecular formula assignment confidence score.
Combines formula-level mass accuracy and isotopologue pattern agreement:
CS = (w_err * m_err) + (w_iso * m_iso)
where m_err is average_mz_error_score, m_iso is
isotopologue_similarity, and the weights are
molecular_search_settings.mz_error_score_weight (default 0.6)
and isotopologue_score_weight (default 0.4).
Returns
- float: Score typically in about [0, 1]; higher values indicate better
agreement with mass error and (when available) isotopologue
evidence. Used for ranking when
score_methodis"prob_score".
Notes
Requires an associated parent mass spectral peak. Isotopologue
terms are only informative when search was run with isotopologue
detection enabled. See the corems.molecular_formula package
documentation and Dewey et al., Anal. Chem. 2025, 97, 13031–13039
(https://doi.org/10.1021/acs.analchem.4c06826).
483 @property 484 def isotopologue_similarity(self): 485 """Isotopologue abundance pattern similarity for this formula. 486 487 Compares expected natural-abundance isotopologue intensities 488 (from dynamic-range-limited expansion) to abundances of peaks 489 assigned as those partners. Implemented as a normalized Manhattan 490 distance mapped to a similarity in about [0, 1] 491 (higher is better). 492 493 Returns 494 ------- 495 float 496 Similarity score. Returns 0.0 when no isotopologues are 497 expected for this monoisotopic formula (or none were computed). 498 499 Notes 500 ----- 501 This is the ``m_iso`` term in :attr:`confidence_score`. See the 502 ``corems.molecular_formula`` package documentation for details. 503 """ 504 if not self._isotopologue_similarity: 505 self._isotopologue_similarity = self._calc_isotopologue_confidence() 506 507 return self._isotopologue_similarity
Isotopologue abundance pattern similarity for this formula.
Compares expected natural-abundance isotopologue intensities (from dynamic-range-limited expansion) to abundances of peaks assigned as those partners. Implemented as a normalized Manhattan distance mapped to a similarity in about [0, 1] (higher is better).
Returns
- float: Similarity score. Returns 0.0 when no isotopologues are expected for this monoisotopic formula (or none were computed).
Notes
This is the m_iso term in confidence_score. See the
corems.molecular_formula package documentation for details.
509 @property 510 def average_mz_error_score(self): 511 """Mean mass-accuracy score over mono and expected isotopologues. 512 513 Averages :attr:`mz_error_score` for the monoisotopic assignment and 514 for each expected isotopologue formula that has a parent peak. 515 Expected isotopologues without a matched peak contribute 0. 516 517 Returns 518 ------- 519 float 520 Mean Gaussian mass-error score in about [0, 1]. 521 522 Notes 523 ----- 524 This is the ``m_err`` term in :attr:`confidence_score`. 525 """ 526 # includes the isotopologues 527 528 if not self._mass_error_average_score: 529 self._mass_error_average_score = self._calc_average_mz_score() 530 531 return self._mass_error_average_score
Mean mass-accuracy score over mono and expected isotopologues.
Averages mz_error_score for the monoisotopic assignment and
for each expected isotopologue formula that has a parent peak.
Expected isotopologues without a matched peak contribute 0.
Returns
- float: Mean Gaussian mass-error score in about [0, 1].
Notes
This is the m_err term in confidence_score.
533 @property 534 def mz_error_score(self): 535 """Gaussian mass-accuracy score for this formula on its parent peak. 536 537 Uses the assignment mass error ``delta`` (ppm) and a width ``sigma`` 538 equal to the parent peak's ``predicted_std`` when set, otherwise a 539 fallback of 1.66 ppm: 540 541 ``` 542 m = exp( -((delta - mu) ** 2) / (2 * (sigma ** 2)) ) 543 ``` 544 545 with mean ``mu = 0`` (calibrated spectrum with near-zero mean error 546 assumed). 547 548 Returns 549 ------- 550 float 551 Score typically in about 0 to 1; exact mass match is 1, 552 and larger ppm error yields values closer to 0. 553 554 Notes 555 ----- 556 Formula-level ranking uses :attr:`average_mz_error_score`, which 557 folds in expected isotopologue peaks as well. 558 """ 559 if not self._mz_error_score: 560 self._mz_error_score = self._calc_mz_confidence() 561 562 return self._mz_error_score
Gaussian mass-accuracy score for this formula on its parent peak.
Uses the assignment mass error delta (ppm) and a width sigma
equal to the parent peak's predicted_std when set, otherwise a
fallback of 1.66 ppm:
m = exp( -((delta - mu) ** 2) / (2 * (sigma ** 2)) )
with mean mu = 0 (calibrated spectrum with near-zero mean error
assumed).
Returns
- float: Score typically in about 0 to 1; exact mass match is 1, and larger ppm error yields values closer to 0.
Notes
Formula-level ranking uses average_mz_error_score, which
folds in expected isotopologue peaks as well.
576 def change_kendrick_base(self, kendrick_dict_base): 577 """Change the Kendrick base. 578 579 Parameters 580 ---------- 581 kendrick_dict_base : dict 582 The Kendrick base dictionary. Ex: {"C": 1, "H": 2} 583 """ 584 self._kmd, self._kendrick_mass, self._nominal_km = self._calc_kmd( 585 kendrick_dict_base 586 )
Change the Kendrick base.
Parameters
- kendrick_dict_base (dict): The Kendrick base dictionary. Ex: {"C": 1, "H": 2}
588 def isotopologues(self, min_abundance, current_mono_abundance, dynamic_range): 589 """Calculate the isotopologues for a given molecular formula. 590 591 Parameters 592 ---------- 593 min_abundance : float 594 The minimum abundance. 595 current_mono_abundance : float 596 The current monoisotopic abundance. 597 dynamic_range : float 598 The dynamic range. 599 600 Yields 601 ------ 602 MolecularFormulaIsotopologue 603 The molecular formula isotopologue. 604 605 Notes 606 ----- 607 This calculation ignores the hydrogen isotopes. 608 """ 609 isotopologues = [] 610 for mf in self._cal_isotopologues( 611 self._d_molecular_formula, 612 min_abundance, 613 current_mono_abundance, 614 dynamic_range, 615 ): 616 isotopologues.append(mf) 617 618 # To account for differences in how the isotopologue outputs are sorted between IsoSpec versions. 619 sorted_isotopologues = sorted(isotopologues, key=lambda mf: mf[1], reverse=True) 620 621 for mf in sorted_isotopologues: 622 yield MolecularFormulaIsotopologue( 623 *mf, 624 current_mono_abundance, 625 self.ion_charge, 626 ion_type=self.ion_type, 627 adduct_atom=self.adduct_atom, 628 )
Calculate the isotopologues for a given molecular formula.
Parameters
- min_abundance (float): The minimum abundance.
- current_mono_abundance (float): The current monoisotopic abundance.
- dynamic_range (float): The dynamic range.
Yields
- MolecularFormulaIsotopologue: The molecular formula isotopologue.
Notes
This calculation ignores the hydrogen isotopes.
630 def atoms_qnt(self, atom): 631 """Get the atom quantity of a specific atom in the molecular formula.""" 632 if atom in self._d_molecular_formula: 633 return self._d_molecular_formula.get(atom) 634 else: 635 raise Warning( 636 "Could not find %s in this Molecular Formula object" % str(atom) 637 )
Get the atom quantity of a specific atom in the molecular formula.
639 def atoms_symbol(self, atom): 640 """Get the atom symbol without the mass number.""" 641 return "".join([i for i in atom if not i.isdigit()])
Get the atom symbol without the mass number.
643 @property 644 def string(self): 645 """Returns the molecular formula as a string.""" 646 if self._d_molecular_formula: 647 if self.adduct_atom is None: 648 mol_form_dict = self._d_molecular_formula 649 else: 650 mol_form_dict = self._d_molecular_formula.copy() 651 if self.adduct_atom not in mol_form_dict.keys(): 652 raise Exception("Adduct atom not found in molecular formula dict") 653 mol_form_dict[self.adduct_atom] -= 1 654 mol_form_dict = { 655 key: val for key, val in mol_form_dict.items() if val != 0 656 } 657 formula_srt = "" 658 for atom in Atoms.atoms_order: 659 if atom in mol_form_dict.keys(): 660 formula_srt += atom + str(int(mol_form_dict.get(atom))) + " " 661 return formula_srt.strip() 662 663 else: 664 raise Exception("Molecular formula identification not performed yet")
Returns the molecular formula as a string.
666 @property 667 def string_formated(self): 668 SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") 669 SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹") 670 671 if self._d_molecular_formula: 672 formula_srt = "" 673 for atom in Atoms.atoms_order: 674 if atom in self.to_dict().keys(): 675 formula_srt += atom.translate(SUP) + str( 676 int(self.to_dict().get(atom)) 677 ).translate(SUB) 678 return formula_srt 679 680 else: 681 raise Exception("Molecular formula identification not performed yet")
683 def to_dict(self): 684 """Returns the molecular formula as a dictionary. 685 686 Returns 687 ------- 688 dict 689 The molecular formula as a dictionary. 690 """ 691 return self._d_molecular_formula
Returns the molecular formula as a dictionary.
Returns
- dict: The molecular formula as a dictionary.
693 def to_list(self): 694 """Returns the molecular formula as a list. 695 696 Returns 697 ------- 698 list 699 The molecular formula as a list. 700 701 Raises 702 ------ 703 Exception 704 If the molecular formula identification was not performed yet. 705 """ 706 # TODO ensure self._d_molecular_formula is a orderedDict 707 708 if self._d_molecular_formula: 709 formula_list = [] 710 711 for atom, atom_number in self._d_molecular_formula.items(): 712 if atom != Labels.ion_type: 713 formula_list.append(atom) 714 formula_list.append(atom_number) 715 716 return formula_list 717 else: 718 raise Exception("Molecular formula identification not performed yet")
Returns the molecular formula as a list.
Returns
- list: The molecular formula as a list.
Raises
- Exception: If the molecular formula identification was not performed yet.
720 @property 721 def class_label(self): 722 if self._d_molecular_formula: 723 formulalist = self.to_list() 724 classstring = "" 725 726 for each in range(0, len(formulalist), 2): 727 if ( 728 formulalist[each] != "C" 729 and formulalist[each] != "H" 730 and formulalist[each] != "HC" 731 ): 732 classstring = ( 733 classstring 734 + str(formulalist[each]) 735 + str(formulalist[each + 1]) 736 + " " 737 ) 738 739 if classstring == "": 740 classstring = "HC" 741 742 classstring = classstring.strip() 743 744 if self._d_molecular_formula.get(Labels.ion_type) == Labels.radical_ion: 745 return classstring + " -R" 746 747 # elif self._d_molecular_formula.get(Labels.ion_type) == Labels.adduct_ion: 748 749 # return classstring + ' -A' 750 751 else: 752 return classstring 753 754 #'dict, tuple or string' 755 756 else: 757 raise Exception("Molecular formula identification not performed yet")
759 @property 760 def class_dict(self): 761 if self._d_molecular_formula: 762 class_dict = {} 763 764 for atom, qnt in self._d_molecular_formula.items(): 765 if atom != Labels.ion_type and atom != "C" and atom != "H": 766 class_dict[atom] = qnt 767 768 return class_dict 769 770 raise Exception("Molecular formula identification not performed yet")
773class MolecularFormulaIsotopologue(MolecularFormulaBase): 774 """Class for representing a molecular formula isotopologue. 775 776 Parameters 777 ---------- 778 _d_molecular_formula : dict 779 The molecular formula as a dictionary. 780 prob_ratio : float 781 The probability ratio. 782 mono_abundance : float 783 The monoisotopic abundance. 784 ion_charge : int 785 The ion charge. 786 mspeak_parent : object, optional 787 The parent mass spectrum peak object instance. Defaults to None. 788 ion_type : str, optional 789 The ion type. Defaults to None. 790 adduct_atom : str, optional 791 The adduct atom. Defaults to None. 792 793 Attributes 794 ---------- 795 prob_ratio : float 796 The probability ratio. 797 abundance_calc : float 798 The calculated abundance. 799 area_error : float 800 The area error. 801 abundance_error : float 802 The abundance error. 803 is_isotopologue : bool 804 The isotopologue flag. Defaults to True. 805 mspeak_index_mono_isotopic : int 806 The index of the monoisotopic peak in the mass spectrum peak list. Defaults to None. 807 mono_isotopic_formula_index : int 808 The index of the monoisotopic formula in the molecular formula list. Defaults to None. 809 """ 810 811 def __init__( 812 self, 813 _d_molecular_formula, 814 prob_ratio, 815 mono_abundance, 816 ion_charge, 817 mspeak_parent=None, 818 ion_type=None, 819 adduct_atom=None, 820 ): 821 if ion_type is None: 822 # check if ion type or adduct_atom is in the molecular formula dict 823 if Labels.ion_type in _d_molecular_formula: 824 ion_type = _d_molecular_formula.get(Labels.ion_type) 825 else: 826 ion_type = None 827 else: 828 ion_type = Labels.ion_type_translate.get(ion_type) 829 830 if ion_type == Labels.adduct_ion: 831 adduct_atom_int = None 832 if adduct_atom in _d_molecular_formula.keys(): 833 adduct_atom_int = adduct_atom 834 else: 835 # Check to see if adduct_atom should actually be an isotope of the adduct atom 836 for adduct_iso in Atoms.isotopes.get(adduct_atom)[1]: 837 if adduct_iso in _d_molecular_formula.keys(): 838 adduct_atom_int = adduct_iso 839 adduct_atom = adduct_atom_int 840 if adduct_atom is None: 841 raise Exception("adduct_atom is required for adduct ion") 842 _d_molecular_formula[adduct_atom] -= 1 843 _d_molecular_formula = { 844 key: val for key, val in _d_molecular_formula.items() if val != 0 845 } 846 847 super().__init__( 848 molecular_formula=_d_molecular_formula, 849 ion_charge=ion_charge, 850 ion_type=ion_type, 851 adduct_atom=adduct_atom, 852 ) 853 # prob_ratio is relative to the monoisotopic peak p_isotopologue/p_mono_isotopic 854 855 self.prob_ratio = prob_ratio 856 857 self.abundance_calc = mono_abundance * prob_ratio 858 859 self.is_isotopologue = True 860 861 self.mspeak_index_mono_isotopic = None 862 863 self.mono_isotopic_formula_index = None 864 # parent mass spectrum peak obj instance 865 self._mspeak_parent = mspeak_parent 866 867 @property 868 def area_error(self): 869 return self._calc_area_error() 870 871 @property 872 def abundance_error(self): 873 return self._calc_abundance_error()
Class for representing a molecular formula isotopologue.
Parameters
- _d_molecular_formula (dict): The molecular formula as a dictionary.
- prob_ratio (float): The probability ratio.
- mono_abundance (float): The monoisotopic abundance.
- ion_charge (int): The ion charge.
- mspeak_parent (object, optional): The parent mass spectrum peak object instance. Defaults to None.
- ion_type (str, optional): The ion type. Defaults to None.
- adduct_atom (str, optional): The adduct atom. Defaults to None.
Attributes
- prob_ratio (float): The probability ratio.
- abundance_calc (float): The calculated abundance.
- area_error (float): The area error.
- abundance_error (float): The abundance error.
- is_isotopologue (bool): The isotopologue flag. Defaults to True.
- mspeak_index_mono_isotopic (int): The index of the monoisotopic peak in the mass spectrum peak list. Defaults to None.
- mono_isotopic_formula_index (int): The index of the monoisotopic formula in the molecular formula list. Defaults to None.
811 def __init__( 812 self, 813 _d_molecular_formula, 814 prob_ratio, 815 mono_abundance, 816 ion_charge, 817 mspeak_parent=None, 818 ion_type=None, 819 adduct_atom=None, 820 ): 821 if ion_type is None: 822 # check if ion type or adduct_atom is in the molecular formula dict 823 if Labels.ion_type in _d_molecular_formula: 824 ion_type = _d_molecular_formula.get(Labels.ion_type) 825 else: 826 ion_type = None 827 else: 828 ion_type = Labels.ion_type_translate.get(ion_type) 829 830 if ion_type == Labels.adduct_ion: 831 adduct_atom_int = None 832 if adduct_atom in _d_molecular_formula.keys(): 833 adduct_atom_int = adduct_atom 834 else: 835 # Check to see if adduct_atom should actually be an isotope of the adduct atom 836 for adduct_iso in Atoms.isotopes.get(adduct_atom)[1]: 837 if adduct_iso in _d_molecular_formula.keys(): 838 adduct_atom_int = adduct_iso 839 adduct_atom = adduct_atom_int 840 if adduct_atom is None: 841 raise Exception("adduct_atom is required for adduct ion") 842 _d_molecular_formula[adduct_atom] -= 1 843 _d_molecular_formula = { 844 key: val for key, val in _d_molecular_formula.items() if val != 0 845 } 846 847 super().__init__( 848 molecular_formula=_d_molecular_formula, 849 ion_charge=ion_charge, 850 ion_type=ion_type, 851 adduct_atom=adduct_atom, 852 ) 853 # prob_ratio is relative to the monoisotopic peak p_isotopologue/p_mono_isotopic 854 855 self.prob_ratio = prob_ratio 856 857 self.abundance_calc = mono_abundance * prob_ratio 858 859 self.is_isotopologue = True 860 861 self.mspeak_index_mono_isotopic = None 862 863 self.mono_isotopic_formula_index = None 864 # parent mass spectrum peak obj instance 865 self._mspeak_parent = mspeak_parent
Inherited Members
- MolecularFormulaBase
- expected_isotopologues
- mspeak_mf_isotopologues_indexes
- get
- split
- isotopologue_count_percentile
- O_C
- H_C
- A_I
- A_I_mod
- nosc
- dbe
- mz_nominal_calc
- mz_error
- mz_calc
- protonated_mz
- radical_mz
- neutral_mass
- adduct_mz
- ion_type
- ion_charge
- atoms
- confidence_score
- isotopologue_similarity
- average_mz_error_score
- mz_error_score
- kmd
- kendrick_mass
- knm
- change_kendrick_base
- isotopologues
- atoms_qnt
- atoms_symbol
- string
- string_formated
- to_dict
- to_list
- class_label
- class_dict
876class LCMSLibRefMolecularFormula: 877 """Removed LC-MS library reference molecular formula type. 878 879 This class is no longer supported. Canonical LC-MS MS1 formula assignment 880 uses :class:`MolecularFormula` via combinatorial / SQL search. Construction 881 raises :class:`NotImplementedError`. The name is retained as a stub until 882 the next major release. 883 884 Parameters 885 ---------- 886 *args 887 Ignored; retained only for call-site compatibility. 888 **kwargs 889 Ignored; retained only for call-site compatibility. 890 """ 891 892 def __init__(self, *args, **kwargs) -> None: 893 raise NotImplementedError( 894 "LCMSLibRefMolecularFormula is no longer supported. " 895 "Use MolecularFormula for formula objects. " 896 "This stub will be removed in the next major release." 897 )
Removed LC-MS library reference molecular formula type.
This class is no longer supported. Canonical LC-MS MS1 formula assignment
uses MolecularFormula via combinatorial / SQL search. Construction
raises NotImplementedError. The name is retained as a stub until
the next major release.
Parameters
- *args: Ignored; retained only for call-site compatibility.
- **kwargs: Ignored; retained only for call-site compatibility.
900class MolecularFormula(MolecularFormulaBase): 901 """General class for representing a molecular formula. 902 903 Parameters 904 ---------- 905 molecular_formula : dict, list, str 906 The molecular formula. 907 ion_charge : int 908 The ion charge. 909 ion_type : str, optional 910 The ion type. Defaults to None. 911 adduct_atom : str, optional 912 The adduct atom. Defaults to None. 913 mspeak_parent : object, optional 914 The parent mass spectrum peak object instance. Defaults to None. 915 external_mz : float, optional 916 The external m/z value. Defaults to False. 917 """ 918 919 def __init__( 920 self, 921 molecular_formula, 922 ion_charge, 923 ion_type=None, 924 adduct_atom=None, 925 mspeak_parent=None, 926 external_mz=False, 927 ): 928 super().__init__( 929 molecular_formula, 930 ion_charge, 931 ion_type=ion_type, 932 adduct_atom=adduct_atom, 933 mspeak_parent=mspeak_parent, 934 external_mz=external_mz, 935 )
General class for representing a molecular formula.
Parameters
- molecular_formula (dict, list, str): The molecular formula.
- ion_charge (int): The ion charge.
- ion_type (str, optional): The ion type. Defaults to None.
- adduct_atom (str, optional): The adduct atom. Defaults to None.
- mspeak_parent (object, optional): The parent mass spectrum peak object instance. Defaults to None.
- external_mz (float, optional): The external m/z value. Defaults to False.
919 def __init__( 920 self, 921 molecular_formula, 922 ion_charge, 923 ion_type=None, 924 adduct_atom=None, 925 mspeak_parent=None, 926 external_mz=False, 927 ): 928 super().__init__( 929 molecular_formula, 930 ion_charge, 931 ion_type=ion_type, 932 adduct_atom=adduct_atom, 933 mspeak_parent=mspeak_parent, 934 external_mz=external_mz, 935 )
Inherited Members
- MolecularFormulaBase
- is_isotopologue
- expected_isotopologues
- mspeak_mf_isotopologues_indexes
- get
- split
- isotopologue_count_percentile
- O_C
- H_C
- A_I
- A_I_mod
- nosc
- dbe
- mz_nominal_calc
- mz_error
- mz_calc
- protonated_mz
- radical_mz
- neutral_mass
- adduct_mz
- ion_type
- ion_charge
- atoms
- confidence_score
- isotopologue_similarity
- average_mz_error_score
- mz_error_score
- kmd
- kendrick_mass
- knm
- change_kendrick_base
- isotopologues
- atoms_qnt
- atoms_symbol
- string
- string_formated
- to_dict
- to_list
- class_label
- class_dict