msfiddle 2.0.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. msfiddle-2.2.0/PKG-INFO +169 -0
  2. msfiddle-2.2.0/README.md +128 -0
  3. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/__init__.py +6 -1
  4. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/api.py +17 -4
  5. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_orbitrap.yml +12 -7
  6. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_qtof.yml +14 -7
  7. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/dataset.py +32 -6
  8. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/download.py +17 -6
  9. msfiddle-2.2.0/msfiddle/external_results.py +380 -0
  10. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/main.py +17 -2
  11. msfiddle-2.2.0/msfiddle/optim.py +160 -0
  12. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/utils/msms_utils.py +134 -103
  13. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/utils/pkl_utils.py +140 -36
  14. msfiddle-2.2.0/msfiddle/utils/refine_utils.py +712 -0
  15. msfiddle-2.2.0/msfiddle.egg-info/PKG-INFO +169 -0
  16. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle.egg-info/SOURCES.txt +8 -0
  17. {msfiddle-2.0.1 → msfiddle-2.2.0}/setup.py +3 -3
  18. {msfiddle-2.0.1 → msfiddle-2.2.0}/tests/test_api.py +101 -1
  19. msfiddle-2.2.0/tests/test_download.py +29 -0
  20. msfiddle-2.2.0/tests/test_external_results.py +282 -0
  21. msfiddle-2.2.0/tests/test_generate_ms.py +52 -0
  22. msfiddle-2.2.0/tests/test_muon.py +94 -0
  23. msfiddle-2.2.0/tests/test_preprocessing_speedups.py +148 -0
  24. msfiddle-2.2.0/tests/test_refine_enumeration.py +136 -0
  25. msfiddle-2.2.0/tests/test_refine_fast_mass.py +106 -0
  26. msfiddle-2.0.1/PKG-INFO +0 -251
  27. msfiddle-2.0.1/README.md +0 -210
  28. msfiddle-2.0.1/msfiddle/utils/refine_utils.py +0 -376
  29. msfiddle-2.0.1/msfiddle.egg-info/PKG-INFO +0 -251
  30. msfiddle-2.0.1/tests/test_download.py +0 -15
  31. {msfiddle-2.0.1 → msfiddle-2.2.0}/LICENSE +0 -0
  32. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_casmi.yml +0 -0
  33. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_demo.yml +0 -0
  34. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_demo_wo_da.yml +0 -0
  35. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/config/fiddle_tcn_embl.yml +0 -0
  36. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/demo/input_msms.mgf +0 -0
  37. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/demo/output_buddy.csv +0 -0
  38. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/demo/output_fiddle.csv +0 -0
  39. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/demo/output_fiddle_all.csv +0 -0
  40. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/demo/output_sirius.csv +0 -0
  41. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/model_tcn.py +0 -0
  42. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/utils/__init__.py +0 -0
  43. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle/utils/mol_utils.py +0 -0
  44. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle.egg-info/dependency_links.txt +0 -0
  45. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle.egg-info/entry_points.txt +0 -0
  46. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle.egg-info/requires.txt +0 -0
  47. {msfiddle-2.0.1 → msfiddle-2.2.0}/msfiddle.egg-info/top_level.txt +0 -0
  48. {msfiddle-2.0.1 → msfiddle-2.2.0}/setup.cfg +0 -0
  49. {msfiddle-2.0.1 → msfiddle-2.2.0}/tests/test_main.py +0 -0
  50. {msfiddle-2.0.1 → msfiddle-2.2.0}/tests/test_mol_utils.py +0 -0
  51. {msfiddle-2.0.1 → msfiddle-2.2.0}/tests/test_msms_utils.py +0 -0
  52. {msfiddle-2.0.1 → msfiddle-2.2.0}/tests/test_refine_utils.py +0 -0
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: msfiddle
3
+ Version: 2.2.0
4
+ Summary: A package for predicting chemical formulas from tandem mass spectra
5
+ Home-page: https://github.com/JosieHong/FIDDLE
6
+ Author: Yuhui Hong
7
+ Author-email: josieexception@outlook.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
14
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy<2.0.0,>=1.20.0
19
+ Requires-Dist: pandas>=2.0.0
20
+ Requires-Dist: tqdm>=4.60.0
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: scikit-learn>=1.0.0
23
+ Requires-Dist: scipy>=1.8.0
24
+ Requires-Dist: pyarrow>=10.0.0
25
+ Requires-Dist: rdkit>=2022.03.5
26
+ Requires-Dist: molmass
27
+ Requires-Dist: pyteomics
28
+ Provides-Extra: inference
29
+ Requires-Dist: torch; extra == "inference"
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: license-file
37
+ Dynamic: provides-extra
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
41
+
42
+ # FIDDLE
43
+
44
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
45
+ [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.19181279-blue)](https://doi.org/10.5281/zenodo.19181279)
46
+ [![Release](https://img.shields.io/github/v/release/JosieHong/FIDDLE?label=Release)](https://github.com/JosieHong/FIDDLE/releases)
47
+
48
+ [<img src="https://raw.githubusercontent.com/JosieHong/FIDDLE/main/img/fiddle_logo.png" align="right" width="220">](https://github.com/JosieHong/FIDDLE)
49
+
50
+ FIDDLE is a deep learning method for predicting molecular formulas from MS/MS spectra. This repository contains the full research codebase for model training, evaluation, and paper reproduction, plus the [`msfiddle`](https://pypi.org/project/msfiddle/) PyPI package (in [`./msfiddle/`](https://github.com/JosieHong/FIDDLE/tree/main/msfiddle)) for inference-only use.
51
+
52
+ - **Paper:** [Nature Communications (2025)](https://www.nature.com/articles/s41467-025-66060-9)
53
+ - **CLI and Python API:** `pip install "msfiddle[inference]"` — see the [package docs](https://msfiddle.readthedocs.io)
54
+ - **Try this demo!** [FIDDLE on Hugging Face](https://huggingface.co/spaces/J0siee/FIDDLE)
55
+
56
+ <br clear="right"/>
57
+
58
+ > **Breaking change (v2.0.0):** The rescore model has been redesigned (Siamese architecture), see details in [CHANGELOG.md](https://github.com/JosieHong/FIDDLE/blob/main/CHANGELOG.md).
59
+
60
+ ## Set up
61
+
62
+ ### Requirements
63
+
64
+ 1. Install [Anaconda](https://docs.anaconda.com/anaconda/install/), if not already installed.
65
+
66
+ 2. Create the environment with the necessary packages:
67
+
68
+ ```bash
69
+ conda env create -f environment.yml
70
+ ```
71
+
72
+ On Apple Silicon (macOS ARM64), use `environment_mac.yml` instead.
73
+
74
+ 3. (optional) Install [BUDDY](https://github.com/Philipbear/msbuddy) and [SIRIUS](https://v6.docs.sirius-ms.io/) following the respective installation instructions provided in each tool's documentation.
75
+
76
+ ### Pre-trained Model Weights
77
+
78
+ To use the pre-trained models, please use the following scripts to download the weights from the [release page](https://github.com/JosieHong/FIDDLE/releases/tag/v2.2.0) and place them in the `./check_point/` directory:
79
+
80
+ - **Orbitrap models**:
81
+ - `fiddle_tcn_orbitrap.pt`: formula prediction model on Orbitrap spectra
82
+ - `fiddle_rescore_orbitrap.pt`: rescore model on Orbitrap spectra
83
+ - **Q-TOF models**:
84
+ - `fiddle_tcn_qtof.pt`: formula prediction model on Q-TOF spectra
85
+ - `fiddle_rescore_qtof.pt`: rescore model on Q-TOF spectra
86
+
87
+ ```bash
88
+ bash ./running_scripts/download_models.sh
89
+ ```
90
+
91
+ ## Usage
92
+
93
+ The input format is `mgf`, where `title`, `precursor_mz`, `precursor_type`, `collision_energy` fields are required. Here, we sampled 21 spectra from the EMBL-MCF 2.0 dataset as an example.
94
+
95
+ ```mgf
96
+ BEGIN IONS
97
+ TITLE=EMBL_MCF_2_0_HRMS_Library000531
98
+ PEPMASS=129.01941
99
+ CHARGE=1-
100
+ PRECURSOR_TYPE=[M-H]-
101
+ PRECURSOR_MZ=129.01941
102
+ COLLISION_ENERGY=50.0
103
+ SMILES=[H]OC(=O)C([H])=C(C(=O)O[H])C([H])([H])[H]
104
+ FORMULA=C5H6O4
105
+ THEORETICAL_PRECURSOR_MZ=129.018785
106
+ PPM=4.844255818912111
107
+ SIMULATED_PRECURSOR_MZ=129.02032113281717
108
+ 41.2041 0.410228
109
+ 55.7698 0.503672
110
+ 56.8647 0.461943
111
+ 85.0296 100.0
112
+ 129.0196 8.036902
113
+ END IONS
114
+ ```
115
+
116
+ **Run FIDDLE!**
117
+
118
+ ```bash
119
+ python scripts/run_fiddle.py --test_data ./msfiddle/demo/input_msms.mgf \
120
+ --config_path ./msfiddle/config/fiddle_tcn_orbitrap.yml \
121
+ --resume_path ./check_point/fiddle_tcn_orbitrap.pt \
122
+ --rescore_resume_path ./check_point/fiddle_rescore_orbitrap.pt \
123
+ --result_path ./msfiddle/demo/output_fiddle.csv --device 0
124
+ ```
125
+
126
+ If you'd like to integrate the results from SIRIUS and BUDDY, please organize the results in the format shown in `./msfiddle/demo/buddy_output.csv` and `./msfiddle/demo/sirius_output.csv`, and provide them to run FIDDLE:
127
+
128
+ ```bash
129
+ python scripts/run_fiddle.py --test_data ./msfiddle/demo/input_msms.mgf \
130
+ --config_path ./msfiddle/config/fiddle_tcn_orbitrap.yml \
131
+ --resume_path ./check_point/fiddle_tcn_orbitrap.pt \
132
+ --rescore_resume_path ./check_point/fiddle_rescore_orbitrap.pt \
133
+ --buddy_path ./msfiddle/demo/output_buddy.csv \
134
+ --sirius_path ./msfiddle/demo/output_sirius.csv \
135
+ --result_path ./msfiddle/demo/output_fiddle_all.csv --device 0
136
+ ```
137
+
138
+ See [`scripts/test_caffeine.py`](https://github.com/JosieHong/FIDDLE/blob/main/scripts/test_caffeine.py) for a worked example running FIDDLE on a caffeine Orbitrap spectrum fetched live from GNPS.
139
+
140
+ ## Reproduce paper results
141
+
142
+ All scripts should be run from the repository root (`FIDDLE/`).
143
+
144
+ | Script | Description |
145
+ |---|---|
146
+ | `running_scripts/experiments_test_benchmark.sh` | Evaluate on external benchmarks (CASMI 2016, CASMI 2017, EMBL-MCF 2.0) |
147
+ | `running_scripts/experiments_test_nist23.sh` | Evaluate on NIST23 |
148
+ | `running_scripts/experiments_test_chimeric.sh` | Evaluate on chimeric spectra |
149
+ | `running_scripts/experiments_test_noised.sh` | Evaluate under noise conditions |
150
+ | `running_scripts/experiments_ablation_study.sh` | Run ablation study |
151
+ | `running_scripts/experiments_demo.sh` | Run demo experiment |
152
+ | `running_scripts/train_released_models.sh` | Train TCN and rescore models for both Orbitrap and Q-TOF |
153
+
154
+ For training from scratch, see the train scripts (`scripts/train_tcn_gpus.py`, `scripts/train_tcn_gpus_cl.py`, `scripts/train_rescore.py`) and the corresponding config files in `./msfiddle/config/`.
155
+
156
+ ## Citation
157
+
158
+ ```
159
+ @article{hong2025fiddle,
160
+ title={FIDDLE: a deep learning method for chemical formulas prediction from tandem mass spectra},
161
+ author={Hong, Yuhui and Li, Sujun and Ye, Yuzhen and Tang, Haixu},
162
+ journal={Nature Communications},
163
+ volume={16},
164
+ number={1},
165
+ pages={11102},
166
+ year={2025},
167
+ publisher={Nature Publishing Group UK London}
168
+ }
169
+ ```
@@ -0,0 +1,128 @@
1
+ # FIDDLE
2
+
3
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+ [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.19181279-blue)](https://doi.org/10.5281/zenodo.19181279)
5
+ [![Release](https://img.shields.io/github/v/release/JosieHong/FIDDLE?label=Release)](https://github.com/JosieHong/FIDDLE/releases)
6
+
7
+ [<img src="https://raw.githubusercontent.com/JosieHong/FIDDLE/main/img/fiddle_logo.png" align="right" width="220">](https://github.com/JosieHong/FIDDLE)
8
+
9
+ FIDDLE is a deep learning method for predicting molecular formulas from MS/MS spectra. This repository contains the full research codebase for model training, evaluation, and paper reproduction, plus the [`msfiddle`](https://pypi.org/project/msfiddle/) PyPI package (in [`./msfiddle/`](https://github.com/JosieHong/FIDDLE/tree/main/msfiddle)) for inference-only use.
10
+
11
+ - **Paper:** [Nature Communications (2025)](https://www.nature.com/articles/s41467-025-66060-9)
12
+ - **CLI and Python API:** `pip install "msfiddle[inference]"` — see the [package docs](https://msfiddle.readthedocs.io)
13
+ - **Try this demo!** [FIDDLE on Hugging Face](https://huggingface.co/spaces/J0siee/FIDDLE)
14
+
15
+ <br clear="right"/>
16
+
17
+ > **Breaking change (v2.0.0):** The rescore model has been redesigned (Siamese architecture), see details in [CHANGELOG.md](https://github.com/JosieHong/FIDDLE/blob/main/CHANGELOG.md).
18
+
19
+ ## Set up
20
+
21
+ ### Requirements
22
+
23
+ 1. Install [Anaconda](https://docs.anaconda.com/anaconda/install/), if not already installed.
24
+
25
+ 2. Create the environment with the necessary packages:
26
+
27
+ ```bash
28
+ conda env create -f environment.yml
29
+ ```
30
+
31
+ On Apple Silicon (macOS ARM64), use `environment_mac.yml` instead.
32
+
33
+ 3. (optional) Install [BUDDY](https://github.com/Philipbear/msbuddy) and [SIRIUS](https://v6.docs.sirius-ms.io/) following the respective installation instructions provided in each tool's documentation.
34
+
35
+ ### Pre-trained Model Weights
36
+
37
+ To use the pre-trained models, please use the following scripts to download the weights from the [release page](https://github.com/JosieHong/FIDDLE/releases/tag/v2.2.0) and place them in the `./check_point/` directory:
38
+
39
+ - **Orbitrap models**:
40
+ - `fiddle_tcn_orbitrap.pt`: formula prediction model on Orbitrap spectra
41
+ - `fiddle_rescore_orbitrap.pt`: rescore model on Orbitrap spectra
42
+ - **Q-TOF models**:
43
+ - `fiddle_tcn_qtof.pt`: formula prediction model on Q-TOF spectra
44
+ - `fiddle_rescore_qtof.pt`: rescore model on Q-TOF spectra
45
+
46
+ ```bash
47
+ bash ./running_scripts/download_models.sh
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ The input format is `mgf`, where `title`, `precursor_mz`, `precursor_type`, `collision_energy` fields are required. Here, we sampled 21 spectra from the EMBL-MCF 2.0 dataset as an example.
53
+
54
+ ```mgf
55
+ BEGIN IONS
56
+ TITLE=EMBL_MCF_2_0_HRMS_Library000531
57
+ PEPMASS=129.01941
58
+ CHARGE=1-
59
+ PRECURSOR_TYPE=[M-H]-
60
+ PRECURSOR_MZ=129.01941
61
+ COLLISION_ENERGY=50.0
62
+ SMILES=[H]OC(=O)C([H])=C(C(=O)O[H])C([H])([H])[H]
63
+ FORMULA=C5H6O4
64
+ THEORETICAL_PRECURSOR_MZ=129.018785
65
+ PPM=4.844255818912111
66
+ SIMULATED_PRECURSOR_MZ=129.02032113281717
67
+ 41.2041 0.410228
68
+ 55.7698 0.503672
69
+ 56.8647 0.461943
70
+ 85.0296 100.0
71
+ 129.0196 8.036902
72
+ END IONS
73
+ ```
74
+
75
+ **Run FIDDLE!**
76
+
77
+ ```bash
78
+ python scripts/run_fiddle.py --test_data ./msfiddle/demo/input_msms.mgf \
79
+ --config_path ./msfiddle/config/fiddle_tcn_orbitrap.yml \
80
+ --resume_path ./check_point/fiddle_tcn_orbitrap.pt \
81
+ --rescore_resume_path ./check_point/fiddle_rescore_orbitrap.pt \
82
+ --result_path ./msfiddle/demo/output_fiddle.csv --device 0
83
+ ```
84
+
85
+ If you'd like to integrate the results from SIRIUS and BUDDY, please organize the results in the format shown in `./msfiddle/demo/buddy_output.csv` and `./msfiddle/demo/sirius_output.csv`, and provide them to run FIDDLE:
86
+
87
+ ```bash
88
+ python scripts/run_fiddle.py --test_data ./msfiddle/demo/input_msms.mgf \
89
+ --config_path ./msfiddle/config/fiddle_tcn_orbitrap.yml \
90
+ --resume_path ./check_point/fiddle_tcn_orbitrap.pt \
91
+ --rescore_resume_path ./check_point/fiddle_rescore_orbitrap.pt \
92
+ --buddy_path ./msfiddle/demo/output_buddy.csv \
93
+ --sirius_path ./msfiddle/demo/output_sirius.csv \
94
+ --result_path ./msfiddle/demo/output_fiddle_all.csv --device 0
95
+ ```
96
+
97
+ See [`scripts/test_caffeine.py`](https://github.com/JosieHong/FIDDLE/blob/main/scripts/test_caffeine.py) for a worked example running FIDDLE on a caffeine Orbitrap spectrum fetched live from GNPS.
98
+
99
+ ## Reproduce paper results
100
+
101
+ All scripts should be run from the repository root (`FIDDLE/`).
102
+
103
+ | Script | Description |
104
+ |---|---|
105
+ | `running_scripts/experiments_test_benchmark.sh` | Evaluate on external benchmarks (CASMI 2016, CASMI 2017, EMBL-MCF 2.0) |
106
+ | `running_scripts/experiments_test_nist23.sh` | Evaluate on NIST23 |
107
+ | `running_scripts/experiments_test_chimeric.sh` | Evaluate on chimeric spectra |
108
+ | `running_scripts/experiments_test_noised.sh` | Evaluate under noise conditions |
109
+ | `running_scripts/experiments_ablation_study.sh` | Run ablation study |
110
+ | `running_scripts/experiments_demo.sh` | Run demo experiment |
111
+ | `running_scripts/train_released_models.sh` | Train TCN and rescore models for both Orbitrap and Q-TOF |
112
+
113
+ For training from scratch, see the train scripts (`scripts/train_tcn_gpus.py`, `scripts/train_tcn_gpus_cl.py`, `scripts/train_rescore.py`) and the corresponding config files in `./msfiddle/config/`.
114
+
115
+ ## Citation
116
+
117
+ ```
118
+ @article{hong2025fiddle,
119
+ title={FIDDLE: a deep learning method for chemical formulas prediction from tandem mass spectra},
120
+ author={Hong, Yuhui and Li, Sujun and Ye, Yuzhen and Tang, Haixu},
121
+ journal={Nature Communications},
122
+ volume={16},
123
+ number={1},
124
+ pages={11102},
125
+ year={2025},
126
+ publisher={Nature Publishing Group UK London}
127
+ }
128
+ ```
@@ -2,7 +2,12 @@
2
2
  msfiddle: A package for predicting chemical formulas from tandem mass spectra
3
3
  """
4
4
 
5
- __version__ = "2.0.1"
5
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
6
+
7
+ try:
8
+ __version__ = _pkg_version("msfiddle")
9
+ except PackageNotFoundError: # running from a source checkout without install
10
+ __version__ = "0.0.0+unknown"
6
11
 
7
12
  from .api import (
8
13
  MsFiddlePredictor,
@@ -20,6 +20,7 @@ from pyteomics import mgf
20
20
 
21
21
  from .download import download_models as download_pretrained_models
22
22
  from .download import get_checkpoint_dir
23
+ from .external_results import load_buddy_results, load_sirius_results
23
24
  from .utils.mol_utils import (
24
25
  ATOMS_INDEX,
25
26
  formula_to_dict,
@@ -198,6 +199,10 @@ class MsFiddlePredictor:
198
199
  ) -> pd.DataFrame:
199
200
  """Predict formulas for spectra in an MGF file.
200
201
 
202
+ ``buddy_path`` and ``sirius_path`` may point to either native/original
203
+ BUDDY/msbuddy and SIRIUS formula-identification outputs or legacy
204
+ msfiddle-normalized CSV files.
205
+
201
206
  The returned DataFrame uses the same result columns as the CLI CSV.
202
207
  """
203
208
 
@@ -212,8 +217,8 @@ class MsFiddlePredictor:
212
217
  encoded = self._load_mgf_spectra(test_data_path)
213
218
  predictions = self._predict_encoded(encoded, batch_size=batch_size)
214
219
 
215
- buddy_df = pd.read_csv(buddy_path) if buddy_path else None
216
- sirius_df = pd.read_csv(sirius_path) if sirius_path else None
220
+ buddy_df = load_buddy_results(buddy_path) if buddy_path else None
221
+ sirius_df = load_sirius_results(sirius_path) if sirius_path else None
217
222
 
218
223
  refined_formula = {f"Refined Formula ({k})": [] for k in range(top_k)}
219
224
  refined_mass = {f"Refined Mass ({k})": [] for k in range(top_k)}
@@ -787,7 +792,8 @@ class MsFiddlePredictor:
787
792
  candidates.extend(
788
793
  formula
789
794
  for formula, score in zip(formulas, scores)
790
- if str(formula) != "nan"
795
+ if _is_external_formula(formula)
796
+ and pd.notna(score)
791
797
  and score < self.config["post_processing"]["buddy_fdr_thr"]
792
798
  )
793
799
  if sirius_df is not None:
@@ -815,7 +821,8 @@ class MsFiddlePredictor:
815
821
  candidates.extend(
816
822
  formula
817
823
  for formula, score in zip(formulas, scores)
818
- if str(formula) != "nan"
824
+ if _is_external_formula(formula)
825
+ and pd.notna(score)
819
826
  and score > self.config["post_processing"]["sirius_score_thr"]
820
827
  )
821
828
  return candidates
@@ -934,6 +941,12 @@ def _normalize_instrument_type(instrument_type: str) -> str:
934
941
  return normalized
935
942
 
936
943
 
944
+ def _is_external_formula(value: object) -> bool:
945
+ if pd.isna(value):
946
+ return False
947
+ return str(value).strip().lower() not in {"", "na", "nan", "none", "null", "<na>"}
948
+
949
+
937
950
  def _load_config(config_path: Path) -> dict[str, Any]:
938
951
  if not config_path.exists():
939
952
  raise FileNotFoundError(f"Config file not found: {config_path}")
@@ -131,12 +131,14 @@ model:
131
131
  # ------------------------
132
132
  train:
133
133
  epochs: 500
134
- batch_size: 512 # 128
134
+ batch_size: 512 # 128 fits 11GB GPUs (CL training holds 2 forward graphs, memory ~ 2x batch)
135
+ grad_accum_steps: 1 # optimizer step every N batches; effective batch = batch_size x N (256 x 2 on 2 x 11GB GPUs = 512)
135
136
  num_workers: 8
136
137
  lr: 0.001
137
138
  weight_decay: 0.01
138
139
  warmup_ratio: 0.1
139
- early_stop_step: 10
140
+ early_stop_step: 15 # ~3x plateau patience so >=2 LR halvings get a chance
141
+ early_stop_min_epochs: 5 # patience does not count before this epoch
140
142
  patience: 5
141
143
  noised_times: 0
142
144
 
@@ -150,15 +152,18 @@ train_rescore:
150
152
  # ------------------------
151
153
  # postprocessing settings
152
154
  # ------------------------
153
- post_processing:
155
+ post_processing:
154
156
  # Orbitrap
155
- refine_atom_type: ['C', 'O', 'N', 'H']
156
- refine_atom_num: [3, 3, 3, -1]
157
+ # D0c: halogens in the refinement search so a missed halogen can be introduced and
158
+ # H rebalanced to the exact precursor mass. (Orbitrap is halogen-rich, so the model
159
+ # usually predicts halogens already; this mainly helps the cases it misses.)
160
+ refine_atom_type: ['C', 'O', 'N', 'H', 'Cl', 'Br', 'F', 'I', 'S', 'P']
161
+ refine_atom_num: [5, 5, 5, -1, 2, 1, 2, 1, 2, 1] # config E caps (08/2026 sweep): recall@20 88.5% -> 94.3% at 10ppm, 95.7% if 5ppm window validates
157
162
 
158
- mass_tolerance: 20.0 # target space
163
+ mass_tolerance: 5.0 # config E (08/2026 sweep): matches the data filters' ppm_tolerance 5 (no admitted spectrum can be orphaned) and Orbitrap instrument accuracy; halves the decoy count vs 10 ppm, recall@20 ceiling 95.7%
159
164
  ppm_mode: True
160
165
  time_out: 300
161
166
  maxium_miss_atom_num: 20 # search space
162
- top_k: 5
167
+ top_k: 20 # config E (08/2026 sweep): +2 points of rescorer ceiling over K=10; keep pair-generation and inference matched
163
168
  buddy_fdr_thr: 0.6
164
169
  sirius_score_thr: -5
@@ -165,17 +165,19 @@ model:
165
165
  # ------------------------
166
166
  train:
167
167
  epochs: 200
168
- batch_size: 512 # 128
168
+ batch_size: 512 # 128 fits 11GB GPUs (CL training holds 2 forward graphs, memory ~ 2x batch)
169
+ grad_accum_steps: 1 # optimizer step every N batches; effective batch = batch_size x N (256 x 2 on 2 x 11GB GPUs = 512)
169
170
  num_workers: 8
170
171
  lr: 0.002 # 0.001
171
172
  weight_decay: 0.01
172
173
  warmup_ratio: 0.1
173
- early_stop_step: 10
174
+ early_stop_step: 15 # ~3x plateau patience so >=2 LR halvings get a chance
175
+ early_stop_min_epochs: 5 # patience does not count before this epoch
174
176
  patience: 5
175
177
  noised_times: 3
176
178
 
177
179
  train_rescore:
178
- epochs: 100
180
+ epochs: 20
179
181
  batch_size: 128
180
182
  num_workers: 8
181
183
  lr: 0.0001
@@ -184,14 +186,19 @@ train_rescore:
184
186
  # ------------------------
185
187
  # postprocessing settings
186
188
  # ------------------------
187
- post_processing:
188
- refine_atom_type: ['C', 'O', 'N', 'H']
189
- refine_atom_num: [5, 5, 5, -1]
189
+ post_processing:
190
+ # D0c (tuned): let refinement INTRODUCE a missed halogen and rebalance H to the
191
+ # exact precursor mass. Base was ['C','O','N','H'] -> halogens unreachable.
192
+ # S/P deliberately NOT in base (already reachable when the model predicts them;
193
+ # adding them only created spurious candidates). Halogen caps kept tight to limit
194
+ # near-isobaric false positives on the CONH majority.
195
+ refine_atom_type: ['C', 'O', 'N', 'H', 'Cl', 'Br', 'F', 'I', 'S', 'P']
196
+ refine_atom_num: [5, 5, 5, -1, 2, 1, 2, 1, 2, 1] # config B (08/2026 sweep): S/P reachable; recall@20 92.3% -> 94.0%
190
197
 
191
198
  mass_tolerance: 10.0 # target space
192
199
  ppm_mode: True
193
200
  time_out: -1 # do not limit the time
194
201
  maxium_miss_atom_num: 50 # search space
195
- top_k: 5
202
+ top_k: 20 # config B (08/2026 sweep): +2 points of rescorer ceiling over K=10; keep pair-generation and inference matched
196
203
  buddy_fdr_thr: 0.6
197
204
  sirius_score_thr: -5
@@ -55,7 +55,7 @@ class MS2FDataset_CL(Dataset):
55
55
  for data_item in self.data:
56
56
  spec = data_item["spec"]
57
57
  data_item["spec"] = np.concatenate(
58
- (spec, np.zeros((padding_dim, spec.shape[1]))), axis=0
58
+ (spec, np.zeros((padding_dim, spec.shape[1]), dtype=spec.dtype)), axis=0
59
59
  )
60
60
 
61
61
  def augment_with_noise(self, data, pairs, noised_times):
@@ -76,11 +76,16 @@ class MS2FDataset_CL(Dataset):
76
76
  """
77
77
  original_length = len(data)
78
78
  augmented_data = copy.deepcopy(data)
79
- for data_item in tqdm(data, desc="Data Augmentation"):
80
- spec = data_item["spec"][:, 0]
81
- spec_mask = np.where(spec > 0, 1.0, 0.0)
79
+ # Copies are appended in whole-dataset blocks (round t of every
80
+ # spectrum, then round t+1, ...) so that augmented copy t of item j
81
+ # sits at index original_length * (t + 1) + j — the layout the pair
82
+ # index shift below assumes. Interleaving copies per spectrum instead
83
+ # silently pairs unrelated molecules once noised_times >= 2.
84
+ for _ in range(noised_times):
85
+ for data_item in tqdm(data, desc="Data Augmentation"):
86
+ spec = data_item["spec"][:, 0]
87
+ spec_mask = np.where(spec > 0, 1.0, 0.0)
82
88
 
83
- for _ in range(noised_times):
84
89
  noise = np.random.normal(0, 0.1, len(spec))
85
90
  new_spec = spec + noise * spec_mask
86
91
 
@@ -151,7 +156,7 @@ class MS2FDataset(Dataset):
151
156
  for data_item in self.data:
152
157
  spec = data_item["spec"]
153
158
  data_item["spec"] = np.concatenate(
154
- (spec, np.zeros((padding_dim, spec.shape[1]))), axis=0
159
+ (spec, np.zeros((padding_dim, spec.shape[1]), dtype=spec.dtype)), axis=0
155
160
  )
156
161
 
157
162
  def augment_with_noise(self, data, noised_times):
@@ -293,6 +298,18 @@ class MGFDataset(Dataset):
293
298
  'precursor_type', and 'use_simulated_precursor_mz'.
294
299
  """
295
300
  for spectrum in spectra:
301
+ # Skip (not crash on) adducts the encoder does not support: user
302
+ # MGFs routinely contain a few, and one unsupported PRECURSOR_TYPE
303
+ # must not abort the whole run with a KeyError.
304
+ precursor_type = spectrum["params"]["precursor_type"]
305
+ if precursor_type not in encoder["type2charge"]:
306
+ print(
307
+ "Skip {}: unsupported precursor type {}".format(
308
+ spectrum["params"].get("title", "?"), precursor_type
309
+ )
310
+ )
311
+ continue
312
+
296
313
  good_spec, _, _, spec_arr = generate_ms(
297
314
  x=spectrum["m/z array"],
298
315
  y=spectrum["intensity array"],
@@ -309,6 +326,15 @@ class MGFDataset(Dataset):
309
326
  adjust_neutral_add_vec, adjust_precursor_type = self.melt_neutral_precursor(
310
327
  spectrum["params"]["precursor_type"]
311
328
  )
329
+ if adjust_precursor_type not in encoder["precursor_type"]:
330
+ print(
331
+ "Skip {}: unsupported precursor type {} (from {})".format(
332
+ spectrum["params"].get("title", "?"),
333
+ adjust_precursor_type,
334
+ precursor_type,
335
+ )
336
+ )
337
+ continue
312
338
 
313
339
  ce, nce = parse_collision_energy(
314
340
  ce_str=spectrum["params"]["collision_energy"],
@@ -16,11 +16,18 @@ from importlib.metadata import (
16
16
 
17
17
 
18
18
  def checkpoint_release_for_package_version(package_version):
19
- """Return the major-version FIDDLE release tag that contains checkpoints."""
20
- match = re.match(r"^(\d+)", str(package_version))
19
+ """Return the FIDDLE release tag that holds this version's checkpoints.
20
+
21
+ From 2.2 on, each minor version has its own checkpoints (tag vX.Y.0,
22
+ shared by its patch releases); 2.0 and 2.1 share the v2.0.0 assets.
23
+ """
24
+ match = re.match(r"^(\d+)(?:\.(\d+))?", str(package_version))
21
25
  if match is None:
22
26
  return f"v{package_version}"
23
- return f"v{match.group(1)}.0.0"
27
+ major, minor = int(match.group(1)), int(match.group(2) or 0)
28
+ if (major, minor) < (2, 2):
29
+ return f"v{major}.0.0"
30
+ return f"v{major}.{minor}.0"
24
31
 
25
32
 
26
33
  try:
@@ -56,10 +63,14 @@ def download_url(url, output_path):
56
63
  def get_checkpoint_dir():
57
64
  """Get the directory where model checkpoints are stored.
58
65
 
59
- Returns ``~/.msfiddle/check_point``, a user-level directory that is
60
- consistent regardless of working directory or install method.
66
+ Returns ``~/.msfiddle/check_point/<release tag>``, a user-level directory
67
+ that is consistent regardless of working directory or install method. The
68
+ tag keeps checkpoints from different releases apart, so an upgrade never
69
+ silently reuses an older release's weights.
61
70
  """
62
- return os.path.join(os.path.expanduser("~"), ".msfiddle", "check_point")
71
+ return os.path.join(
72
+ os.path.expanduser("~"), ".msfiddle", "check_point", FIDDLE_RELEASE
73
+ )
63
74
 
64
75
 
65
76
  def download_models(destination=None, models=None):