corems.molecular_id.search.molecularFormulaSearch
1__author__ = "Yuri E. Corilo" 2__date__ = "Jul 29, 2019" 3 4 5from typing import Callable, List, NamedTuple, Optional, Tuple, Union 6 7import tqdm 8 9from corems import chunks, timeit 10from corems.encapsulation.constant import Labels 11from corems.encapsulation.factory.processingSetting import validate_used_atoms_keys 12from corems.molecular_formula.factory.MolecularFormulaFactory import MolecularFormula 13from corems.molecular_id.factory.MolecularLookupTable import MolecularCombinations 14from corems.molecular_id.factory.molecularSQL import MolForm_SQL 15from corems.ms_peak.factory.MSPeakClasses import _MSPeak 16 17last_error = 0 18last_dif = 0 19closest_error = 0 20error_average = 0 21nbValues = 0 22 23 24class FormulaSearchJob(NamedTuple): 25 """One ion-type candidate batch for molecular formula search. 26 27 Parameters 28 ---------- 29 ion_charge : int 30 Signed search charge for this batch. 31 ion_type : str 32 ``Labels`` ion type (protonated/deprotonated, radical, or adduct). 33 adduct_atom : str or None 34 Adduct element symbol when ``ion_type`` is adduct; otherwise None. 35 candidate_formulas : dict 36 Mapping of nominal m/z to formula objects, as consumed by ``run_search``. 37 progress : str 38 tqdm description for this class / ion type / charge. 39 """ 40 41 ion_charge: int 42 ion_type: str 43 adduct_atom: Optional[str] 44 candidate_formulas: dict 45 progress: str 46 47 48class SearchMolecularFormulas: 49 """Class for searching molecular formulas in a mass spectrum. 50 51 Parameters 52 ---------- 53 mass_spectrum_obj : MassSpectrum 54 The mass spectrum object. 55 sql_db : MolForm_SQL, optional 56 The SQL database object, by default None. 57 first_hit : bool, optional 58 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 59 find_isotopologues : bool, optional 60 Flag to indicate whether to find isotopologues, by default True. 61 62 Attributes 63 ---------- 64 mass_spectrum_obj : MassSpectrum 65 The mass spectrum object. 66 sql_db : MolForm_SQL 67 The SQL database object. 68 first_hit : bool 69 Flag to indicate whether to skip peaks that already have a molecular formula assigned. 70 find_isotopologues : bool 71 Flag to indicate whether to find isotopologues. 72 73 74 Methods 75 ------- 76 * run_search(). 77 Run the molecular formula search. 78 * run_worker_mass_spectrum(). 79 Run the molecular formula search on the mass spectrum object. 80 * run_worker_ms_peaks(). 81 Run the molecular formula search on the given list of mass spectrum peaks. 82 * database_to_dict(). 83 Convert the database results to a dictionary. 84 * run_molecular_formula(). 85 Run the molecular formula search on the given list of mass spectrum peaks. 86 * search_mol_formulas(). 87 Search for molecular formulas in the mass spectrum. 88 * ion_charges_for_search(). 89 Build the signed charge tuple from polarity and absolute min/max settings. 90 91 """ 92 93 def __init__( 94 self, 95 mass_spectrum_obj, 96 sql_db=None, 97 first_hit: bool = False, 98 find_isotopologues: bool = True, 99 ): 100 self.first_hit = first_hit 101 102 self.find_isotopologues = find_isotopologues 103 104 self.mass_spectrum_obj = mass_spectrum_obj 105 106 # Catch in-place usedAtoms mutations after settings construction 107 validate_used_atoms_keys(mass_spectrum_obj.molecular_search_settings.usedAtoms) 108 109 if not sql_db: 110 self.sql_db = MolForm_SQL( 111 url=mass_spectrum_obj.molecular_search_settings.url_database 112 ) 113 114 else: 115 self.sql_db = sql_db 116 117 def __enter__(self): 118 """Open the SQL database connection.""" 119 return self 120 121 def __exit__(self, exc_type, exc_val, exc_tb): 122 """Close the SQL database connection.""" 123 self.sql_db.close() 124 125 return False 126 127 @staticmethod 128 def ion_charges_for_search( 129 polarity: Union[int, str], 130 min_ion_charge: int = 1, 131 max_ion_charge: int = 1, 132 ) -> Tuple[int, ...]: 133 """Signed ion charges to use for molecular formula search. 134 135 Absolute charges from ``min_ion_charge`` through ``max_ion_charge`` 136 (inclusive) are multiplied by the polarity sign. No charge-state 137 determination is performed; every selected peak is searched at each 138 returned charge and candidates are ranked by formula scores. 139 140 Parameters 141 ---------- 142 polarity : int or str 143 Mass-spectrum polarity as ``+1`` / ``-1``, or LCMS polarity string 144 ``"positive"`` / ``"negative"``. 145 min_ion_charge : int, optional 146 Minimum absolute charge (default 1). 147 max_ion_charge : int, optional 148 Maximum absolute charge (default 1). Must be >= ``min_ion_charge``. 149 150 Returns 151 ------- 152 tuple of int 153 Signed charges sorted by ``(abs(z), z)`` so lower |z| is first 154 (relevant when ``first_hit=True``). 155 156 Raises 157 ------ 158 ValueError 159 If polarity is invalid, min < 1, or max < min. 160 """ 161 min_z = int(min_ion_charge) 162 max_z = int(max_ion_charge) 163 if min_z < 1: 164 raise ValueError("min_ion_charge must be >= 1") 165 if max_z < min_z: 166 raise ValueError("max_ion_charge must be >= min_ion_charge") 167 168 if isinstance(polarity, str): 169 p = polarity.strip().lower() 170 if p == "positive": 171 sign = 1 172 elif p == "negative": 173 sign = -1 174 else: 175 raise ValueError( 176 "polarity string must be 'positive' or 'negative', " 177 f"got {polarity!r}" 178 ) 179 else: 180 pol = int(polarity) 181 if pol == 0: 182 raise ValueError("polarity must be non-zero") 183 sign = 1 if pol > 0 else -1 184 185 charges = [sign * z for z in range(min_z, max_z + 1)] 186 return tuple(sorted(charges, key=lambda z: (abs(z), z))) 187 188 @staticmethod 189 def _iter_ion_type_jobs(settings, dict_res, classe_str, ion_charge): 190 """Yield ion-type candidate jobs for one heteroatom class at one charge. 191 192 Encodes shared search policy: protonated and radical follow the 193 corresponding settings flags; adduct search runs only when adducts 194 are enabled and ``abs(ion_charge) == 1``. Empty candidate lists and 195 missing ``dict_res`` keys are skipped. 196 197 Parameters 198 ---------- 199 settings : MolecularFormulaSearchSettings 200 Molecular formula search settings (ion-type flags). 201 dict_res : dict 202 Result of ``database_to_dict`` for this charge: protonated/radical 203 map class string to a nominal-m/z query dict; adduct maps adduct 204 atom to that class mapping. 205 classe_str : str 206 Heteroatom class key as stored in ``dict_res``. 207 ion_charge : int 208 Signed ion charge for this batch. 209 210 Yields 211 ------ 212 FormulaSearchJob 213 One job per enabled ion type (and per adduct atom) with candidates. 214 """ 215 if settings.isProtonated: 216 by_class = (dict_res.get(Labels.protonated_de_ion) or {}).get(classe_str) 217 if by_class: 218 yield FormulaSearchJob( 219 ion_charge=ion_charge, 220 ion_type=Labels.protonated_de_ion, 221 adduct_atom=None, 222 candidate_formulas=by_class, 223 progress=( 224 "Started molecular formula search for class %s, " 225 "(de)protonated z=%s " % (classe_str, ion_charge) 226 ), 227 ) 228 229 if settings.isRadical: 230 by_class = (dict_res.get(Labels.radical_ion) or {}).get(classe_str) 231 if by_class: 232 yield FormulaSearchJob( 233 ion_charge=ion_charge, 234 ion_type=Labels.radical_ion, 235 adduct_atom=None, 236 candidate_formulas=by_class, 237 progress=( 238 "Started molecular formula search for class %s, " 239 "radical z=%s " % (classe_str, ion_charge) 240 ), 241 ) 242 243 # Adduct search only at |z|==1; multi-charge adducts are not modeled. 244 if settings.isAdduct and abs(ion_charge) == 1: 245 dict_atoms_formulas = dict_res.get(Labels.adduct_ion) or {} 246 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 247 by_class = dict_by_class.get(classe_str) 248 if by_class: 249 yield FormulaSearchJob( 250 ion_charge=ion_charge, 251 ion_type=Labels.adduct_ion, 252 adduct_atom=adduct_atom, 253 candidate_formulas=by_class, 254 progress=( 255 "Started molecular formula search for class %s, " 256 "adduct z=%s " % (classe_str, ion_charge) 257 ), 258 ) 259 260 @staticmethod 261 def _run_formula_search_jobs( 262 classes, 263 nominal_mzs, 264 mf_search_settings, 265 search_charges, 266 sql_db, 267 apply_fn: Callable[[FormulaSearchJob], None], 268 ): 269 """Run charge × class × ion-type search, applying each candidate batch. 270 271 Loads the formula database once per charge per class chunk. Does not 272 close ``sql_db``. tqdm iterates class chunks; the description is the 273 job ``progress`` string. 274 275 Parameters 276 ---------- 277 classes : list 278 Heteroatom class tuples from ``MolecularCombinations.runworker``. 279 nominal_mzs : list 280 Nominal m/z values to load from the database. 281 mf_search_settings : MolecularFormulaSearchSettings 282 Search settings (chunk size, ion-type flags, verbosity). 283 search_charges : sequence of int 284 Signed charges from ``ion_charges_for_search``. 285 sql_db : MolForm_SQL 286 Open formula database connection. 287 apply_fn : callable 288 Called with each ``FormulaSearchJob``. Path-specific: DI uses 289 ``run_search``; LC uses ``search_spectra_against_candidates``. 290 """ 291 verbose = mf_search_settings.verbose_processing 292 for classe_chunk in chunks(classes, mf_search_settings.db_chunk_size): 293 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 294 for ion_charge in search_charges: 295 dict_res = SearchMolecularFormulas.database_to_dict( 296 classes_str_list, 297 nominal_mzs, 298 mf_search_settings, 299 ion_charge, 300 sql_db=sql_db, 301 ) 302 pbar = tqdm.tqdm(classe_chunk, disable=not verbose) 303 for classe_tuple in pbar: 304 classe_str = classe_tuple[0] 305 for job in SearchMolecularFormulas._iter_ion_type_jobs( 306 mf_search_settings, dict_res, classe_str, ion_charge 307 ): 308 pbar.set_description_str(desc=job.progress, refresh=True) 309 apply_fn(job) 310 311 def run_search( 312 self, 313 mspeaks: list, 314 query: dict, 315 min_abundance: float, 316 ion_type: str, 317 ion_charge: int, 318 adduct_atom=None, 319 ): 320 """Run the molecular formula search. 321 322 Parameters 323 ---------- 324 mspeaks : list of MSPeak 325 The list of mass spectrum peaks. 326 query : dict 327 The query dictionary containing the possible molecular formulas. 328 min_abundance : float 329 The minimum abundance threshold. 330 ion_type : str 331 The ion type. 332 ion_charge : int 333 The ion charge. 334 adduct_atom : str, optional 335 The adduct atom, by default None. 336 """ 337 338 def get_formulas(nominal_overlay: float = 0.1): 339 """ 340 Get the list of formulas based on the nominal overlay. 341 342 Parameters 343 ---------- 344 nominal_overlay : float, optional 345 The nominal overlay, by default 0.1. 346 347 Returns 348 ------- 349 list 350 The list of formulas. 351 """ 352 nominal_mz = ms_peak.nominal_mz_exp 353 354 defect_mass = ms_peak.mz_exp - nominal_mz 355 nominal_masses = [nominal_mz] 356 357 if (defect_mass) >= 1 - nominal_overlay: 358 nominal_masses.append(nominal_mz + 1) 359 elif (defect_mass) <= nominal_overlay: 360 nominal_masses.append(nominal_mz - 1) 361 362 list_formulas_candidates = [] 363 364 for nominal_mass in nominal_masses: 365 if nominal_mass in query.keys(): 366 list_formulas_candidates.extend(query.get(nominal_mass)) 367 368 return list_formulas_candidates 369 370 all_assigned_indexes = list() 371 372 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 373 374 search_molfrom = SearchMolecularFormulaWorker( 375 find_isotopologues=self.find_isotopologues 376 ) 377 378 for ms_peak in mspeaks: 379 # already assigned a molecular formula 380 if self.first_hit: 381 if ms_peak.is_assigned: 382 continue 383 384 ms_peak_indexes = search_molfrom.find_formulas( 385 get_formulas(), 386 min_abundance, 387 self.mass_spectrum_obj, 388 ms_peak, 389 ion_type, 390 ion_charge, 391 adduct_atom, 392 ) 393 394 all_assigned_indexes.extend(ms_peak_indexes) 395 396 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 397 398 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 399 400 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 401 # filter per min peaks per mono isotopic class 402 403 def run_worker_mass_spectrum(self): 404 """Run the molecular formula search on the mass spectrum object.""" 405 self.run_molecular_formula( 406 self.mass_spectrum_obj.sort_by_abundance(), 407 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 408 ) 409 410 def run_worker_ms_peaks(self, ms_peaks): 411 """Run the molecular formula search on the given list of mass spectrum peaks. 412 413 Parameters 414 ---------- 415 ms_peaks : list of MSPeak 416 The list of mass spectrum peaks. 417 """ 418 self.run_molecular_formula( 419 ms_peaks, 420 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 421 ) 422 423 @staticmethod 424 def database_to_dict( 425 classe_str_list, 426 nominal_mzs, 427 mf_search_settings, 428 ion_charge, 429 sql_db=None, 430 ): 431 """Convert the database results to a dictionary. 432 433 Parameters 434 ---------- 435 classe_str_list : list 436 The list of class strings. 437 nominal_mzs : list 438 The list of nominal m/z values. 439 mf_search_settings : MolecularFormulaSearchSettings 440 The molecular formula search settings. 441 ion_charge : int 442 The ion charge. 443 sql_db : MolForm_SQL, optional 444 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 445 446 Returns 447 ------- 448 dict 449 The dictionary containing the database results. 450 """ 451 owns_db = sql_db is None 452 if owns_db: 453 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 454 455 try: 456 dict_res = {} 457 458 if mf_search_settings.isProtonated: 459 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 460 classe_str_list, 461 Labels.protonated_de_ion, 462 nominal_mzs, 463 ion_charge, 464 mf_search_settings, 465 ) 466 467 if mf_search_settings.isRadical: 468 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 469 classe_str_list, 470 Labels.radical_ion, 471 nominal_mzs, 472 ion_charge, 473 mf_search_settings, 474 ) 475 476 if mf_search_settings.isAdduct: 477 adduct_list = ( 478 mf_search_settings.adduct_atoms_neg 479 if ion_charge < 0 480 else mf_search_settings.adduct_atoms_pos 481 ) 482 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 483 classe_str_list, 484 Labels.adduct_ion, 485 nominal_mzs, 486 ion_charge, 487 mf_search_settings, 488 adducts=adduct_list, 489 ) 490 491 return dict_res 492 finally: 493 if owns_db: 494 sql_db.close() 495 496 @timeit(print_time=True) 497 def run_molecular_formula(self, ms_peaks, **kwargs): 498 """Run the molecular formula search on the given list of mass spectrum peaks. 499 500 Parameters 501 ---------- 502 ms_peaks : list of MSPeak 503 The list of mass spectrum peaks. 504 **kwargs 505 Additional keyword arguments. 506 Most notably, print_time, which is a boolean flag to indicate whether to print the time 507 and passed to the timeit decorator. 508 """ 509 settings = self.mass_spectrum_obj.molecular_search_settings 510 # Re-check after possible post-construction mutation of settings fields 511 settings.validate_ion_charge_settings() 512 # Polarity must come from the mass spectrum (DI often -1 from data). 513 # Do not default to +1; LC already requires polarity on the LCMS object. 514 polarity_raw = self.mass_spectrum_obj.polarity 515 if polarity_raw is None: 516 raise ValueError( 517 "Mass spectrum polarity must be set from the data before " 518 "formula search (e.g. +1 or -1). There is no default polarity." 519 ) 520 try: 521 polarity = int(polarity_raw) 522 except (TypeError, ValueError) as exc: 523 raise ValueError( 524 "Mass spectrum polarity must be an integer +1 or -1 " 525 f"(got {polarity_raw!r})." 526 ) from exc 527 if polarity == 0: 528 raise ValueError("Mass spectrum polarity must be non-zero (+1 or -1).") 529 # Multi-charge formula search: expand absolute min..max (default 1..1). 530 # Does not rewrite MSPeak.polarity (remains ±1). 531 search_charges = self.ion_charges_for_search( 532 polarity, 533 min_ion_charge=settings.min_ion_charge, 534 max_ion_charge=settings.max_ion_charge, 535 ) 536 min_abundance = self.mass_spectrum_obj.min_abundance 537 nominal_mzs = self.mass_spectrum_obj.nominal_mz 538 539 # reset average error, only relevant is average mass error method is being used 540 SearchMolecularFormulaWorker( 541 find_isotopologues=self.find_isotopologues 542 ).reset_error(self.mass_spectrum_obj) 543 544 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 545 classes = MolecularCombinations(self.sql_db).runworker( 546 settings, print_time=settings.verbose_processing 547 ) 548 549 def apply_job(job): 550 self.run_search( 551 ms_peaks, 552 job.candidate_formulas, 553 min_abundance, 554 job.ion_type, 555 job.ion_charge, 556 adduct_atom=job.adduct_atom, 557 ) 558 559 self._run_formula_search_jobs( 560 classes, 561 nominal_mzs, 562 settings, 563 search_charges, 564 self.sql_db, 565 apply_job, 566 ) 567 self.sql_db.close() 568 569 def search_mol_formulas( 570 self, 571 possible_formulas_list: List[MolecularFormula], 572 ion_type: str, 573 neutral_molform=True, 574 find_isotopologues=True, 575 adduct_atom=None, 576 ) -> List[_MSPeak]: 577 """Search for molecular formulas in the mass spectrum. 578 579 Parameters 580 ---------- 581 possible_formulas_list : list of MolecularFormula 582 The list of possible molecular formulas. 583 ion_type : str 584 The ion type. 585 neutral_molform : bool, optional 586 Flag to indicate whether the molecular formulas are neutral, by default True. 587 find_isotopologues : bool, optional 588 Flag to indicate whether to find isotopologues, by default True. 589 adduct_atom : str, optional 590 The adduct atom, by default None. 591 592 Returns 593 ------- 594 list of MSPeak 595 The list of mass spectrum peaks with assigned molecular formulas. 596 """ 597 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 598 # if that is the case than turn neutral_molform off 599 600 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 601 self.mass_spectrum_obj 602 ) 603 604 initial_min_peak_bool = ( 605 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 606 ) 607 initial_runtime_kendrick_filter = ( 608 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 609 ) 610 611 # Are the following 3 lines redundant? 612 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 613 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 614 False # TODO check this line 615 ) 616 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 617 False 618 ) 619 620 possible_formulas_dict_nm = {} 621 622 for mf in possible_formulas_list: 623 if neutral_molform: 624 # Bin by the nominal m/z of the requested ion hypothesis so 625 # candidates land in the same bin run_search looks them up under. 626 if ion_type == Labels.radical_ion: 627 nm = int(mf.radical_mz) 628 elif ion_type == Labels.adduct_ion and adduct_atom: 629 nm = int(mf.adduct_mz(adduct_atom)) 630 else: 631 nm = int(mf.protonated_mz) 632 else: 633 nm = int(mf.mz_nominal_calc) 634 635 if nm in possible_formulas_dict_nm.keys(): 636 possible_formulas_dict_nm[nm].append(mf) 637 638 else: 639 possible_formulas_dict_nm[nm] = [mf] 640 641 min_abundance = self.mass_spectrum_obj.min_abundance 642 643 ion_type = ion_type 644 645 self.run_search( 646 self.mass_spectrum_obj, 647 possible_formulas_dict_nm, 648 min_abundance, 649 ion_type, 650 self.mass_spectrum_obj.polarity, 651 adduct_atom=adduct_atom, 652 ) 653 654 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 655 initial_min_peak_bool 656 ) 657 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 658 initial_runtime_kendrick_filter 659 ) 660 661 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 662 663 self.sql_db.close() 664 665 return mspeaks 666 667 668class SearchMolecularFormulaWorker: 669 """Class for searching molecular formulas in a mass spectrum. 670 671 Parameters 672 ---------- 673 find_isotopologues : bool, optional 674 Flag to indicate whether to find isotopologues, by default True. 675 676 Attributes 677 ---------- 678 find_isotopologues : bool 679 Flag to indicate whether to find isotopologues. 680 681 Methods 682 ------- 683 * reset_error(). 684 Reset the error variables. 685 * set_last_error(). 686 Set the last error. 687 * find_formulas(). 688 Find the formulas. 689 * calc_error(). 690 Calculate the error. 691 """ 692 693 # TODO add reset error function 694 # needs this wraper to pass the class to multiprocessing 695 696 def __init__(self, find_isotopologues=True): 697 self.find_isotopologues = find_isotopologues 698 699 def __call__(self, args): 700 """Call the find formulas function. 701 702 Parameters 703 ---------- 704 args : tuple 705 The arguments. 706 707 Returns 708 ------- 709 list 710 The list of mass spectrum peaks with assigned molecular formulas. 711 """ 712 return self.find_formulas(*args) # ,args[1] 713 714 def reset_error(self, mass_spectrum_obj): 715 """Reset the error variables. 716 717 Parameters 718 ---------- 719 mass_spectrum_obj : MassSpectrum 720 The mass spectrum object. 721 722 Notes 723 ----- 724 This function resets the error variables for the given mass spectrum object. 725 """ 726 global last_error, last_dif, closest_error, error_average, nbValues 727 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0 728 729 def set_last_error(self, error, mass_spectrum_obj): 730 """Set the last error. 731 732 Parameters 733 ---------- 734 error : float 735 The error. 736 mass_spectrum_obj : MassSpectrum 737 The mass spectrum object. 738 """ 739 # set the changes to the global variables, not internal ones 740 global last_error, last_dif, closest_error, error_average, nbValues 741 742 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 743 dif = error - last_error 744 if dif < last_dif: 745 last_dif = dif 746 closest_error = error 747 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 748 closest_error 749 - mass_spectrum_obj.molecular_search_settings.mz_error_range 750 ) 751 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 752 closest_error 753 + mass_spectrum_obj.molecular_search_settings.mz_error_range 754 ) 755 756 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 757 if error < last_error: 758 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 759 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 760 ) 761 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 762 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 763 ) 764 last_error = error 765 766 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 767 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 768 mass_spectrum_obj.molecular_search_settings.mz_error_average 769 - mass_spectrum_obj.molecular_search_settings.mz_error_range 770 ) 771 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 772 mass_spectrum_obj.molecular_search_settings.mz_error_average 773 + mass_spectrum_obj.molecular_search_settings.mz_error_range 774 ) 775 776 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 777 nbValues += 1 778 error_average = error_average + ((error - error_average) / nbValues) 779 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 780 error_average 781 - mass_spectrum_obj.molecular_search_settings.mz_error_range 782 ) 783 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 784 error_average 785 + mass_spectrum_obj.molecular_search_settings.mz_error_range 786 ) 787 788 else: 789 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 790 pass 791 792 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method 793 794 @staticmethod 795 def calc_error(mz_exp, mz_calc, method="ppm"): 796 """Calculate the error. 797 798 Parameters 799 ---------- 800 mz_exp : float 801 The experimental m/z value. 802 mz_calc : float 803 The calculated m/z value. 804 method : str, optional 805 The method, by default 'ppm'. 806 807 Raises 808 ------- 809 Exception 810 If the method is not ppm or ppb. 811 812 Returns 813 ------- 814 float 815 The error. 816 """ 817 818 if method == "ppm": 819 multi_factor = 1_000_000 820 821 elif method == "ppb": 822 multi_factor = 1_000_000_000 823 824 elif method == "perc": 825 multi_factor = 100 826 827 else: 828 raise Exception( 829 "method needs to be ppm or ppb, you have entered %s" % method 830 ) 831 832 if mz_exp: 833 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 834 835 else: 836 raise Exception("Please set mz_calc first") 837 838 def find_formulas( 839 self, 840 formulas, 841 min_abundance, 842 mass_spectrum_obj, 843 ms_peak, 844 ion_type, 845 ion_charge, 846 adduct_atom=None, 847 ): 848 """Find the formulas. 849 850 Parameters 851 ---------- 852 formulas : list of MolecularFormula 853 The list of molecular formulas. 854 min_abundance : float 855 The minimum abundance threshold. 856 mass_spectrum_obj : MassSpectrum 857 The mass spectrum object. 858 ms_peak : MSPeak 859 The mass spectrum peak. 860 ion_type : str 861 The ion type. 862 ion_charge : int 863 The ion charge. 864 adduct_atom : str, optional 865 The adduct atom, by default None. 866 867 Returns 868 ------- 869 list of MSPeak 870 The list of mass spectrum peaks with assigned molecular formulas. 871 872 Notes 873 ----- 874 Uses the closest error the next search (this is not ideal, it needs to use confidence 875 metric to choose the right candidate then propagate the error using the error from the best candidate). 876 It needs to add s/n to the equation. 877 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 878 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 879 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 880 Method='distance' 881 """ 882 mspeak_assigned_index = list() 883 884 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 885 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 886 887 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 888 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 889 890 # f = open("abundance_error.txt", "a+") 891 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 892 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 893 894 def mass_by_ion_type(possible_formula_obj): 895 if ion_type == Labels.protonated_de_ion: 896 return possible_formula_obj._protonated_mz(ion_charge) 897 898 elif ion_type == Labels.radical_ion: 899 return possible_formula_obj._radical_mz(ion_charge) 900 901 elif ion_type == Labels.adduct_ion and adduct_atom: 902 # Positional argument order differs between formula objects 903 # and SQL links; keywords are unambiguous. 904 return possible_formula_obj._adduct_mz( 905 ion_charge=ion_charge, adduct_atom=adduct_atom 906 ) 907 908 else: 909 # will return externally calculated mz if is set, #use on Bruker Reference list import 910 # if the ion type is known the ion mass based on molecular formula ion type 911 # if ion type is unknow will return neutral mass 912 return possible_formula_obj.mz_calc 913 914 for possible_formula in formulas: 915 if possible_formula: 916 error = self.calc_error( 917 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 918 ) 919 920 # error = possible_formula.mz_error 921 922 if min_ppm_error <= error <= max_ppm_error: 923 # update the error 924 925 self.set_last_error(error, mass_spectrum_obj) 926 927 # add molecular formula match to ms_peak 928 929 # get molecular formula dict from sql obj 930 # formula_dict = pickle.loads(possible_formula.mol_formula) 931 932 formula_dict = possible_formula.to_dict() 933 # create the molecular formula obj to be stored 934 molecular_formula = MolecularFormula( 935 formula_dict, 936 ion_charge, 937 ion_type=ion_type, 938 adduct_atom=adduct_atom, 939 ) 940 # add the molecular formula obj to the mspeak obj 941 # add the mspeak obj and it's index for tracking next assignment step 942 943 if self.find_isotopologues: 944 # calculates isotopologues 945 isotopologues = molecular_formula.isotopologues( 946 min_abundance, 947 ms_peak_abundance, 948 mass_spectrum_obj.dynamic_range, 949 ) 950 951 # search for isotopologues 952 for isotopologue_formula in isotopologues: 953 molecular_formula.expected_isotopologues.append( 954 isotopologue_formula 955 ) 956 # move this outside to improve preformace 957 # we need to increase the search space to -+1 m_z 958 first_index, last_index = ( 959 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 960 isotopologue_formula.mz_nominal_calc 961 ) 962 ) 963 964 for ms_peak_iso in mass_spectrum_obj[ 965 first_index:last_index 966 ]: 967 error = self.calc_error( 968 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 969 ) 970 971 if min_ppm_error <= error <= max_ppm_error: 972 # need to define error distribution for abundance measurements 973 974 # if mass_spectrum_obj.is_centroid: 975 976 abundance_error = self.calc_error( 977 isotopologue_formula.abundance_calc, 978 ms_peak_iso.abundance, 979 method="perc", 980 ) 981 982 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 983 984 # margin of error was set empirically/ needs statistical calculation 985 # of margin of error for the measurement of the abundances 986 if ( 987 min_abun_error 988 <= abundance_error 989 <= max_abun_error 990 ): 991 # update the error 992 993 self.set_last_error(error, mass_spectrum_obj) 994 995 # isotopologue_formula.mz_error = error 996 997 # isotopologue_formula.area_error = area_error 998 999 # isotopologue_formula.abundance_error = abundance_error 1000 1001 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1002 1003 mono_isotopic_formula_index = len(ms_peak) 1004 1005 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1006 1007 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 1008 1009 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 1010 1011 # add molecular formula match to ms_peak 1012 x = ms_peak_iso.add_molecular_formula( 1013 isotopologue_formula 1014 ) 1015 1016 molecular_formula.mspeak_mf_isotopologues_indexes.append( 1017 (ms_peak_iso.index, x) 1018 ) 1019 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 1020 1021 y = ms_peak.add_molecular_formula(molecular_formula) 1022 1023 mspeak_assigned_index.append((ms_peak.index, y)) 1024 1025 return mspeak_assigned_index 1026 1027 1028class SearchMolecularFormulasLC: 1029 """Class for searching molecular formulas in a LC object. 1030 1031 Parameters 1032 ---------- 1033 lcms_obj : LCMSBase 1034 The LCMSBase object. 1035 sql_db : MolForm_SQL, optional 1036 The SQL database object, by default None. 1037 first_hit : bool, optional 1038 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 1039 find_isotopologues : bool, optional 1040 Flag to indicate whether to find isotopologues, by default True. 1041 1042 Methods 1043 ------- 1044 1045 * search_spectra_against_candidates(). 1046 Search a list of mass spectra against a list of candidate formulas with a given ion type, charge, and optional adduct_atom. 1047 * bulk_run_molecular_formula_search(). 1048 Run the molecular formula search on the given list of mass spectra. 1049 Pulls the settings from the LCMSBase object to set ion type and charge to search for. 1050 * run_mass_feature_search(). 1051 Run the molecular formula search on mass features. 1052 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1053 * run_untargeted_worker_ms1(). 1054 Run untargeted molecular formula search on the ms1 mass spectrum. 1055 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 1056 * run_target_worker_ms1(). 1057 Run targeted molecular formula search on the ms1 mass spectrum. 1058 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 1059 """ 1060 1061 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 1062 self.first_hit = first_hit 1063 1064 self.find_isotopologues = find_isotopologues 1065 1066 self.lcms_obj = lcms_obj 1067 1068 # Catch in-place usedAtoms mutations after settings construction 1069 validate_used_atoms_keys( 1070 self.lcms_obj.parameters.mass_spectrum["ms1"].molecular_search.usedAtoms 1071 ) 1072 1073 if not sql_db: 1074 self.sql_db = MolForm_SQL( 1075 url=self.lcms_obj.parameters.mass_spectrum[ 1076 "ms1" 1077 ].molecular_search.url_database 1078 ) 1079 1080 else: 1081 self.sql_db = sql_db 1082 1083 def search_spectra_against_candidates( 1084 self, 1085 mass_spectrum_list, 1086 ms_peaks_list, 1087 candidate_formulas, 1088 ion_type, 1089 ion_charge, 1090 adduct_atom=None, 1091 ): 1092 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 1093 1094 Parameters 1095 ---------- 1096 mass_spectrum_list : list of MassSpectrum 1097 The list of mass spectra to perform the search on. 1098 ms_peaks_list : list of lists of MSPeak objects 1099 The list of mass spectrum peaks to search within each mass spectrum. 1100 candidate_formulas : dict 1101 The candidate formulas. 1102 ion_type : str 1103 The ion type. 1104 ion_charge : int 1105 signed ion charge used for the candidate formulas. 1106 adduct_atom : str, optional 1107 The adduct atom, by default None. 1108 1109 Notes 1110 ----- 1111 This function is designed to be used with the bulk_run_molecular_formula_search function. 1112 """ 1113 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 1114 single_ms_search = SearchMolecularFormulas( 1115 mass_spectrum, 1116 sql_db=self.sql_db, 1117 first_hit=self.first_hit, 1118 find_isotopologues=self.find_isotopologues, 1119 ) 1120 single_ms_search.run_search( 1121 ms_peaks, 1122 candidate_formulas, 1123 mass_spectrum.min_abundance, 1124 ion_type, 1125 ion_charge, 1126 adduct_atom=adduct_atom, 1127 ) 1128 1129 def bulk_run_molecular_formula_search( 1130 self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key="ms1" 1131 ): 1132 """Run the molecular formula search on the given list of mass spectra. 1133 1134 Uses ``min_ion_charge`` / ``max_ion_charge`` from molecular search settings 1135 to search each peak at every signed charge in that absolute range 1136 (defaults 1..1 = single-charge). Does not determine or rewrite peak 1137 ``polarity``; assignment charge is on each ``MolecularFormula``. 1138 1139 Parameters 1140 ---------- 1141 mass_spectrum_list : list of MassSpectrum 1142 The list of mass spectra to search. 1143 ms_peaks_list : list of lists of MSPeak objects 1144 The mass peaks to perform molecular formula search within each mass spectrum 1145 mass_spectrum_setting_key : str, optional 1146 The mass spectrum setting key, by default 'ms1'. 1147 This is used to get the appropriate molecular search settings from the LCMSBase object 1148 """ 1149 if self.lcms_obj.polarity not in ("positive", "negative"): 1150 raise ValueError("Polarity must be either 'positive' or 'negative'") 1151 1152 # Check that the length of the mass spectrum list and the ms_peaks list are the same 1153 if len(mass_spectrum_list) != len(ms_peaks_list): 1154 raise ValueError( 1155 "The length of the mass spectrum list and the ms_peaks list must be the same" 1156 ) 1157 1158 mol_settings = self.lcms_obj.parameters.mass_spectrum[ 1159 mass_spectrum_setting_key 1160 ].molecular_search 1161 # Re-check after possible post-construction mutation of settings fields 1162 mol_settings.validate_ion_charge_settings() 1163 search_charges = SearchMolecularFormulas.ion_charges_for_search( 1164 self.lcms_obj.polarity, 1165 min_ion_charge=mol_settings.min_ion_charge, 1166 max_ion_charge=mol_settings.max_ion_charge, 1167 ) 1168 1169 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 1170 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 1171 1172 # reset average error, only relevant if average mass error method is being used 1173 SearchMolecularFormulaWorker( 1174 find_isotopologues=self.find_isotopologues 1175 ).reset_error(mass_spectrum_list[0]) 1176 1177 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 1178 classes = MolecularCombinations(self.sql_db).runworker( 1179 mol_settings, print_time=mol_settings.verbose_processing 1180 ) 1181 1182 try: 1183 1184 def apply_job(job): 1185 self.search_spectra_against_candidates( 1186 mass_spectrum_list=mass_spectrum_list, 1187 ms_peaks_list=ms_peaks_list, 1188 candidate_formulas=job.candidate_formulas, 1189 ion_type=job.ion_type, 1190 ion_charge=job.ion_charge, 1191 adduct_atom=job.adduct_atom, 1192 ) 1193 1194 SearchMolecularFormulas._run_formula_search_jobs( 1195 classes, 1196 nominal_mzs, 1197 mol_settings, 1198 search_charges, 1199 self.sql_db, 1200 apply_job, 1201 ) 1202 finally: 1203 self.sql_db.close() 1204 1205 def run_mass_feature_search(self): 1206 """Run the molecular formula search on the mass features. 1207 1208 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1209 Multi-charge range comes from molecular search ``min_ion_charge`` / 1210 ``max_ion_charge``. Peak ``polarity`` is not rewritten. 1211 """ 1212 mass_features_df = self.lcms_obj.mass_features_to_df() 1213 1214 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1215 scan_list = mass_features_df.apex_scan.unique() 1216 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1217 ms_peaks = [] 1218 for scan in scan_list: 1219 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1220 peaks_to_search = [ 1221 self.lcms_obj.mass_features[x].ms1_peak 1222 for x in mf_df_scan.index.tolist() 1223 ] 1224 ms_peaks.append(peaks_to_search) 1225 1226 # Run the molecular formula search 1227 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks) 1228 1229 def run_untargeted_worker_ms1(self): 1230 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1231 raise NotImplementedError( 1232 "run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater" 1233 ) 1234 1235 def run_target_worker_ms1(self): 1236 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1237 raise NotImplementedError( 1238 "run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater" 1239 )
25class FormulaSearchJob(NamedTuple): 26 """One ion-type candidate batch for molecular formula search. 27 28 Parameters 29 ---------- 30 ion_charge : int 31 Signed search charge for this batch. 32 ion_type : str 33 ``Labels`` ion type (protonated/deprotonated, radical, or adduct). 34 adduct_atom : str or None 35 Adduct element symbol when ``ion_type`` is adduct; otherwise None. 36 candidate_formulas : dict 37 Mapping of nominal m/z to formula objects, as consumed by ``run_search``. 38 progress : str 39 tqdm description for this class / ion type / charge. 40 """ 41 42 ion_charge: int 43 ion_type: str 44 adduct_atom: Optional[str] 45 candidate_formulas: dict 46 progress: str
One ion-type candidate batch for molecular formula search.
Parameters
- ion_charge (int): Signed search charge for this batch.
- ion_type (str):
Labelsion type (protonated/deprotonated, radical, or adduct). - adduct_atom (str or None):
Adduct element symbol when
ion_typeis adduct; otherwise None. - candidate_formulas (dict):
Mapping of nominal m/z to formula objects, as consumed by
run_search. - progress (str): tqdm description for this class / ion type / charge.
49class SearchMolecularFormulas: 50 """Class for searching molecular formulas in a mass spectrum. 51 52 Parameters 53 ---------- 54 mass_spectrum_obj : MassSpectrum 55 The mass spectrum object. 56 sql_db : MolForm_SQL, optional 57 The SQL database object, by default None. 58 first_hit : bool, optional 59 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 60 find_isotopologues : bool, optional 61 Flag to indicate whether to find isotopologues, by default True. 62 63 Attributes 64 ---------- 65 mass_spectrum_obj : MassSpectrum 66 The mass spectrum object. 67 sql_db : MolForm_SQL 68 The SQL database object. 69 first_hit : bool 70 Flag to indicate whether to skip peaks that already have a molecular formula assigned. 71 find_isotopologues : bool 72 Flag to indicate whether to find isotopologues. 73 74 75 Methods 76 ------- 77 * run_search(). 78 Run the molecular formula search. 79 * run_worker_mass_spectrum(). 80 Run the molecular formula search on the mass spectrum object. 81 * run_worker_ms_peaks(). 82 Run the molecular formula search on the given list of mass spectrum peaks. 83 * database_to_dict(). 84 Convert the database results to a dictionary. 85 * run_molecular_formula(). 86 Run the molecular formula search on the given list of mass spectrum peaks. 87 * search_mol_formulas(). 88 Search for molecular formulas in the mass spectrum. 89 * ion_charges_for_search(). 90 Build the signed charge tuple from polarity and absolute min/max settings. 91 92 """ 93 94 def __init__( 95 self, 96 mass_spectrum_obj, 97 sql_db=None, 98 first_hit: bool = False, 99 find_isotopologues: bool = True, 100 ): 101 self.first_hit = first_hit 102 103 self.find_isotopologues = find_isotopologues 104 105 self.mass_spectrum_obj = mass_spectrum_obj 106 107 # Catch in-place usedAtoms mutations after settings construction 108 validate_used_atoms_keys(mass_spectrum_obj.molecular_search_settings.usedAtoms) 109 110 if not sql_db: 111 self.sql_db = MolForm_SQL( 112 url=mass_spectrum_obj.molecular_search_settings.url_database 113 ) 114 115 else: 116 self.sql_db = sql_db 117 118 def __enter__(self): 119 """Open the SQL database connection.""" 120 return self 121 122 def __exit__(self, exc_type, exc_val, exc_tb): 123 """Close the SQL database connection.""" 124 self.sql_db.close() 125 126 return False 127 128 @staticmethod 129 def ion_charges_for_search( 130 polarity: Union[int, str], 131 min_ion_charge: int = 1, 132 max_ion_charge: int = 1, 133 ) -> Tuple[int, ...]: 134 """Signed ion charges to use for molecular formula search. 135 136 Absolute charges from ``min_ion_charge`` through ``max_ion_charge`` 137 (inclusive) are multiplied by the polarity sign. No charge-state 138 determination is performed; every selected peak is searched at each 139 returned charge and candidates are ranked by formula scores. 140 141 Parameters 142 ---------- 143 polarity : int or str 144 Mass-spectrum polarity as ``+1`` / ``-1``, or LCMS polarity string 145 ``"positive"`` / ``"negative"``. 146 min_ion_charge : int, optional 147 Minimum absolute charge (default 1). 148 max_ion_charge : int, optional 149 Maximum absolute charge (default 1). Must be >= ``min_ion_charge``. 150 151 Returns 152 ------- 153 tuple of int 154 Signed charges sorted by ``(abs(z), z)`` so lower |z| is first 155 (relevant when ``first_hit=True``). 156 157 Raises 158 ------ 159 ValueError 160 If polarity is invalid, min < 1, or max < min. 161 """ 162 min_z = int(min_ion_charge) 163 max_z = int(max_ion_charge) 164 if min_z < 1: 165 raise ValueError("min_ion_charge must be >= 1") 166 if max_z < min_z: 167 raise ValueError("max_ion_charge must be >= min_ion_charge") 168 169 if isinstance(polarity, str): 170 p = polarity.strip().lower() 171 if p == "positive": 172 sign = 1 173 elif p == "negative": 174 sign = -1 175 else: 176 raise ValueError( 177 "polarity string must be 'positive' or 'negative', " 178 f"got {polarity!r}" 179 ) 180 else: 181 pol = int(polarity) 182 if pol == 0: 183 raise ValueError("polarity must be non-zero") 184 sign = 1 if pol > 0 else -1 185 186 charges = [sign * z for z in range(min_z, max_z + 1)] 187 return tuple(sorted(charges, key=lambda z: (abs(z), z))) 188 189 @staticmethod 190 def _iter_ion_type_jobs(settings, dict_res, classe_str, ion_charge): 191 """Yield ion-type candidate jobs for one heteroatom class at one charge. 192 193 Encodes shared search policy: protonated and radical follow the 194 corresponding settings flags; adduct search runs only when adducts 195 are enabled and ``abs(ion_charge) == 1``. Empty candidate lists and 196 missing ``dict_res`` keys are skipped. 197 198 Parameters 199 ---------- 200 settings : MolecularFormulaSearchSettings 201 Molecular formula search settings (ion-type flags). 202 dict_res : dict 203 Result of ``database_to_dict`` for this charge: protonated/radical 204 map class string to a nominal-m/z query dict; adduct maps adduct 205 atom to that class mapping. 206 classe_str : str 207 Heteroatom class key as stored in ``dict_res``. 208 ion_charge : int 209 Signed ion charge for this batch. 210 211 Yields 212 ------ 213 FormulaSearchJob 214 One job per enabled ion type (and per adduct atom) with candidates. 215 """ 216 if settings.isProtonated: 217 by_class = (dict_res.get(Labels.protonated_de_ion) or {}).get(classe_str) 218 if by_class: 219 yield FormulaSearchJob( 220 ion_charge=ion_charge, 221 ion_type=Labels.protonated_de_ion, 222 adduct_atom=None, 223 candidate_formulas=by_class, 224 progress=( 225 "Started molecular formula search for class %s, " 226 "(de)protonated z=%s " % (classe_str, ion_charge) 227 ), 228 ) 229 230 if settings.isRadical: 231 by_class = (dict_res.get(Labels.radical_ion) or {}).get(classe_str) 232 if by_class: 233 yield FormulaSearchJob( 234 ion_charge=ion_charge, 235 ion_type=Labels.radical_ion, 236 adduct_atom=None, 237 candidate_formulas=by_class, 238 progress=( 239 "Started molecular formula search for class %s, " 240 "radical z=%s " % (classe_str, ion_charge) 241 ), 242 ) 243 244 # Adduct search only at |z|==1; multi-charge adducts are not modeled. 245 if settings.isAdduct and abs(ion_charge) == 1: 246 dict_atoms_formulas = dict_res.get(Labels.adduct_ion) or {} 247 for adduct_atom, dict_by_class in dict_atoms_formulas.items(): 248 by_class = dict_by_class.get(classe_str) 249 if by_class: 250 yield FormulaSearchJob( 251 ion_charge=ion_charge, 252 ion_type=Labels.adduct_ion, 253 adduct_atom=adduct_atom, 254 candidate_formulas=by_class, 255 progress=( 256 "Started molecular formula search for class %s, " 257 "adduct z=%s " % (classe_str, ion_charge) 258 ), 259 ) 260 261 @staticmethod 262 def _run_formula_search_jobs( 263 classes, 264 nominal_mzs, 265 mf_search_settings, 266 search_charges, 267 sql_db, 268 apply_fn: Callable[[FormulaSearchJob], None], 269 ): 270 """Run charge × class × ion-type search, applying each candidate batch. 271 272 Loads the formula database once per charge per class chunk. Does not 273 close ``sql_db``. tqdm iterates class chunks; the description is the 274 job ``progress`` string. 275 276 Parameters 277 ---------- 278 classes : list 279 Heteroatom class tuples from ``MolecularCombinations.runworker``. 280 nominal_mzs : list 281 Nominal m/z values to load from the database. 282 mf_search_settings : MolecularFormulaSearchSettings 283 Search settings (chunk size, ion-type flags, verbosity). 284 search_charges : sequence of int 285 Signed charges from ``ion_charges_for_search``. 286 sql_db : MolForm_SQL 287 Open formula database connection. 288 apply_fn : callable 289 Called with each ``FormulaSearchJob``. Path-specific: DI uses 290 ``run_search``; LC uses ``search_spectra_against_candidates``. 291 """ 292 verbose = mf_search_settings.verbose_processing 293 for classe_chunk in chunks(classes, mf_search_settings.db_chunk_size): 294 classes_str_list = [class_tuple[0] for class_tuple in classe_chunk] 295 for ion_charge in search_charges: 296 dict_res = SearchMolecularFormulas.database_to_dict( 297 classes_str_list, 298 nominal_mzs, 299 mf_search_settings, 300 ion_charge, 301 sql_db=sql_db, 302 ) 303 pbar = tqdm.tqdm(classe_chunk, disable=not verbose) 304 for classe_tuple in pbar: 305 classe_str = classe_tuple[0] 306 for job in SearchMolecularFormulas._iter_ion_type_jobs( 307 mf_search_settings, dict_res, classe_str, ion_charge 308 ): 309 pbar.set_description_str(desc=job.progress, refresh=True) 310 apply_fn(job) 311 312 def run_search( 313 self, 314 mspeaks: list, 315 query: dict, 316 min_abundance: float, 317 ion_type: str, 318 ion_charge: int, 319 adduct_atom=None, 320 ): 321 """Run the molecular formula search. 322 323 Parameters 324 ---------- 325 mspeaks : list of MSPeak 326 The list of mass spectrum peaks. 327 query : dict 328 The query dictionary containing the possible molecular formulas. 329 min_abundance : float 330 The minimum abundance threshold. 331 ion_type : str 332 The ion type. 333 ion_charge : int 334 The ion charge. 335 adduct_atom : str, optional 336 The adduct atom, by default None. 337 """ 338 339 def get_formulas(nominal_overlay: float = 0.1): 340 """ 341 Get the list of formulas based on the nominal overlay. 342 343 Parameters 344 ---------- 345 nominal_overlay : float, optional 346 The nominal overlay, by default 0.1. 347 348 Returns 349 ------- 350 list 351 The list of formulas. 352 """ 353 nominal_mz = ms_peak.nominal_mz_exp 354 355 defect_mass = ms_peak.mz_exp - nominal_mz 356 nominal_masses = [nominal_mz] 357 358 if (defect_mass) >= 1 - nominal_overlay: 359 nominal_masses.append(nominal_mz + 1) 360 elif (defect_mass) <= nominal_overlay: 361 nominal_masses.append(nominal_mz - 1) 362 363 list_formulas_candidates = [] 364 365 for nominal_mass in nominal_masses: 366 if nominal_mass in query.keys(): 367 list_formulas_candidates.extend(query.get(nominal_mass)) 368 369 return list_formulas_candidates 370 371 all_assigned_indexes = list() 372 373 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 374 375 search_molfrom = SearchMolecularFormulaWorker( 376 find_isotopologues=self.find_isotopologues 377 ) 378 379 for ms_peak in mspeaks: 380 # already assigned a molecular formula 381 if self.first_hit: 382 if ms_peak.is_assigned: 383 continue 384 385 ms_peak_indexes = search_molfrom.find_formulas( 386 get_formulas(), 387 min_abundance, 388 self.mass_spectrum_obj, 389 ms_peak, 390 ion_type, 391 ion_charge, 392 adduct_atom, 393 ) 394 395 all_assigned_indexes.extend(ms_peak_indexes) 396 397 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 398 399 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 400 401 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 402 # filter per min peaks per mono isotopic class 403 404 def run_worker_mass_spectrum(self): 405 """Run the molecular formula search on the mass spectrum object.""" 406 self.run_molecular_formula( 407 self.mass_spectrum_obj.sort_by_abundance(), 408 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 409 ) 410 411 def run_worker_ms_peaks(self, ms_peaks): 412 """Run the molecular formula search on the given list of mass spectrum peaks. 413 414 Parameters 415 ---------- 416 ms_peaks : list of MSPeak 417 The list of mass spectrum peaks. 418 """ 419 self.run_molecular_formula( 420 ms_peaks, 421 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 422 ) 423 424 @staticmethod 425 def database_to_dict( 426 classe_str_list, 427 nominal_mzs, 428 mf_search_settings, 429 ion_charge, 430 sql_db=None, 431 ): 432 """Convert the database results to a dictionary. 433 434 Parameters 435 ---------- 436 classe_str_list : list 437 The list of class strings. 438 nominal_mzs : list 439 The list of nominal m/z values. 440 mf_search_settings : MolecularFormulaSearchSettings 441 The molecular formula search settings. 442 ion_charge : int 443 The ion charge. 444 sql_db : MolForm_SQL, optional 445 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 446 447 Returns 448 ------- 449 dict 450 The dictionary containing the database results. 451 """ 452 owns_db = sql_db is None 453 if owns_db: 454 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 455 456 try: 457 dict_res = {} 458 459 if mf_search_settings.isProtonated: 460 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 461 classe_str_list, 462 Labels.protonated_de_ion, 463 nominal_mzs, 464 ion_charge, 465 mf_search_settings, 466 ) 467 468 if mf_search_settings.isRadical: 469 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 470 classe_str_list, 471 Labels.radical_ion, 472 nominal_mzs, 473 ion_charge, 474 mf_search_settings, 475 ) 476 477 if mf_search_settings.isAdduct: 478 adduct_list = ( 479 mf_search_settings.adduct_atoms_neg 480 if ion_charge < 0 481 else mf_search_settings.adduct_atoms_pos 482 ) 483 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 484 classe_str_list, 485 Labels.adduct_ion, 486 nominal_mzs, 487 ion_charge, 488 mf_search_settings, 489 adducts=adduct_list, 490 ) 491 492 return dict_res 493 finally: 494 if owns_db: 495 sql_db.close() 496 497 @timeit(print_time=True) 498 def run_molecular_formula(self, ms_peaks, **kwargs): 499 """Run the molecular formula search on the given list of mass spectrum peaks. 500 501 Parameters 502 ---------- 503 ms_peaks : list of MSPeak 504 The list of mass spectrum peaks. 505 **kwargs 506 Additional keyword arguments. 507 Most notably, print_time, which is a boolean flag to indicate whether to print the time 508 and passed to the timeit decorator. 509 """ 510 settings = self.mass_spectrum_obj.molecular_search_settings 511 # Re-check after possible post-construction mutation of settings fields 512 settings.validate_ion_charge_settings() 513 # Polarity must come from the mass spectrum (DI often -1 from data). 514 # Do not default to +1; LC already requires polarity on the LCMS object. 515 polarity_raw = self.mass_spectrum_obj.polarity 516 if polarity_raw is None: 517 raise ValueError( 518 "Mass spectrum polarity must be set from the data before " 519 "formula search (e.g. +1 or -1). There is no default polarity." 520 ) 521 try: 522 polarity = int(polarity_raw) 523 except (TypeError, ValueError) as exc: 524 raise ValueError( 525 "Mass spectrum polarity must be an integer +1 or -1 " 526 f"(got {polarity_raw!r})." 527 ) from exc 528 if polarity == 0: 529 raise ValueError("Mass spectrum polarity must be non-zero (+1 or -1).") 530 # Multi-charge formula search: expand absolute min..max (default 1..1). 531 # Does not rewrite MSPeak.polarity (remains ±1). 532 search_charges = self.ion_charges_for_search( 533 polarity, 534 min_ion_charge=settings.min_ion_charge, 535 max_ion_charge=settings.max_ion_charge, 536 ) 537 min_abundance = self.mass_spectrum_obj.min_abundance 538 nominal_mzs = self.mass_spectrum_obj.nominal_mz 539 540 # reset average error, only relevant is average mass error method is being used 541 SearchMolecularFormulaWorker( 542 find_isotopologues=self.find_isotopologues 543 ).reset_error(self.mass_spectrum_obj) 544 545 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 546 classes = MolecularCombinations(self.sql_db).runworker( 547 settings, print_time=settings.verbose_processing 548 ) 549 550 def apply_job(job): 551 self.run_search( 552 ms_peaks, 553 job.candidate_formulas, 554 min_abundance, 555 job.ion_type, 556 job.ion_charge, 557 adduct_atom=job.adduct_atom, 558 ) 559 560 self._run_formula_search_jobs( 561 classes, 562 nominal_mzs, 563 settings, 564 search_charges, 565 self.sql_db, 566 apply_job, 567 ) 568 self.sql_db.close() 569 570 def search_mol_formulas( 571 self, 572 possible_formulas_list: List[MolecularFormula], 573 ion_type: str, 574 neutral_molform=True, 575 find_isotopologues=True, 576 adduct_atom=None, 577 ) -> List[_MSPeak]: 578 """Search for molecular formulas in the mass spectrum. 579 580 Parameters 581 ---------- 582 possible_formulas_list : list of MolecularFormula 583 The list of possible molecular formulas. 584 ion_type : str 585 The ion type. 586 neutral_molform : bool, optional 587 Flag to indicate whether the molecular formulas are neutral, by default True. 588 find_isotopologues : bool, optional 589 Flag to indicate whether to find isotopologues, by default True. 590 adduct_atom : str, optional 591 The adduct atom, by default None. 592 593 Returns 594 ------- 595 list of MSPeak 596 The list of mass spectrum peaks with assigned molecular formulas. 597 """ 598 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 599 # if that is the case than turn neutral_molform off 600 601 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 602 self.mass_spectrum_obj 603 ) 604 605 initial_min_peak_bool = ( 606 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 607 ) 608 initial_runtime_kendrick_filter = ( 609 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 610 ) 611 612 # Are the following 3 lines redundant? 613 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 614 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 615 False # TODO check this line 616 ) 617 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 618 False 619 ) 620 621 possible_formulas_dict_nm = {} 622 623 for mf in possible_formulas_list: 624 if neutral_molform: 625 # Bin by the nominal m/z of the requested ion hypothesis so 626 # candidates land in the same bin run_search looks them up under. 627 if ion_type == Labels.radical_ion: 628 nm = int(mf.radical_mz) 629 elif ion_type == Labels.adduct_ion and adduct_atom: 630 nm = int(mf.adduct_mz(adduct_atom)) 631 else: 632 nm = int(mf.protonated_mz) 633 else: 634 nm = int(mf.mz_nominal_calc) 635 636 if nm in possible_formulas_dict_nm.keys(): 637 possible_formulas_dict_nm[nm].append(mf) 638 639 else: 640 possible_formulas_dict_nm[nm] = [mf] 641 642 min_abundance = self.mass_spectrum_obj.min_abundance 643 644 ion_type = ion_type 645 646 self.run_search( 647 self.mass_spectrum_obj, 648 possible_formulas_dict_nm, 649 min_abundance, 650 ion_type, 651 self.mass_spectrum_obj.polarity, 652 adduct_atom=adduct_atom, 653 ) 654 655 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 656 initial_min_peak_bool 657 ) 658 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 659 initial_runtime_kendrick_filter 660 ) 661 662 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 663 664 self.sql_db.close() 665 666 return mspeaks
Class for searching molecular formulas in a mass spectrum.
Parameters
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None.
- first_hit (bool, optional): Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Attributes
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- sql_db (MolForm_SQL): The SQL database object.
- first_hit (bool): Flag to indicate whether to skip peaks that already have a molecular formula assigned.
- find_isotopologues (bool): Flag to indicate whether to find isotopologues.
Methods
- run_search(). Run the molecular formula search.
- run_worker_mass_spectrum(). Run the molecular formula search on the mass spectrum object.
- run_worker_ms_peaks(). Run the molecular formula search on the given list of mass spectrum peaks.
- database_to_dict(). Convert the database results to a dictionary.
- run_molecular_formula(). Run the molecular formula search on the given list of mass spectrum peaks.
- search_mol_formulas(). Search for molecular formulas in the mass spectrum.
- ion_charges_for_search(). Build the signed charge tuple from polarity and absolute min/max settings.
94 def __init__( 95 self, 96 mass_spectrum_obj, 97 sql_db=None, 98 first_hit: bool = False, 99 find_isotopologues: bool = True, 100 ): 101 self.first_hit = first_hit 102 103 self.find_isotopologues = find_isotopologues 104 105 self.mass_spectrum_obj = mass_spectrum_obj 106 107 # Catch in-place usedAtoms mutations after settings construction 108 validate_used_atoms_keys(mass_spectrum_obj.molecular_search_settings.usedAtoms) 109 110 if not sql_db: 111 self.sql_db = MolForm_SQL( 112 url=mass_spectrum_obj.molecular_search_settings.url_database 113 ) 114 115 else: 116 self.sql_db = sql_db
128 @staticmethod 129 def ion_charges_for_search( 130 polarity: Union[int, str], 131 min_ion_charge: int = 1, 132 max_ion_charge: int = 1, 133 ) -> Tuple[int, ...]: 134 """Signed ion charges to use for molecular formula search. 135 136 Absolute charges from ``min_ion_charge`` through ``max_ion_charge`` 137 (inclusive) are multiplied by the polarity sign. No charge-state 138 determination is performed; every selected peak is searched at each 139 returned charge and candidates are ranked by formula scores. 140 141 Parameters 142 ---------- 143 polarity : int or str 144 Mass-spectrum polarity as ``+1`` / ``-1``, or LCMS polarity string 145 ``"positive"`` / ``"negative"``. 146 min_ion_charge : int, optional 147 Minimum absolute charge (default 1). 148 max_ion_charge : int, optional 149 Maximum absolute charge (default 1). Must be >= ``min_ion_charge``. 150 151 Returns 152 ------- 153 tuple of int 154 Signed charges sorted by ``(abs(z), z)`` so lower |z| is first 155 (relevant when ``first_hit=True``). 156 157 Raises 158 ------ 159 ValueError 160 If polarity is invalid, min < 1, or max < min. 161 """ 162 min_z = int(min_ion_charge) 163 max_z = int(max_ion_charge) 164 if min_z < 1: 165 raise ValueError("min_ion_charge must be >= 1") 166 if max_z < min_z: 167 raise ValueError("max_ion_charge must be >= min_ion_charge") 168 169 if isinstance(polarity, str): 170 p = polarity.strip().lower() 171 if p == "positive": 172 sign = 1 173 elif p == "negative": 174 sign = -1 175 else: 176 raise ValueError( 177 "polarity string must be 'positive' or 'negative', " 178 f"got {polarity!r}" 179 ) 180 else: 181 pol = int(polarity) 182 if pol == 0: 183 raise ValueError("polarity must be non-zero") 184 sign = 1 if pol > 0 else -1 185 186 charges = [sign * z for z in range(min_z, max_z + 1)] 187 return tuple(sorted(charges, key=lambda z: (abs(z), z)))
Signed ion charges to use for molecular formula search.
Absolute charges from min_ion_charge through max_ion_charge
(inclusive) are multiplied by the polarity sign. No charge-state
determination is performed; every selected peak is searched at each
returned charge and candidates are ranked by formula scores.
Parameters
- polarity (int or str):
Mass-spectrum polarity as
+1/-1, or LCMS polarity string"positive"/"negative". - min_ion_charge (int, optional): Minimum absolute charge (default 1).
- max_ion_charge (int, optional):
Maximum absolute charge (default 1). Must be >=
min_ion_charge.
Returns
- tuple of int: Signed charges sorted by
(abs(z), z)so lower |z| is first (relevant whenfirst_hit=True).
Raises
- ValueError: If polarity is invalid, min < 1, or max < min.
312 def run_search( 313 self, 314 mspeaks: list, 315 query: dict, 316 min_abundance: float, 317 ion_type: str, 318 ion_charge: int, 319 adduct_atom=None, 320 ): 321 """Run the molecular formula search. 322 323 Parameters 324 ---------- 325 mspeaks : list of MSPeak 326 The list of mass spectrum peaks. 327 query : dict 328 The query dictionary containing the possible molecular formulas. 329 min_abundance : float 330 The minimum abundance threshold. 331 ion_type : str 332 The ion type. 333 ion_charge : int 334 The ion charge. 335 adduct_atom : str, optional 336 The adduct atom, by default None. 337 """ 338 339 def get_formulas(nominal_overlay: float = 0.1): 340 """ 341 Get the list of formulas based on the nominal overlay. 342 343 Parameters 344 ---------- 345 nominal_overlay : float, optional 346 The nominal overlay, by default 0.1. 347 348 Returns 349 ------- 350 list 351 The list of formulas. 352 """ 353 nominal_mz = ms_peak.nominal_mz_exp 354 355 defect_mass = ms_peak.mz_exp - nominal_mz 356 nominal_masses = [nominal_mz] 357 358 if (defect_mass) >= 1 - nominal_overlay: 359 nominal_masses.append(nominal_mz + 1) 360 elif (defect_mass) <= nominal_overlay: 361 nominal_masses.append(nominal_mz - 1) 362 363 list_formulas_candidates = [] 364 365 for nominal_mass in nominal_masses: 366 if nominal_mass in query.keys(): 367 list_formulas_candidates.extend(query.get(nominal_mass)) 368 369 return list_formulas_candidates 370 371 all_assigned_indexes = list() 372 373 # molecular_search_settings = self.mass_spectrum_obj.molecular_search_settings 374 375 search_molfrom = SearchMolecularFormulaWorker( 376 find_isotopologues=self.find_isotopologues 377 ) 378 379 for ms_peak in mspeaks: 380 # already assigned a molecular formula 381 if self.first_hit: 382 if ms_peak.is_assigned: 383 continue 384 385 ms_peak_indexes = search_molfrom.find_formulas( 386 get_formulas(), 387 min_abundance, 388 self.mass_spectrum_obj, 389 ms_peak, 390 ion_type, 391 ion_charge, 392 adduct_atom, 393 ) 394 395 all_assigned_indexes.extend(ms_peak_indexes) 396 397 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_isotopologue(all_assigned_indexes, self.mass_spectrum_obj) 398 399 # all_assigned_indexes = MolecularFormulaSearchFilters().filter_kendrick(all_assigned_indexes, self.mass_spectrum_obj) 400 401 # MolecularFormulaSearchFilters().check_min_peaks(all_assigned_indexes, self.mass_spectrum_obj) 402 # filter per min peaks per mono isotopic class
Run the molecular formula search.
Parameters
- mspeaks (list of MSPeak): The list of mass spectrum peaks.
- query (dict): The query dictionary containing the possible molecular formulas.
- min_abundance (float): The minimum abundance threshold.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge.
- adduct_atom (str, optional): The adduct atom, by default None.
404 def run_worker_mass_spectrum(self): 405 """Run the molecular formula search on the mass spectrum object.""" 406 self.run_molecular_formula( 407 self.mass_spectrum_obj.sort_by_abundance(), 408 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 409 )
Run the molecular formula search on the mass spectrum object.
411 def run_worker_ms_peaks(self, ms_peaks): 412 """Run the molecular formula search on the given list of mass spectrum peaks. 413 414 Parameters 415 ---------- 416 ms_peaks : list of MSPeak 417 The list of mass spectrum peaks. 418 """ 419 self.run_molecular_formula( 420 ms_peaks, 421 print_time=self.mass_spectrum_obj.molecular_search_settings.verbose_processing, 422 )
Run the molecular formula search on the given list of mass spectrum peaks.
Parameters
- ms_peaks (list of MSPeak): The list of mass spectrum peaks.
424 @staticmethod 425 def database_to_dict( 426 classe_str_list, 427 nominal_mzs, 428 mf_search_settings, 429 ion_charge, 430 sql_db=None, 431 ): 432 """Convert the database results to a dictionary. 433 434 Parameters 435 ---------- 436 classe_str_list : list 437 The list of class strings. 438 nominal_mzs : list 439 The list of nominal m/z values. 440 mf_search_settings : MolecularFormulaSearchSettings 441 The molecular formula search settings. 442 ion_charge : int 443 The ion charge. 444 sql_db : MolForm_SQL, optional 445 The SQL database object, by default None. If None, a new MolForm_SQL object will be created. 446 447 Returns 448 ------- 449 dict 450 The dictionary containing the database results. 451 """ 452 owns_db = sql_db is None 453 if owns_db: 454 sql_db = MolForm_SQL(url=mf_search_settings.url_database) 455 456 try: 457 dict_res = {} 458 459 if mf_search_settings.isProtonated: 460 dict_res[Labels.protonated_de_ion] = sql_db.get_dict_by_classes( 461 classe_str_list, 462 Labels.protonated_de_ion, 463 nominal_mzs, 464 ion_charge, 465 mf_search_settings, 466 ) 467 468 if mf_search_settings.isRadical: 469 dict_res[Labels.radical_ion] = sql_db.get_dict_by_classes( 470 classe_str_list, 471 Labels.radical_ion, 472 nominal_mzs, 473 ion_charge, 474 mf_search_settings, 475 ) 476 477 if mf_search_settings.isAdduct: 478 adduct_list = ( 479 mf_search_settings.adduct_atoms_neg 480 if ion_charge < 0 481 else mf_search_settings.adduct_atoms_pos 482 ) 483 dict_res[Labels.adduct_ion] = sql_db.get_dict_by_classes( 484 classe_str_list, 485 Labels.adduct_ion, 486 nominal_mzs, 487 ion_charge, 488 mf_search_settings, 489 adducts=adduct_list, 490 ) 491 492 return dict_res 493 finally: 494 if owns_db: 495 sql_db.close()
Convert the database results to a dictionary.
Parameters
- classe_str_list (list): The list of class strings.
- nominal_mzs (list): The list of nominal m/z values.
- mf_search_settings (MolecularFormulaSearchSettings): The molecular formula search settings.
- ion_charge (int): The ion charge.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None. If None, a new MolForm_SQL object will be created.
Returns
- dict: The dictionary containing the database results.
39 def timed(*args, **kw): 40 # Extract print_time from kwargs if provided 41 local_print_time = kw.pop("print_time", print_time) 42 ts = time.time() 43 result = method(*args, **kw) 44 te = time.time() 45 if "log_time" in kw: 46 name = kw.get("log_name", method.__name__.upper()) 47 kw["log_time"][name] = int((te - ts) * 1000) 48 elif local_print_time: 49 print("%r %2.2f ms" % (method.__name__, (te - ts) * 1000)) 50 return result
Run the molecular formula search on the given list of mass spectrum peaks.
Parameters
- ms_peaks (list of MSPeak): The list of mass spectrum peaks.
- **kwargs: Additional keyword arguments. Most notably, print_time, which is a boolean flag to indicate whether to print the time and passed to the timeit decorator.
570 def search_mol_formulas( 571 self, 572 possible_formulas_list: List[MolecularFormula], 573 ion_type: str, 574 neutral_molform=True, 575 find_isotopologues=True, 576 adduct_atom=None, 577 ) -> List[_MSPeak]: 578 """Search for molecular formulas in the mass spectrum. 579 580 Parameters 581 ---------- 582 possible_formulas_list : list of MolecularFormula 583 The list of possible molecular formulas. 584 ion_type : str 585 The ion type. 586 neutral_molform : bool, optional 587 Flag to indicate whether the molecular formulas are neutral, by default True. 588 find_isotopologues : bool, optional 589 Flag to indicate whether to find isotopologues, by default True. 590 adduct_atom : str, optional 591 The adduct atom, by default None. 592 593 Returns 594 ------- 595 list of MSPeak 596 The list of mass spectrum peaks with assigned molecular formulas. 597 """ 598 # neutral_molform: some reference files already present the formula on ion mode, for instance, bruker reference files 599 # if that is the case than turn neutral_molform off 600 601 SearchMolecularFormulaWorker(find_isotopologues=find_isotopologues).reset_error( 602 self.mass_spectrum_obj 603 ) 604 605 initial_min_peak_bool = ( 606 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter 607 ) 608 initial_runtime_kendrick_filter = ( 609 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter 610 ) 611 612 # Are the following 3 lines redundant? 613 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = False 614 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 615 False # TODO check this line 616 ) 617 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 618 False 619 ) 620 621 possible_formulas_dict_nm = {} 622 623 for mf in possible_formulas_list: 624 if neutral_molform: 625 # Bin by the nominal m/z of the requested ion hypothesis so 626 # candidates land in the same bin run_search looks them up under. 627 if ion_type == Labels.radical_ion: 628 nm = int(mf.radical_mz) 629 elif ion_type == Labels.adduct_ion and adduct_atom: 630 nm = int(mf.adduct_mz(adduct_atom)) 631 else: 632 nm = int(mf.protonated_mz) 633 else: 634 nm = int(mf.mz_nominal_calc) 635 636 if nm in possible_formulas_dict_nm.keys(): 637 possible_formulas_dict_nm[nm].append(mf) 638 639 else: 640 possible_formulas_dict_nm[nm] = [mf] 641 642 min_abundance = self.mass_spectrum_obj.min_abundance 643 644 ion_type = ion_type 645 646 self.run_search( 647 self.mass_spectrum_obj, 648 possible_formulas_dict_nm, 649 min_abundance, 650 ion_type, 651 self.mass_spectrum_obj.polarity, 652 adduct_atom=adduct_atom, 653 ) 654 655 self.mass_spectrum_obj.molecular_search_settings.use_min_peaks_filter = ( 656 initial_min_peak_bool 657 ) 658 self.mass_spectrum_obj.molecular_search_settings.use_runtime_kendrick_filter = ( 659 initial_runtime_kendrick_filter 660 ) 661 662 mspeaks = [mspeak for mspeak in self.mass_spectrum_obj if mspeak.is_assigned] 663 664 self.sql_db.close() 665 666 return mspeaks
Search for molecular formulas in the mass spectrum.
Parameters
- possible_formulas_list (list of MolecularFormula): The list of possible molecular formulas.
- ion_type (str): The ion type.
- neutral_molform (bool, optional): Flag to indicate whether the molecular formulas are neutral, by default True.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
- adduct_atom (str, optional): The adduct atom, by default None.
Returns
- list of MSPeak: The list of mass spectrum peaks with assigned molecular formulas.
669class SearchMolecularFormulaWorker: 670 """Class for searching molecular formulas in a mass spectrum. 671 672 Parameters 673 ---------- 674 find_isotopologues : bool, optional 675 Flag to indicate whether to find isotopologues, by default True. 676 677 Attributes 678 ---------- 679 find_isotopologues : bool 680 Flag to indicate whether to find isotopologues. 681 682 Methods 683 ------- 684 * reset_error(). 685 Reset the error variables. 686 * set_last_error(). 687 Set the last error. 688 * find_formulas(). 689 Find the formulas. 690 * calc_error(). 691 Calculate the error. 692 """ 693 694 # TODO add reset error function 695 # needs this wraper to pass the class to multiprocessing 696 697 def __init__(self, find_isotopologues=True): 698 self.find_isotopologues = find_isotopologues 699 700 def __call__(self, args): 701 """Call the find formulas function. 702 703 Parameters 704 ---------- 705 args : tuple 706 The arguments. 707 708 Returns 709 ------- 710 list 711 The list of mass spectrum peaks with assigned molecular formulas. 712 """ 713 return self.find_formulas(*args) # ,args[1] 714 715 def reset_error(self, mass_spectrum_obj): 716 """Reset the error variables. 717 718 Parameters 719 ---------- 720 mass_spectrum_obj : MassSpectrum 721 The mass spectrum object. 722 723 Notes 724 ----- 725 This function resets the error variables for the given mass spectrum object. 726 """ 727 global last_error, last_dif, closest_error, error_average, nbValues 728 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0 729 730 def set_last_error(self, error, mass_spectrum_obj): 731 """Set the last error. 732 733 Parameters 734 ---------- 735 error : float 736 The error. 737 mass_spectrum_obj : MassSpectrum 738 The mass spectrum object. 739 """ 740 # set the changes to the global variables, not internal ones 741 global last_error, last_dif, closest_error, error_average, nbValues 742 743 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 744 dif = error - last_error 745 if dif < last_dif: 746 last_dif = dif 747 closest_error = error 748 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 749 closest_error 750 - mass_spectrum_obj.molecular_search_settings.mz_error_range 751 ) 752 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 753 closest_error 754 + mass_spectrum_obj.molecular_search_settings.mz_error_range 755 ) 756 757 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 758 if error < last_error: 759 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 760 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 761 ) 762 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 763 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 764 ) 765 last_error = error 766 767 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 768 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 769 mass_spectrum_obj.molecular_search_settings.mz_error_average 770 - mass_spectrum_obj.molecular_search_settings.mz_error_range 771 ) 772 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 773 mass_spectrum_obj.molecular_search_settings.mz_error_average 774 + mass_spectrum_obj.molecular_search_settings.mz_error_range 775 ) 776 777 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 778 nbValues += 1 779 error_average = error_average + ((error - error_average) / nbValues) 780 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 781 error_average 782 - mass_spectrum_obj.molecular_search_settings.mz_error_range 783 ) 784 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 785 error_average 786 + mass_spectrum_obj.molecular_search_settings.mz_error_range 787 ) 788 789 else: 790 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 791 pass 792 793 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method 794 795 @staticmethod 796 def calc_error(mz_exp, mz_calc, method="ppm"): 797 """Calculate the error. 798 799 Parameters 800 ---------- 801 mz_exp : float 802 The experimental m/z value. 803 mz_calc : float 804 The calculated m/z value. 805 method : str, optional 806 The method, by default 'ppm'. 807 808 Raises 809 ------- 810 Exception 811 If the method is not ppm or ppb. 812 813 Returns 814 ------- 815 float 816 The error. 817 """ 818 819 if method == "ppm": 820 multi_factor = 1_000_000 821 822 elif method == "ppb": 823 multi_factor = 1_000_000_000 824 825 elif method == "perc": 826 multi_factor = 100 827 828 else: 829 raise Exception( 830 "method needs to be ppm or ppb, you have entered %s" % method 831 ) 832 833 if mz_exp: 834 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 835 836 else: 837 raise Exception("Please set mz_calc first") 838 839 def find_formulas( 840 self, 841 formulas, 842 min_abundance, 843 mass_spectrum_obj, 844 ms_peak, 845 ion_type, 846 ion_charge, 847 adduct_atom=None, 848 ): 849 """Find the formulas. 850 851 Parameters 852 ---------- 853 formulas : list of MolecularFormula 854 The list of molecular formulas. 855 min_abundance : float 856 The minimum abundance threshold. 857 mass_spectrum_obj : MassSpectrum 858 The mass spectrum object. 859 ms_peak : MSPeak 860 The mass spectrum peak. 861 ion_type : str 862 The ion type. 863 ion_charge : int 864 The ion charge. 865 adduct_atom : str, optional 866 The adduct atom, by default None. 867 868 Returns 869 ------- 870 list of MSPeak 871 The list of mass spectrum peaks with assigned molecular formulas. 872 873 Notes 874 ----- 875 Uses the closest error the next search (this is not ideal, it needs to use confidence 876 metric to choose the right candidate then propagate the error using the error from the best candidate). 877 It needs to add s/n to the equation. 878 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 879 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 880 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 881 Method='distance' 882 """ 883 mspeak_assigned_index = list() 884 885 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 886 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 887 888 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 889 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 890 891 # f = open("abundance_error.txt", "a+") 892 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 893 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 894 895 def mass_by_ion_type(possible_formula_obj): 896 if ion_type == Labels.protonated_de_ion: 897 return possible_formula_obj._protonated_mz(ion_charge) 898 899 elif ion_type == Labels.radical_ion: 900 return possible_formula_obj._radical_mz(ion_charge) 901 902 elif ion_type == Labels.adduct_ion and adduct_atom: 903 # Positional argument order differs between formula objects 904 # and SQL links; keywords are unambiguous. 905 return possible_formula_obj._adduct_mz( 906 ion_charge=ion_charge, adduct_atom=adduct_atom 907 ) 908 909 else: 910 # will return externally calculated mz if is set, #use on Bruker Reference list import 911 # if the ion type is known the ion mass based on molecular formula ion type 912 # if ion type is unknow will return neutral mass 913 return possible_formula_obj.mz_calc 914 915 for possible_formula in formulas: 916 if possible_formula: 917 error = self.calc_error( 918 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 919 ) 920 921 # error = possible_formula.mz_error 922 923 if min_ppm_error <= error <= max_ppm_error: 924 # update the error 925 926 self.set_last_error(error, mass_spectrum_obj) 927 928 # add molecular formula match to ms_peak 929 930 # get molecular formula dict from sql obj 931 # formula_dict = pickle.loads(possible_formula.mol_formula) 932 933 formula_dict = possible_formula.to_dict() 934 # create the molecular formula obj to be stored 935 molecular_formula = MolecularFormula( 936 formula_dict, 937 ion_charge, 938 ion_type=ion_type, 939 adduct_atom=adduct_atom, 940 ) 941 # add the molecular formula obj to the mspeak obj 942 # add the mspeak obj and it's index for tracking next assignment step 943 944 if self.find_isotopologues: 945 # calculates isotopologues 946 isotopologues = molecular_formula.isotopologues( 947 min_abundance, 948 ms_peak_abundance, 949 mass_spectrum_obj.dynamic_range, 950 ) 951 952 # search for isotopologues 953 for isotopologue_formula in isotopologues: 954 molecular_formula.expected_isotopologues.append( 955 isotopologue_formula 956 ) 957 # move this outside to improve preformace 958 # we need to increase the search space to -+1 m_z 959 first_index, last_index = ( 960 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 961 isotopologue_formula.mz_nominal_calc 962 ) 963 ) 964 965 for ms_peak_iso in mass_spectrum_obj[ 966 first_index:last_index 967 ]: 968 error = self.calc_error( 969 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 970 ) 971 972 if min_ppm_error <= error <= max_ppm_error: 973 # need to define error distribution for abundance measurements 974 975 # if mass_spectrum_obj.is_centroid: 976 977 abundance_error = self.calc_error( 978 isotopologue_formula.abundance_calc, 979 ms_peak_iso.abundance, 980 method="perc", 981 ) 982 983 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 984 985 # margin of error was set empirically/ needs statistical calculation 986 # of margin of error for the measurement of the abundances 987 if ( 988 min_abun_error 989 <= abundance_error 990 <= max_abun_error 991 ): 992 # update the error 993 994 self.set_last_error(error, mass_spectrum_obj) 995 996 # isotopologue_formula.mz_error = error 997 998 # isotopologue_formula.area_error = area_error 999 1000 # isotopologue_formula.abundance_error = abundance_error 1001 1002 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1003 1004 mono_isotopic_formula_index = len(ms_peak) 1005 1006 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1007 1008 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 1009 1010 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 1011 1012 # add molecular formula match to ms_peak 1013 x = ms_peak_iso.add_molecular_formula( 1014 isotopologue_formula 1015 ) 1016 1017 molecular_formula.mspeak_mf_isotopologues_indexes.append( 1018 (ms_peak_iso.index, x) 1019 ) 1020 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 1021 1022 y = ms_peak.add_molecular_formula(molecular_formula) 1023 1024 mspeak_assigned_index.append((ms_peak.index, y)) 1025 1026 return mspeak_assigned_index
Class for searching molecular formulas in a mass spectrum.
Parameters
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Attributes
- find_isotopologues (bool): Flag to indicate whether to find isotopologues.
Methods
- reset_error(). Reset the error variables.
- set_last_error(). Set the last error.
- find_formulas(). Find the formulas.
- calc_error(). Calculate the error.
715 def reset_error(self, mass_spectrum_obj): 716 """Reset the error variables. 717 718 Parameters 719 ---------- 720 mass_spectrum_obj : MassSpectrum 721 The mass spectrum object. 722 723 Notes 724 ----- 725 This function resets the error variables for the given mass spectrum object. 726 """ 727 global last_error, last_dif, closest_error, error_average, nbValues 728 last_error, last_dif, closest_error, nbValues = 0.0, 0.0, 0.0, 0.0
Reset the error variables.
Parameters
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
Notes
This function resets the error variables for the given mass spectrum object.
730 def set_last_error(self, error, mass_spectrum_obj): 731 """Set the last error. 732 733 Parameters 734 ---------- 735 error : float 736 The error. 737 mass_spectrum_obj : MassSpectrum 738 The mass spectrum object. 739 """ 740 # set the changes to the global variables, not internal ones 741 global last_error, last_dif, closest_error, error_average, nbValues 742 743 if mass_spectrum_obj.molecular_search_settings.error_method == "distance": 744 dif = error - last_error 745 if dif < last_dif: 746 last_dif = dif 747 closest_error = error 748 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 749 closest_error 750 - mass_spectrum_obj.molecular_search_settings.mz_error_range 751 ) 752 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 753 closest_error 754 + mass_spectrum_obj.molecular_search_settings.mz_error_range 755 ) 756 757 elif mass_spectrum_obj.molecular_search_settings.error_method == "lowest": 758 if error < last_error: 759 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 760 error - mass_spectrum_obj.molecular_search_settings.mz_error_range 761 ) 762 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 763 error + mass_spectrum_obj.molecular_search_settings.mz_error_range 764 ) 765 last_error = error 766 767 elif mass_spectrum_obj.molecular_search_settings.error_method == "symmetrical": 768 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 769 mass_spectrum_obj.molecular_search_settings.mz_error_average 770 - mass_spectrum_obj.molecular_search_settings.mz_error_range 771 ) 772 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 773 mass_spectrum_obj.molecular_search_settings.mz_error_average 774 + mass_spectrum_obj.molecular_search_settings.mz_error_range 775 ) 776 777 elif mass_spectrum_obj.molecular_search_settings.error_method == "average": 778 nbValues += 1 779 error_average = error_average + ((error - error_average) / nbValues) 780 mass_spectrum_obj.molecular_search_settings.min_ppm_error = ( 781 error_average 782 - mass_spectrum_obj.molecular_search_settings.mz_error_range 783 ) 784 mass_spectrum_obj.molecular_search_settings.max_ppm_error = ( 785 error_average 786 + mass_spectrum_obj.molecular_search_settings.mz_error_range 787 ) 788 789 else: 790 # using set mass_spectrum_obj.molecular_search_settings.min_ppm_error and max_ppm_error range 791 pass 792 793 # returns the error based on the selected method at mass_spectrum_obj.molecular_search_settings.method
Set the last error.
Parameters
- error (float): The error.
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
795 @staticmethod 796 def calc_error(mz_exp, mz_calc, method="ppm"): 797 """Calculate the error. 798 799 Parameters 800 ---------- 801 mz_exp : float 802 The experimental m/z value. 803 mz_calc : float 804 The calculated m/z value. 805 method : str, optional 806 The method, by default 'ppm'. 807 808 Raises 809 ------- 810 Exception 811 If the method is not ppm or ppb. 812 813 Returns 814 ------- 815 float 816 The error. 817 """ 818 819 if method == "ppm": 820 multi_factor = 1_000_000 821 822 elif method == "ppb": 823 multi_factor = 1_000_000_000 824 825 elif method == "perc": 826 multi_factor = 100 827 828 else: 829 raise Exception( 830 "method needs to be ppm or ppb, you have entered %s" % method 831 ) 832 833 if mz_exp: 834 return ((mz_exp - mz_calc) / mz_calc) * multi_factor 835 836 else: 837 raise Exception("Please set mz_calc first")
Calculate the error.
Parameters
- mz_exp (float): The experimental m/z value.
- mz_calc (float): The calculated m/z value.
- method (str, optional): The method, by default 'ppm'.
Raises
- Exception: If the method is not ppm or ppb.
Returns
- float: The error.
839 def find_formulas( 840 self, 841 formulas, 842 min_abundance, 843 mass_spectrum_obj, 844 ms_peak, 845 ion_type, 846 ion_charge, 847 adduct_atom=None, 848 ): 849 """Find the formulas. 850 851 Parameters 852 ---------- 853 formulas : list of MolecularFormula 854 The list of molecular formulas. 855 min_abundance : float 856 The minimum abundance threshold. 857 mass_spectrum_obj : MassSpectrum 858 The mass spectrum object. 859 ms_peak : MSPeak 860 The mass spectrum peak. 861 ion_type : str 862 The ion type. 863 ion_charge : int 864 The ion charge. 865 adduct_atom : str, optional 866 The adduct atom, by default None. 867 868 Returns 869 ------- 870 list of MSPeak 871 The list of mass spectrum peaks with assigned molecular formulas. 872 873 Notes 874 ----- 875 Uses the closest error the next search (this is not ideal, it needs to use confidence 876 metric to choose the right candidate then propagate the error using the error from the best candidate). 877 It needs to add s/n to the equation. 878 It need optimization to define the mz_error_range within a m/z unit since it is directly proportional 879 with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. 880 The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. 881 Method='distance' 882 """ 883 mspeak_assigned_index = list() 884 885 min_ppm_error = mass_spectrum_obj.molecular_search_settings.min_ppm_error 886 max_ppm_error = mass_spectrum_obj.molecular_search_settings.max_ppm_error 887 888 min_abun_error = mass_spectrum_obj.molecular_search_settings.min_abun_error 889 max_abun_error = mass_spectrum_obj.molecular_search_settings.max_abun_error 890 891 # f = open("abundance_error.txt", "a+") 892 ms_peak_mz_exp, ms_peak_abundance = ms_peak.mz_exp, ms_peak.abundance 893 # min_error = min([pmf.mz_error for pmf in possible_formulas]) 894 895 def mass_by_ion_type(possible_formula_obj): 896 if ion_type == Labels.protonated_de_ion: 897 return possible_formula_obj._protonated_mz(ion_charge) 898 899 elif ion_type == Labels.radical_ion: 900 return possible_formula_obj._radical_mz(ion_charge) 901 902 elif ion_type == Labels.adduct_ion and adduct_atom: 903 # Positional argument order differs between formula objects 904 # and SQL links; keywords are unambiguous. 905 return possible_formula_obj._adduct_mz( 906 ion_charge=ion_charge, adduct_atom=adduct_atom 907 ) 908 909 else: 910 # will return externally calculated mz if is set, #use on Bruker Reference list import 911 # if the ion type is known the ion mass based on molecular formula ion type 912 # if ion type is unknow will return neutral mass 913 return possible_formula_obj.mz_calc 914 915 for possible_formula in formulas: 916 if possible_formula: 917 error = self.calc_error( 918 ms_peak_mz_exp, mass_by_ion_type(possible_formula) 919 ) 920 921 # error = possible_formula.mz_error 922 923 if min_ppm_error <= error <= max_ppm_error: 924 # update the error 925 926 self.set_last_error(error, mass_spectrum_obj) 927 928 # add molecular formula match to ms_peak 929 930 # get molecular formula dict from sql obj 931 # formula_dict = pickle.loads(possible_formula.mol_formula) 932 933 formula_dict = possible_formula.to_dict() 934 # create the molecular formula obj to be stored 935 molecular_formula = MolecularFormula( 936 formula_dict, 937 ion_charge, 938 ion_type=ion_type, 939 adduct_atom=adduct_atom, 940 ) 941 # add the molecular formula obj to the mspeak obj 942 # add the mspeak obj and it's index for tracking next assignment step 943 944 if self.find_isotopologues: 945 # calculates isotopologues 946 isotopologues = molecular_formula.isotopologues( 947 min_abundance, 948 ms_peak_abundance, 949 mass_spectrum_obj.dynamic_range, 950 ) 951 952 # search for isotopologues 953 for isotopologue_formula in isotopologues: 954 molecular_formula.expected_isotopologues.append( 955 isotopologue_formula 956 ) 957 # move this outside to improve preformace 958 # we need to increase the search space to -+1 m_z 959 first_index, last_index = ( 960 mass_spectrum_obj.get_nominal_mz_first_last_indexes( 961 isotopologue_formula.mz_nominal_calc 962 ) 963 ) 964 965 for ms_peak_iso in mass_spectrum_obj[ 966 first_index:last_index 967 ]: 968 error = self.calc_error( 969 ms_peak_iso.mz_exp, isotopologue_formula.mz_calc 970 ) 971 972 if min_ppm_error <= error <= max_ppm_error: 973 # need to define error distribution for abundance measurements 974 975 # if mass_spectrum_obj.is_centroid: 976 977 abundance_error = self.calc_error( 978 isotopologue_formula.abundance_calc, 979 ms_peak_iso.abundance, 980 method="perc", 981 ) 982 983 # area_error = self.calc_error(ms_peak.area, ms_peak_iso.area, method='perc') 984 985 # margin of error was set empirically/ needs statistical calculation 986 # of margin of error for the measurement of the abundances 987 if ( 988 min_abun_error 989 <= abundance_error 990 <= max_abun_error 991 ): 992 # update the error 993 994 self.set_last_error(error, mass_spectrum_obj) 995 996 # isotopologue_formula.mz_error = error 997 998 # isotopologue_formula.area_error = area_error 999 1000 # isotopologue_formula.abundance_error = abundance_error 1001 1002 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1003 1004 mono_isotopic_formula_index = len(ms_peak) 1005 1006 isotopologue_formula.mspeak_index_mono_isotopic = ms_peak.index 1007 1008 isotopologue_formula.mono_isotopic_formula_index = mono_isotopic_formula_index 1009 1010 # add mspeaks isotopologue index to the mono isotopic MolecularFormula obj and the respective formula position 1011 1012 # add molecular formula match to ms_peak 1013 x = ms_peak_iso.add_molecular_formula( 1014 isotopologue_formula 1015 ) 1016 1017 molecular_formula.mspeak_mf_isotopologues_indexes.append( 1018 (ms_peak_iso.index, x) 1019 ) 1020 # add mspeaks mono isotopic index to the isotopologue MolecularFormula obj 1021 1022 y = ms_peak.add_molecular_formula(molecular_formula) 1023 1024 mspeak_assigned_index.append((ms_peak.index, y)) 1025 1026 return mspeak_assigned_index
Find the formulas.
Parameters
- formulas (list of MolecularFormula): The list of molecular formulas.
- min_abundance (float): The minimum abundance threshold.
- mass_spectrum_obj (MassSpectrum): The mass spectrum object.
- ms_peak (MSPeak): The mass spectrum peak.
- ion_type (str): The ion type.
- ion_charge (int): The ion charge.
- adduct_atom (str, optional): The adduct atom, by default None.
Returns
- list of MSPeak: The list of mass spectrum peaks with assigned molecular formulas.
Notes
Uses the closest error the next search (this is not ideal, it needs to use confidence metric to choose the right candidate then propagate the error using the error from the best candidate). It needs to add s/n to the equation. It need optimization to define the mz_error_range within a m/z unit since it is directly proportional with the mass, and inversely proportional to the rp. It's not linear, i.e., sigma mass. The idea it to correlate sigma to resolving power, signal to noise and sample complexity per mz unit. Method='distance'
1029class SearchMolecularFormulasLC: 1030 """Class for searching molecular formulas in a LC object. 1031 1032 Parameters 1033 ---------- 1034 lcms_obj : LCMSBase 1035 The LCMSBase object. 1036 sql_db : MolForm_SQL, optional 1037 The SQL database object, by default None. 1038 first_hit : bool, optional 1039 Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False. 1040 find_isotopologues : bool, optional 1041 Flag to indicate whether to find isotopologues, by default True. 1042 1043 Methods 1044 ------- 1045 1046 * search_spectra_against_candidates(). 1047 Search a list of mass spectra against a list of candidate formulas with a given ion type, charge, and optional adduct_atom. 1048 * bulk_run_molecular_formula_search(). 1049 Run the molecular formula search on the given list of mass spectra. 1050 Pulls the settings from the LCMSBase object to set ion type and charge to search for. 1051 * run_mass_feature_search(). 1052 Run the molecular formula search on mass features. 1053 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1054 * run_untargeted_worker_ms1(). 1055 Run untargeted molecular formula search on the ms1 mass spectrum. 1056 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 1057 * run_target_worker_ms1(). 1058 Run targeted molecular formula search on the ms1 mass spectrum. 1059 DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead. 1060 """ 1061 1062 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 1063 self.first_hit = first_hit 1064 1065 self.find_isotopologues = find_isotopologues 1066 1067 self.lcms_obj = lcms_obj 1068 1069 # Catch in-place usedAtoms mutations after settings construction 1070 validate_used_atoms_keys( 1071 self.lcms_obj.parameters.mass_spectrum["ms1"].molecular_search.usedAtoms 1072 ) 1073 1074 if not sql_db: 1075 self.sql_db = MolForm_SQL( 1076 url=self.lcms_obj.parameters.mass_spectrum[ 1077 "ms1" 1078 ].molecular_search.url_database 1079 ) 1080 1081 else: 1082 self.sql_db = sql_db 1083 1084 def search_spectra_against_candidates( 1085 self, 1086 mass_spectrum_list, 1087 ms_peaks_list, 1088 candidate_formulas, 1089 ion_type, 1090 ion_charge, 1091 adduct_atom=None, 1092 ): 1093 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 1094 1095 Parameters 1096 ---------- 1097 mass_spectrum_list : list of MassSpectrum 1098 The list of mass spectra to perform the search on. 1099 ms_peaks_list : list of lists of MSPeak objects 1100 The list of mass spectrum peaks to search within each mass spectrum. 1101 candidate_formulas : dict 1102 The candidate formulas. 1103 ion_type : str 1104 The ion type. 1105 ion_charge : int 1106 signed ion charge used for the candidate formulas. 1107 adduct_atom : str, optional 1108 The adduct atom, by default None. 1109 1110 Notes 1111 ----- 1112 This function is designed to be used with the bulk_run_molecular_formula_search function. 1113 """ 1114 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 1115 single_ms_search = SearchMolecularFormulas( 1116 mass_spectrum, 1117 sql_db=self.sql_db, 1118 first_hit=self.first_hit, 1119 find_isotopologues=self.find_isotopologues, 1120 ) 1121 single_ms_search.run_search( 1122 ms_peaks, 1123 candidate_formulas, 1124 mass_spectrum.min_abundance, 1125 ion_type, 1126 ion_charge, 1127 adduct_atom=adduct_atom, 1128 ) 1129 1130 def bulk_run_molecular_formula_search( 1131 self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key="ms1" 1132 ): 1133 """Run the molecular formula search on the given list of mass spectra. 1134 1135 Uses ``min_ion_charge`` / ``max_ion_charge`` from molecular search settings 1136 to search each peak at every signed charge in that absolute range 1137 (defaults 1..1 = single-charge). Does not determine or rewrite peak 1138 ``polarity``; assignment charge is on each ``MolecularFormula``. 1139 1140 Parameters 1141 ---------- 1142 mass_spectrum_list : list of MassSpectrum 1143 The list of mass spectra to search. 1144 ms_peaks_list : list of lists of MSPeak objects 1145 The mass peaks to perform molecular formula search within each mass spectrum 1146 mass_spectrum_setting_key : str, optional 1147 The mass spectrum setting key, by default 'ms1'. 1148 This is used to get the appropriate molecular search settings from the LCMSBase object 1149 """ 1150 if self.lcms_obj.polarity not in ("positive", "negative"): 1151 raise ValueError("Polarity must be either 'positive' or 'negative'") 1152 1153 # Check that the length of the mass spectrum list and the ms_peaks list are the same 1154 if len(mass_spectrum_list) != len(ms_peaks_list): 1155 raise ValueError( 1156 "The length of the mass spectrum list and the ms_peaks list must be the same" 1157 ) 1158 1159 mol_settings = self.lcms_obj.parameters.mass_spectrum[ 1160 mass_spectrum_setting_key 1161 ].molecular_search 1162 # Re-check after possible post-construction mutation of settings fields 1163 mol_settings.validate_ion_charge_settings() 1164 search_charges = SearchMolecularFormulas.ion_charges_for_search( 1165 self.lcms_obj.polarity, 1166 min_ion_charge=mol_settings.min_ion_charge, 1167 max_ion_charge=mol_settings.max_ion_charge, 1168 ) 1169 1170 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 1171 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 1172 1173 # reset average error, only relevant if average mass error method is being used 1174 SearchMolecularFormulaWorker( 1175 find_isotopologues=self.find_isotopologues 1176 ).reset_error(mass_spectrum_list[0]) 1177 1178 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 1179 classes = MolecularCombinations(self.sql_db).runworker( 1180 mol_settings, print_time=mol_settings.verbose_processing 1181 ) 1182 1183 try: 1184 1185 def apply_job(job): 1186 self.search_spectra_against_candidates( 1187 mass_spectrum_list=mass_spectrum_list, 1188 ms_peaks_list=ms_peaks_list, 1189 candidate_formulas=job.candidate_formulas, 1190 ion_type=job.ion_type, 1191 ion_charge=job.ion_charge, 1192 adduct_atom=job.adduct_atom, 1193 ) 1194 1195 SearchMolecularFormulas._run_formula_search_jobs( 1196 classes, 1197 nominal_mzs, 1198 mol_settings, 1199 search_charges, 1200 self.sql_db, 1201 apply_job, 1202 ) 1203 finally: 1204 self.sql_db.close() 1205 1206 def run_mass_feature_search(self): 1207 """Run the molecular formula search on the mass features. 1208 1209 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1210 Multi-charge range comes from molecular search ``min_ion_charge`` / 1211 ``max_ion_charge``. Peak ``polarity`` is not rewritten. 1212 """ 1213 mass_features_df = self.lcms_obj.mass_features_to_df() 1214 1215 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1216 scan_list = mass_features_df.apex_scan.unique() 1217 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1218 ms_peaks = [] 1219 for scan in scan_list: 1220 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1221 peaks_to_search = [ 1222 self.lcms_obj.mass_features[x].ms1_peak 1223 for x in mf_df_scan.index.tolist() 1224 ] 1225 ms_peaks.append(peaks_to_search) 1226 1227 # Run the molecular formula search 1228 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks) 1229 1230 def run_untargeted_worker_ms1(self): 1231 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1232 raise NotImplementedError( 1233 "run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater" 1234 ) 1235 1236 def run_target_worker_ms1(self): 1237 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1238 raise NotImplementedError( 1239 "run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater" 1240 )
Class for searching molecular formulas in a LC object.
Parameters
- lcms_obj (LCMSBase): The LCMSBase object.
- sql_db (MolForm_SQL, optional): The SQL database object, by default None.
- first_hit (bool, optional): Flag to indicate whether to skip peaks that already have a molecular formula assigned, by default False.
- find_isotopologues (bool, optional): Flag to indicate whether to find isotopologues, by default True.
Methods
- search_spectra_against_candidates(). Search a list of mass spectra against a list of candidate formulas with a given ion type, charge, and optional adduct_atom.
- bulk_run_molecular_formula_search(). Run the molecular formula search on the given list of mass spectra. Pulls the settings from the LCMSBase object to set ion type and charge to search for.
- run_mass_feature_search(). Run the molecular formula search on mass features. Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks.
- run_untargeted_worker_ms1(). Run untargeted molecular formula search on the ms1 mass spectrum. DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead.
- run_target_worker_ms1(). Run targeted molecular formula search on the ms1 mass spectrum. DEPRECATED: use run_mass_feature_search() or bulk_run_molecular_formula_search() instead.
1062 def __init__(self, lcms_obj, sql_db=None, first_hit=False, find_isotopologues=True): 1063 self.first_hit = first_hit 1064 1065 self.find_isotopologues = find_isotopologues 1066 1067 self.lcms_obj = lcms_obj 1068 1069 # Catch in-place usedAtoms mutations after settings construction 1070 validate_used_atoms_keys( 1071 self.lcms_obj.parameters.mass_spectrum["ms1"].molecular_search.usedAtoms 1072 ) 1073 1074 if not sql_db: 1075 self.sql_db = MolForm_SQL( 1076 url=self.lcms_obj.parameters.mass_spectrum[ 1077 "ms1" 1078 ].molecular_search.url_database 1079 ) 1080 1081 else: 1082 self.sql_db = sql_db
1084 def search_spectra_against_candidates( 1085 self, 1086 mass_spectrum_list, 1087 ms_peaks_list, 1088 candidate_formulas, 1089 ion_type, 1090 ion_charge, 1091 adduct_atom=None, 1092 ): 1093 """Search a list of mass spectra against a list of candidate formulas with a given ion type and charge. 1094 1095 Parameters 1096 ---------- 1097 mass_spectrum_list : list of MassSpectrum 1098 The list of mass spectra to perform the search on. 1099 ms_peaks_list : list of lists of MSPeak objects 1100 The list of mass spectrum peaks to search within each mass spectrum. 1101 candidate_formulas : dict 1102 The candidate formulas. 1103 ion_type : str 1104 The ion type. 1105 ion_charge : int 1106 signed ion charge used for the candidate formulas. 1107 adduct_atom : str, optional 1108 The adduct atom, by default None. 1109 1110 Notes 1111 ----- 1112 This function is designed to be used with the bulk_run_molecular_formula_search function. 1113 """ 1114 for mass_spectrum, ms_peaks in zip(mass_spectrum_list, ms_peaks_list): 1115 single_ms_search = SearchMolecularFormulas( 1116 mass_spectrum, 1117 sql_db=self.sql_db, 1118 first_hit=self.first_hit, 1119 find_isotopologues=self.find_isotopologues, 1120 ) 1121 single_ms_search.run_search( 1122 ms_peaks, 1123 candidate_formulas, 1124 mass_spectrum.min_abundance, 1125 ion_type, 1126 ion_charge, 1127 adduct_atom=adduct_atom, 1128 )
Search a list of mass spectra against a list of candidate formulas with a given ion type and charge.
Parameters
- mass_spectrum_list (list of MassSpectrum): The list of mass spectra to perform the search on.
- ms_peaks_list (list of lists of MSPeak objects): The list of mass spectrum peaks to search within each mass spectrum.
- candidate_formulas (dict): The candidate formulas.
- ion_type (str): The ion type.
- ion_charge (int): signed ion charge used for the candidate formulas.
- adduct_atom (str, optional): The adduct atom, by default None.
Notes
This function is designed to be used with the bulk_run_molecular_formula_search function.
1130 def bulk_run_molecular_formula_search( 1131 self, mass_spectrum_list, ms_peaks_list, mass_spectrum_setting_key="ms1" 1132 ): 1133 """Run the molecular formula search on the given list of mass spectra. 1134 1135 Uses ``min_ion_charge`` / ``max_ion_charge`` from molecular search settings 1136 to search each peak at every signed charge in that absolute range 1137 (defaults 1..1 = single-charge). Does not determine or rewrite peak 1138 ``polarity``; assignment charge is on each ``MolecularFormula``. 1139 1140 Parameters 1141 ---------- 1142 mass_spectrum_list : list of MassSpectrum 1143 The list of mass spectra to search. 1144 ms_peaks_list : list of lists of MSPeak objects 1145 The mass peaks to perform molecular formula search within each mass spectrum 1146 mass_spectrum_setting_key : str, optional 1147 The mass spectrum setting key, by default 'ms1'. 1148 This is used to get the appropriate molecular search settings from the LCMSBase object 1149 """ 1150 if self.lcms_obj.polarity not in ("positive", "negative"): 1151 raise ValueError("Polarity must be either 'positive' or 'negative'") 1152 1153 # Check that the length of the mass spectrum list and the ms_peaks list are the same 1154 if len(mass_spectrum_list) != len(ms_peaks_list): 1155 raise ValueError( 1156 "The length of the mass spectrum list and the ms_peaks list must be the same" 1157 ) 1158 1159 mol_settings = self.lcms_obj.parameters.mass_spectrum[ 1160 mass_spectrum_setting_key 1161 ].molecular_search 1162 # Re-check after possible post-construction mutation of settings fields 1163 mol_settings.validate_ion_charge_settings() 1164 search_charges = SearchMolecularFormulas.ion_charges_for_search( 1165 self.lcms_obj.polarity, 1166 min_ion_charge=mol_settings.min_ion_charge, 1167 max_ion_charge=mol_settings.max_ion_charge, 1168 ) 1169 1170 nominal_mzs = [x.nominal_mz for x in mass_spectrum_list] 1171 nominal_mzs = list(set([item for sublist in nominal_mzs for item in sublist])) 1172 1173 # reset average error, only relevant if average mass error method is being used 1174 SearchMolecularFormulaWorker( 1175 find_isotopologues=self.find_isotopologues 1176 ).reset_error(mass_spectrum_list[0]) 1177 1178 # check database for all possible molecular formula combinations based on the setting passed to self.mass_spectrum_obj.molecular_search_settings 1179 classes = MolecularCombinations(self.sql_db).runworker( 1180 mol_settings, print_time=mol_settings.verbose_processing 1181 ) 1182 1183 try: 1184 1185 def apply_job(job): 1186 self.search_spectra_against_candidates( 1187 mass_spectrum_list=mass_spectrum_list, 1188 ms_peaks_list=ms_peaks_list, 1189 candidate_formulas=job.candidate_formulas, 1190 ion_type=job.ion_type, 1191 ion_charge=job.ion_charge, 1192 adduct_atom=job.adduct_atom, 1193 ) 1194 1195 SearchMolecularFormulas._run_formula_search_jobs( 1196 classes, 1197 nominal_mzs, 1198 mol_settings, 1199 search_charges, 1200 self.sql_db, 1201 apply_job, 1202 ) 1203 finally: 1204 self.sql_db.close()
Run the molecular formula search on the given list of mass spectra.
Uses min_ion_charge / max_ion_charge from molecular search settings
to search each peak at every signed charge in that absolute range
(defaults 1..1 = single-charge). Does not determine or rewrite peak
polarity; assignment charge is on each MolecularFormula.
Parameters
- mass_spectrum_list (list of MassSpectrum): The list of mass spectra to search.
- ms_peaks_list (list of lists of MSPeak objects): The mass peaks to perform molecular formula search within each mass spectrum
- mass_spectrum_setting_key (str, optional): The mass spectrum setting key, by default 'ms1'. This is used to get the appropriate molecular search settings from the LCMSBase object
1206 def run_mass_feature_search(self): 1207 """Run the molecular formula search on the mass features. 1208 1209 Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks. 1210 Multi-charge range comes from molecular search ``min_ion_charge`` / 1211 ``max_ion_charge``. Peak ``polarity`` is not rewritten. 1212 """ 1213 mass_features_df = self.lcms_obj.mass_features_to_df() 1214 1215 # Get the list of mass spectrum (and peaks to search with each mass spectrum) for all mass features 1216 scan_list = mass_features_df.apex_scan.unique() 1217 mass_spectrum_list = [self.lcms_obj._ms[x] for x in scan_list] 1218 ms_peaks = [] 1219 for scan in scan_list: 1220 mf_df_scan = mass_features_df[mass_features_df.apex_scan == scan] 1221 peaks_to_search = [ 1222 self.lcms_obj.mass_features[x].ms1_peak 1223 for x in mf_df_scan.index.tolist() 1224 ] 1225 ms_peaks.append(peaks_to_search) 1226 1227 # Run the molecular formula search 1228 self.bulk_run_molecular_formula_search(mass_spectrum_list, ms_peaks)
Run the molecular formula search on the mass features.
Calls bulk_run_molecular_formula_search() with specified mass spectra and mass peaks.
Multi-charge range comes from molecular search min_ion_charge /
max_ion_charge. Peak polarity is not rewritten.
1230 def run_untargeted_worker_ms1(self): 1231 """Run untargeted molecular formula search on the ms1 mass spectrum.""" 1232 raise NotImplementedError( 1233 "run_untargeted_worker_ms1 search is not implemented in CoreMS 3.0 and greater" 1234 )
Run untargeted molecular formula search on the ms1 mass spectrum.
1236 def run_target_worker_ms1(self): 1237 """Run targeted molecular formula search on the ms1 mass spectrum.""" 1238 raise NotImplementedError( 1239 "run_target_worker_ms1 formula search is not yet implemented in CoreMS 3.0 and greater" 1240 )
Run targeted molecular formula search on the ms1 mass spectrum.