Part of the MuMDIA developer documentation (see docs/README.md).
MuMDIA is a clean-room Rust reimplementation of a data-independent-acquisition
(DIA) proteomics search engine. It takes an mzML run plus a spectral library
source and produces peptide and protein-group identifications at target-decoy
FDR, with optional quantification. This document describes the pipeline as a
graph: every stage, the artifacts each stage reads and writes, the two library
sources (FASTA digest versus imported DIA-NN library) and how the orchestrator
branches between them, the single-run run orchestration and its
manifest.json, and the current best-performing workflow.
The design principle is that every computational stage is an independent command
over path-addressable inputs and outputs on disk (the interstage contract,
docs/18 B1). No stage shares in-memory state with another. run threads those
file paths, invokes the optional DeepLC fine-tune between stages, writes the
human-readable report, and records provenance. Most stage commands can therefore
be run standalone on prior outputs, on a different configuration, or on
hand-crafted minimal files.
run recomputes every stage on every invocation. There is no artifact caching,
no skip-if-exists, and no resume: run calls each stage unconditionally and
overwrites its outputs even when identical artifacts already exist in --out-dir
(run.rs:85-509; no existence check precedes any stage call, and
std::fs::create_dir_all at run.rs:90 does not clear or skip). To reuse a
prior artifact, invoke the downstream stage command standalone on it instead of
rerunning run. Use a fresh output directory for each orchestrated run. Reusing
one can leave a stale optional sidecar from an earlier configuration, and a
failed rerun can leave an old manifest beside partially replaced outputs.
The validated findings and the interstage, determinism, and sidecar contracts
cited throughout this document are consolidated, self-contained, in
docs/18_findings_and_decisions.md (sections A1-A7 for findings, B1-B3 for
contracts); the citations below point there. plan.md holds the deeper
algorithmic spec but is local-only and gitignored.
| Path | Role |
|---|---|
rust/mumdia/crates/mumdia/src/main.rs |
CLI: one clap subcommand per stage (Cmd enum, main.rs:20); each arm loads config, hashes it, and calls the stage run. |
rust/mumdia/crates/mumdia/src/stages/run.rs |
run orchestrator: preflight, library-source branch, per-run chain, manifest assembly (run.rs:85). |
rust/mumdia/crates/mumdia/src/stages/mod.rs |
Re-exports every stage module. |
rust/mumdia/crates/mumdia/src/stages/*.rs |
The stage implementations (convert, digest, peptidoforms, predict_frag, search_seed, rt_im_train, extract, features, compete, rescore, quant, report, align, audit). |
rust/mumdia/crates/mumdia/src/lib.rs |
Crate lib root (bin+lib split, lib.rs:5-16): re-exports the pipeline modules so integration tests can drive stages directly. Public modules: stages, matchers (fragment matchers, doc 06), index (Library + inverted index, doc 06), predict (predictor traits + native fallbacks, doc 06), rescoring (percolator_lite + native NN, doc 11), calibrate (LOESS/linear/percentile, doc 08), peaks (peak enumeration, doc 09), spectra (in-memory spectrum model + loaders), sidecar (Python worker dispatch, doc 13), stats (pearson/cosine/spectral_angle kernel), fdr (target-decoy + entrapment q, doc 11). |
rust/mumdia/crates/mumdia-core/src/schema.rs |
Frozen (logical name, schema version) for every artifact (artifact module, schema.rs:6). |
rust/mumdia/crates/mumdia-core/src/manifest.rs |
Manifest and ArtifactRecord (manifest.rs:10, manifest.rs:22). |
rust/mumdia/crates/mumdia-core/src/config.rs |
The single typed serde Config (config.rs:973) with per-stage sections and apply_profile (config.rs:1107). |
rust/mumdia/crates/mumdia-io/src/table.rs |
The Col/Table typed Parquet layer every stage writes through (table.rs:23). |
rust/mumdia/crates/mumdia-io/src/lib.rs |
record_artifact, inspect, init_logging, blake3 hashing. |
Three crates, one direction of dependency (mumdia -> mumdia-io ->
mumdia-core):
mumdia-core types, mass model, Config (per-stage sections + strategy enums),
schema (frozen artifact names/versions), manifest, errors
mumdia-io Col/Table over arrow+parquet (SNAPPY), blake3 hashing, JSON,
inspect, per-artifact report.json
mumdia bin + lib: fragment index, stages/, predictor + rescorer traits,
sidecar dispatch; main.rs is a thin CLI over the lib
The two ML predictors are traits with a deterministic native fallback and an
optional Python sidecar, selected by config (the sidecar replaces the native
path without changing callers, predict.rs:1-8). The rescorer is not a trait: a
free function plus the RescorerKind enum.
RtPredictor (trait, predict.rs:13)
|-- NativeRt (predict.rs:25, additive RT-coefficient model, deterministic)
\-- DeepLC (Python sidecar; predict_frag.rt_predictor = deeplc)
FragmentPredictor (trait, predict.rs:19)
|-- NativeFrag (predict.rs:73, heuristic b/y intensities, deterministic)
\-- MS2PIP (Python sidecar; predict_frag.predictor = ms2pip)
Rescorer (free fn + RescorerKind enum, no trait)
native_tda (percolator_lite) | nn_torch | mokapot | entrapment
(the last three are Python sidecars via rescore.python)
The pipeline consumes an mzML file plus one of two library sources, and produces
a fixed set of Parquet artifacts plus JSON sidecars. Most primary stage Parquets
receive a small <artifact>.report.json with row counts, parameters, hashes, and
timing. Coverage is deliberately partial: report TSVs, schema/PIN files, some
diagnostic/optional Parquets, and several Python-written outputs do not have one.
See docs/03 and docs/12 rather than assuming a report always exists.
External inputs:
--mzml <file>(mzML;.raw/.dmust be converted upstream by msconvert).- FASTA mode:
--fasta <file>(digested into the library). - Library-input mode:
--lib-precursors <parquet>+--lib-fragments <parquet>(a prebuilt library, for example an imported DIA-NN speclib).
Inputs on disk: a pre-built E. coli test library is already present under lib/:
lib_precursors.parquet + lib_fragments.parquet (an imported DIA-NN speclib
with fragment intensities and iRT) plus lib_precursors_ft.parquet (the DeepLC
fine-tuned iRT variant, the recommended --lib-precursors per docs/18 A4), and a
cached seed_psms.parquet (+ .masscal.json). See docs/19_getting_started.md
for the local sidecar environments and two copy-pasteable end-to-end runs (a
zero-dependency native FASTA run and the best-sensitivity library run).
Parquet artifacts and their column schemas as written in the code:
spectra_ms1.parquet (convert.rs:180): scan_index u32, rt_seconds f64,
mz list, intensity list.
spectra_ms2.parquet (convert.rs:207): scan_index u32, id str,
rt_seconds f64, window_id u32, window_target f64, window_lower f64,
window_upper f64, precursor_mz opt, precursor_charge opt,
mz list, intensity list.
isolation_windows.parquet (convert.rs:224): window_id u32, target f64,
lower f64, upper f64.
ms2_to_ms1.parquet (convert.rs:234): ms2_scan_index u32,
ms1_scan_index i32 (the preceding MS1 scan; -1 if none).
peptides.parquet (digest; FASTA mode only): fully-tryptic stripped peptides
plus their paired decoys.
peptidoforms.parquet (peptidoforms; FASTA mode only): concrete peptidoforms
with fixed/variable mods and charges as ProForma-lite strings.
fragment_library_precursors.parquet (predict_frag.rs:189): candidate_id
u32, peptidoform_id u32, base_peptide_id u32, peptidoform str, charge
i32, precursor_mz f64, predicted_irt f32, label str
(target/decoy), protein str, n_fragments i32. candidate_id is a
contiguous 0-based index sorted by precursor m/z (the fragment-index build
precondition).
fragment_library_fragments.parquet (predict_frag.rs:204): candidate_id
u32, mz f64, predicted_intensity f32, name str, ion_type str, ordinal
i32, frag_charge i32.
seed_psms.parquet (search_seed.rs:220): candidate_id u32, peptidoform
str, charge i32, precursor_mz f64, base_peptide_id u32, protein str,
label str, score f64 (hyperscore), spectrum_q f64 (per-spectrum
target-decoy q), observed_rt f64, predicted_irt f32, matched_peaks i32,
scan_index u32. Sidecar seed_psms.parquet.masscal.json carries the per-run
mass recalibration consumed by extract.
run_windows.parquet (rt_im_train.rs:185): candidate_id u32, rt_pred_cal
f64, rt_lo f64, rt_hi f64, im_pred_cal/im_lo/im_hi opt (IM columns
null in 3D). Sidecar cal.json records the fitted RT calibration.
psms_extracted.parquet (extract.rs:1359): candidate_id u32, apex_rt f64,
apex_im opt, apex_intensity f32, n_matched_fragments i32,
n_predicted_fragments i32, coelution_run i32, rt_pred_cal f64,
precursor_mz f64, charge i32, label str, base_peptide_id u32,
peptidoform str, protein str, predicted_irt f32, contested_frac f64,
ms1_isom1/ms1_mono/ms1_iso1/ms1_iso2 opt. Optional columns:
contested_count_frac, apportioned_frac when emit_contested_features
(extract.rs:1383); gate_apex, gate_peak_spectral, gate_coelution,
gate_spectral_entropy when emit_gate_diagnostics (extract.rs:1389).
chromatograms.parquet (extract.rs:1399): candidate_id u32, frag_name
str, frag_mz f64, frag_obs_mz f64, predicted_intensity f32, rt
largelist, intensity largelist. LargeList (64-bit offsets) is
required because total list-value count can exceed the 32-bit ListArray limit
when extraction accepts a very large candidate set.
<psms>.peaks.parquet (extract.rs:1419, written only when
extract.retain_top_peaks > 1): candidate_id u32, peak_rank i32, apex_rt
f64, start_rt f64, end_rt f64, evidence_count f64, area f64. This is a
diagnostic sidecar not yet scored downstream (see docs/18 A6, and Gotchas below).
features.parquet (features.rs:828): bookkeeping columns candidate_id,
label, base_peptide_id, peptidoform, protein, apex_rt, elution_lo,
elution_hi, precursor_mz, prelim_score, followed by one f64 column per
active feature name (features.rs:839). Sidecar features.parquet.schema.json
records the ordered feature-column list and its hashed schema_id; run.pin is
the Percolator input written deterministically alongside.
psms_competed.parquet (compete.rs:118): the same bookkeeping columns plus
every feature column carried through unchanged (compete.rs:128), for surviving
rows only. Sidecar psms_competed.parquet.schema.json carries the schema
forward for rescore.
psms_scored.parquet (schema version 3): candidate_id u32,
peptidoform str, charge i32, label str, protein str, base_peptide_id
u32, carried apex_rt/elution_lo/elution_hi, score f64, q_value f64,
peptide_q_value f64, protein_group str,
pg_q_value f64, global_q_value f64, prelim_score f64, source u32,
run_psm_q f64, experiment_psm_q f64, precursor_q f64. The q-value columns
are independent per level, not a rollup (see Gotchas).
peptide_quant.parquet (schema version 2): candidate_id, base_peptide_id,
peptidoform, charge, protein_group, nullable quantity, quant_status,
n_fragments_used, and nullable applied integration apex/bounds.
protein_group_quant.parquet (schema version 2): protein_group, nullable
quantity, quant_status, and n_peptides (unique positive base peptides).
Optional fragment_quant.parquet and
peak_bounds (quant.rs:278) diagnostics. quant-lfq emits a
protein-by-run matrix (quant.rs:479): protein_group, run i32, quantity,
n_features i32.
candidate_audit.parquet (audit.rs:180, written when
extract.emit_candidate_audit or via mumdia audit): run_id str,
precursor_id u32, modified_sequence str, charge i32,
target_decoy_label str, entrapment_label bool, then the boolean stage-flags
candidate_generated, traces_extracted, peak_generated, peak_selected,
variant_selected, target_decoy_winner, passed_precursor_fdr,
passed_peptide_fdr, reported, and rejection_reason str (earliest loss
reason).
Human-readable outputs: peptides.tsv, proteins.tsv (report), and
manifest.json (orchestrator).
main.rs is a thin clap layer: the Cmd enum (main.rs:20) defines one
subcommand per stage plus the run orchestrator, the experiment-level
align/mbr, and the utilities inspect/report/doctor. Each stage arm
loads the config, hashes its canonical JSON into a config_hash, and calls the
stage run; every stage command is runnable standalone on prior outputs. Flags
are --kebab-case; a num_args = 1.. flag accepts several values.
Which subcommands read --config (verified against main.rs):
Takes --config |
No --config |
|---|---|
digest, peptidoforms, predict-frag, search-seed, rt-im-train, extract, features, compete, rescore, quant, run, align, mbr, doctor (optional) |
convert, quant-lfq, inspect, audit, report |
The five with no --config are fixed-behavior: convert always runs on
Config::default() (main.rs:397); quant-lfq takes typed string flags instead
(main.rs:642-658); inspect, audit, and report load no config at all
(main.rs:712, main.rs:557, main.rs:715). The full subcommand set, in source
order:
mumdia <cmd> |
Inputs (flags) | Outputs (flags / artifacts) |
|---|---|---|
convert (main.rs:23) |
--mzml, --out-dir; caps --max-spectra, --top-peaks-ms2, --top-peaks-ms1 (each 0 = uncapped) |
spectra_ms1, spectra_ms2, isolation_windows, ms2_to_ms1 in --out-dir |
digest (main.rs:43) |
--fasta, --config |
--out = peptides.parquet |
peptidoforms (main.rs:52) |
--peptides, --config |
--out = peptidoforms.parquet |
predict-frag (main.rs:61) |
--peptidoforms, --work-dir (default sidecar_work), --config |
--out-precursors, --out-fragments (the library pair) |
search-seed (main.rs:75) |
--ms2, --library-precursors, --library-fragments, --config |
--out = seed_psms.parquet (+ <out>.masscal.json) |
rt-im-train (main.rs:88) |
--seed-psms, --library-precursors, --config |
--out-windows = run_windows.parquet, --out-cal = cal.json |
extract (main.rs:101) |
--ms2, --library-precursors, --library-fragments, --run-windows, --ms1 (opt), --mass-cal (opt), --restrict-candidates (opt allowlist), --config |
--out-psms = psms_extracted.parquet, --out-chrom = chromatograms.parquet (+ <psms>.peaks.parquet when retain_top_peaks>1) |
features (main.rs:130) |
--psms, --chromatograms, --seed (opt), --config |
--out = features.parquet (+ .schema.json), --out-pin = PIN |
compete (main.rs:146) |
--features, --config |
--out = psms_competed.parquet (+ .schema.json) |
rescore (main.rs:155) |
--competed (1+ competed tables), --config |
--out = psms_scored.parquet |
quant (main.rs:165) |
--psms-scored, --chromatograms, --config |
--out-peptide, --out-protein, --out-fragment (opt), --out-peak-bounds (opt) |
quant-lfq (main.rs:184) |
--inputs (1+ per-run tables), --method (maxlfq default / directlfq), --normalize (median_ratio default / median / none) |
--out = protein-by-run matrix; no --config |
run (main.rs:199) |
--mzml, --out-dir; --fasta xor (--lib-precursors + --lib-fragments); --config, --profile, --max-spectra, --top-peaks-ms2 |
the full chain + manifest.json |
align (main.rs:230) |
--seeds (1+ seed_psms, first = reference), --config |
--out = alignment.parquet |
mbr (main.rs:240) |
--scored (experiment-wide scored_combined), --psms (1+ per-run in source order), --frag (0+ per-run fragment_quant), --config |
--out = transferred.parquet, --out-scored (opt augmented scored) |
inspect (main.rs:261) |
positional artifact (any parquet) |
schema + head + row count to stdout; no --config |
audit (main.rs:265) |
--library-precursors, --psms, --competed, --scored, --q (0.01), --run-id (run), --entrapment-substr ("") |
--out = candidate_audit.parquet; no --config |
report (main.rs:292) |
--scored, --peptide-quant (opt), --protein-quant (opt), --q (0.01) |
peptides.tsv + proteins.tsv in --out-dir |
doctor (main.rs:305) |
--config (opt) |
probes the configured sidecar interpreters; nonzero exit if any FAIL |
Per-subcommand specifics that are easy to miss:
convertis the only stage command with no--config; it always runs onConfig::default()(main.rs:397). Because the three caps are not part of the config,convertfolds them into the artifactconfig_hash(main.rs:402, comment.md A2/C4) so two different caps do not collide on an identical hash.top_peaks_ms2is an irreversible conversion-time cap that also affects extraction/features/quant;search_seed.top_n_peaksis the seed-only alternative.quant-lfq,inspect,audit, andreportalso take no--config.quant-lfqvalidates its two string flags:--methodmust bemaxlfqordirectlfq(main.rs:649) and--normalizeis parsed byNormalizeMethod::from_token(config.rs:754), erroring on anything butmedian_ratio/median/none(main.rs:652).search-seedreadsextract.bucket_sizefrom the config (not asearch_seedfield) for its fragment-index bucketing (main.rs:473,run.rs:225).rescorestandalone accepts several--competedtables for experiment-wide scoring and uses a fixedsidecar_workworking directory (main.rs:588); insiderunit is passed exactly one table and a per-out-dir work directory.alignusesrt_im_train.q_trainfor its training set and a hardcoded 100-knot grid (main.rs:666-667); it needs >=2 seeds and is not part of therunchain.mbr(main.rs:671) is a wired standalone command, not part ofrun. It hard-errors whenmbr.strategy = none(main.rs:680), when fewer than two--psmsare supplied (main.rs:685), or whenmbr.pythonis unset (main.rs:688), then runs thembr_worker.pysidecar with thembr.*thresholds (main.rs:697). It transfers identifications across a run set (Stage D3); see the extend section for its stub status insiderun.doctor(main.rs:313) probes three interpreters and prints[ ok ]/[FAIL]/[skip]per line, exiting nonzero if any fail:rescore.python(packages depend on the classifier:torch,numpy,pandas,pyarrowfornn_torch, elsemokapot,sklearn,numpy,pandas,pyarrow),predict_frag.deeplc_python(deeplc,numpy,pandas), andpredict_frag.ms2pip_python(ms2pip,numpy,pandas).
FASTA mode library-input mode
(--fasta present) (--lib-precursors + --lib-fragments)
| |
digest peptides.parquet |
| |
peptidoforms peptidoforms.parquet |
| |
predict-frag fragment_library_precursors.parquet (supplied directly, digest/
fragment_library_fragments.parquet peptidoforms/predict-frag skipped)
\________________________________________/
|
lib_p (precursors)
lib_f (fragments)
|
--mzml ---> convert ---> spectra_ms1.parquet, spectra_ms2.parquet,
isolation_windows.parquet, ms2_to_ms1.parquet
|
spectra_ms2 + lib_p + lib_f --> search-seed --> seed_psms.parquet
seed_psms.parquet.masscal.json
|
[optional] lib_p + seed --> deeplc_finetune --> fragment_library_precursors_ft.parquet
(new table with updated predicted_irt; lib_p := *_ft)
|
seed + lib_p --> rt-im-train --> run_windows.parquet, cal.json
|
spectra_ms2 + lib_p + lib_f + run_windows + spectra_ms1 + masscal
--> extract --> psms_extracted.parquet, chromatograms.parquet
[<psms>.peaks.parquet if retain_top_peaks>1]
|
psms_extracted + chromatograms + seed --> features --> features.parquet,
features.parquet.schema.json, run.pin
|
features --> compete --> psms_competed.parquet (+ .schema.json)
|
psms_competed --> rescore --> psms_scored.parquet
|
[optional] lib_p + psms_extracted + competed + scored --> audit --> candidate_audit.parquet
|
psms_scored + chromatograms --> quant --> peptide_quant.parquet,
protein_group_quant.parquet, fragment_quant.parquet
|
psms_scored + quant --> report --> peptides.tsv, proteins.tsv
|
selected primary Parquets recorded --> manifest.json
The spectral library is the experiment-level, run-independent artifact pair
(lib_p, lib_f). The orchestrator produces it in one of two ways, decided by a
match on (p.lib_precursors, p.lib_fragments) at run.rs:98:
-
Library-input mode (
(Some(lp), Some(lf)),run.rs:99): the supplied library is consumed directly.digest,peptidoforms, andpredict-fragare skipped and the FASTA is never read. The orchestrator only reads the two Parquet files to record their row counts in the manifest, then uses their paths downstream (run.rs:107-125). This is the highest-sensitivity path, used with an imported DIA-NN speclib that already carries fragment intensities and iRT. -
FASTA-digest mode (the
_ =>arm,run.rs:127): the library is built from the FASTA.digest::runwritespeptides.parquet(run.rs:132),peptidoforms::runwritespeptidoforms.parquet(run.rs:149), andpredict_frag::runwrites both library Parquet files (run.rs:166). The returned(lib_p, lib_f)paths feed the rest of the chain. This path has zero external runtime dependencies (native predictors).
preflight (run.rs:38) validates the mode before any compute: exactly one of
the two source configurations must be present, and every required input path must
exist (run.rs:42-61). It also checks sidecar prerequisites: finetune_deeplc
requires predict_frag.deeplc_python; Mokapot and NnTorch require
rescore.python; entrapment requires rescore.entrapment_marker. Config::validate
rejects the same rescorer omissions at load time. mumdia doctor additionally
checks that the configured interpreters can import the required packages.
After the library is resolved, the orchestrator runs the per-run stages in order,
recording each output in the manifest with record_artifact (run.rs:85-509):
-
convert::run(run.rs:196) reads the mzML through mzdata, centroids profile spectra, caps peaks, synthesizes a full-range window for AIF/all-ion scans, and writes the four spectra artifacts. It returns aConvertOutputsstruct of paths (convert.rs:96).runforwards itsmax_spectraandtop_peaks_ms2but forcestop_peaks_ms1 = 0(run.rs:201), sorunnever caps MS1 peaks; only the standaloneconvertcommand can (--top-peaks-ms1). -
search_seed::run(run.rs:219) runs a native Sage-lite broad DIA search overspectra_ms2against the fragment index for calibration (not final ID). It writesseed_psms.parquetand the mass recalibrationmasscal.json. The seed is iRT-independent, so it is computed once here on the base library and reused. It borrowsextract.bucket_sizefor the fragment-index bucketing (run.rs:225), not asearch_seedfield. -
Optional DeepLC multitask fine-tune (
run.rs:242, gated onrt_im_train.finetune_deeplc):sidecar::run_deeplc_finetuneadapts the RT model to this run's confident seed PSMs and writes a newfragment_library_precursors_ft.parquetwhosepredicted_irtvalues are updated; the input library is not modified. It is driven by thert_im_trainfieldsfinetune_epochs,finetune_patience,q_train(the confident-seed cutoff), andfinetune_batch(run.rs:259-262;finetune_batch = 0auto-scales the batch to the seed size). Thelib_pbinding is then rebound to the fine-tuned file so rt-im-train and extract read it.lib_fis unchanged (fine-tune touches iRT only). This step is nondeterministic (no fixed torch/numpy seed). -
rt_im_train::run(run.rs:284) maps the run-independent iRT onto this run's observed RT (LOESS or linear) and sets per-candidate RT windows from the residual percentile, writingrun_windows.parquetandcal.json. -
extract::run(run.rs:303) is the core stage: a peak-major cascade over the inverted fragment index. It readsspectra_ms2, both library files,run_windows,spectra_ms1(for MS1 isotope XICs), and themasscal.json; it writespsms_extracted.parquetandchromatograms.parquet.runpassesrestrict_candidates: None(no allowlist). -
features::run(run.rs:335) computes the config-selected feature vector plusprelim_scoreper PSM and emitsfeatures.parquet, its schema sidecar, andrun.pin. It also readsseedfor search-engine corroboration features. -
compete::run(run.rs:354) keeps the best candidate per competition group before FDR counting, writingpsms_competed.parquetand carrying the feature schema forward. -
rescore::run(run.rs:370) does semi-supervised rescoring and native target-decoy q-values at multiple contexts, writingpsms_scored.parquet. Inrunit is passed a single competed table (std::slice::from_ref(&competed)), so this is single-run rescoring; the standalonerescorecommand accepts several tables for experiment-wide scoring. -
Optional
audit::run(run.rs:405, gated onextract.emit_candidate_audit) reconstructs the per-candidate identification-loss ladder intocandidate_audit.parquet. It is a cheap join over the artifact chain and runs no extraction. Insiderunits parameters are fixed:q_threshold = 0.01,run_id = out_dir, and an emptyentrapment_substr(run.rs:413-415); the standaloneauditcommand exposes all three as flags. -
quant::run(run.rs:422) integrates fragment chromatograms and rolls up to protein groups, writingpeptide_quant,protein_group_quant, andfragment_quant. -
report::runwritespeptides.tsvandproteins.tsvfrom the scored table plus quant, thresholded atquant.q_threshold.runreads the rescore artifact report and uses its actual classifier in stdout and the manifest;psms_scored.parquet.report.jsonremains the authoritative record.
Manifest (manifest.rs:22) has five fields: mumdia_version (the crate
version, env!("CARGO_PKG_VERSION"), stamped by Manifest::new), config_json
(the fully-resolved canonical config), config_hash (its blake3 hash),
model_identities (a BTreeMap<String, String>), and artifacts (a
BTreeMap<String, ArtifactRecord>). Manifest::new (manifest.rs:34) seeds it
with the canonical config JSON and hash (run.rs:89,93). Selected primary
Parquet outputs are recorded by record_artifact (mumdia-io), which builds an
ArtifactRecord
(manifest.rs:10) with nine fields: logical_name, path, format,
schema_name, schema_version, rows, content_hash, producing_stage, and
config_hash. Manifest::record (manifest.rs:44) keys artifacts by
logical_name, so re-recording the same logical name overwrites and the map is
sorted, not insertion-ordered. The producing_stage is the stage name
(digest, convert, ...); in library-input mode the two library records carry
the synthetic stage "library-input" (run.rs:114,122). Four model_identities
keys are recorded: rt_predictor, fragment_predictor, rescorer, and
feature_schema_id. The rescorer identity comes from the rescore report, and the
fine-tuned precursor table is recorded when it is the table actually consumed
downstream. Calibration JSON, PIN/schema companions, report TSVs, and some
optional diagnostic sidecars are not manifest artifacts. The manifest is written last to
<out_dir>/manifest.json (run.rs:501) with mumdia_io::json::write_json. The
manifest is provenance recorded, not required: because inputs are
path-addressable, no stage depends on it to run (docs/18 B1).
The validated best-performing configuration (docs/18 A1 and its "current best workflow") is library-input mode with:
- an imported DIA-NN library (fragment intensities + iRT),
- per-run DeepLC multitask fine-tune of iRT (
rt_im_train.finetune_deeplc = truepredict_frag.deeplc_python), which is essential in library mode because raw DIA-NN iRT is locally noisy (~110 s MAD) and the fine-tune tightens it to ~13-27 s (docs/18 A4),
- the Extended feature set (
features.set = extended, applied by--profile dia,config.rs:1101), - the
nn_torchPyTorch MLP rescorer (rescore.classifier = nn_torch+rescore.python, withrescore.strict = true), which is the dominant sensitivity lever and beats the native linear rescorer by ~8.5% (docs/18 A1), - a loose default apex-intensity Pearson gate
(
extract.gate_mode = apex_pearson,extract.min_frag_corr ~= 0.2), since a strong nonlinear rescorer prefers recall and absorbs the loose-gate flood (docs/18 A1/A2), - the conversion-time MS2 peak cap explicitly kept at 300 on AIF
(
--top-peaks-ms2 300; both conversion entry points otherwise default to uncapped). This is distinct from seed-onlysearch_seed.top_n_peaks.
On LFQ_Orbitrap_AIF_Ecoli_01.mzML this historically reaches ~10,300
precursor-shaped (peptidoform, charge) report rows selected at
peptide_q_value <= 0.01; it is not a precursor-q count or a universal preset.
The zero-dependency native FASTA-digest mode (~1,213 rows under the same report
definition) is the high-precision fallback. See docs/20 for the exact command,
acceptance gates, and quantification-specific choices.
| Name | file:line | What it does |
|---|---|---|
Cmd (enum) |
main.rs:20 |
One clap subcommand per stage; Run at main.rs:199. |
main |
main.rs:386 |
Parses the CLI, loads config, dispatches to the stage run. |
load_config |
main.rs:376 |
Reads a config JSON or returns Config::default(). |
doctor |
main.rs:313 |
Probes configured sidecar interpreters for required packages. |
RunParams |
run.rs:18 |
The orchestrator's inputs (config, fasta, mzml, out_dir, lib paths, caps). |
preflight |
run.rs:38 |
Validates mode, input existence, and sidecar prerequisites before compute. |
stages::run::run |
run.rs:85 |
The orchestrator: library branch, per-run chain, manifest write. |
Manifest / ArtifactRecord |
manifest.rs:22 / manifest.rs:10 |
Provenance record for a chained run. |
artifact module |
schema.rs:6 |
Frozen (logical name, schema version) constants. |
Config::apply_profile |
config.rs:1107 |
Applies the dia preset (Extended features, apex window 5, RT prior 120 s). |
Config::canonical_json |
config.rs:1116 |
Canonical JSON for hashing into the manifest. |
NormalizeMethod::from_token |
config.rs:754 |
Parses --normalize for quant-lfq (median_ratio/median/none). |
Manifest::record |
manifest.rs:44 |
Inserts an ArtifactRecord keyed by logical_name (overwrites, sorted map). |
Col (enum) |
table.rs:23 |
Typed column for writing Parquet; LargeListF32 for chromatograms. |
Table::read + typed getters |
table.rs:204 |
Reads a Parquet artifact and extracts typed columns. |
The orchestrator reads a single typed Config (config.rs:973) with per-stage
sections. Every field has #[serde(default)] and the struct uses
deny_unknown_fields, so a partial JSON overrides only named fields and an
unknown key is a hard error. --profile dia is applied on top of the loaded
config (main.rs:607) before run. The fields this overview area touches:
| Field | Default | Effect |
|---|---|---|
rng_seed |
0 |
Seeds decoy generation and rescorer folds (determinism). |
digest.decoy.strategy |
reverse |
Decoy scheme; diann_shift and none are rejected/no-op. |
peptidoforms.charge_min / charge_max |
2 / 3 |
Precursor charge range enumerated in FASTA mode. |
predict_frag.rt_predictor |
native |
native or deeplc iRT source (recorded in manifest). |
predict_frag.predictor |
native |
native or ms2pip fragment intensities. |
predict_frag.deeplc_python |
None |
Interpreter for DeepLC; required if finetune_deeplc; probed by doctor. |
predict_frag.ms2pip_python |
None |
Interpreter for MS2PIP; probed by doctor (main.rs:332). |
predict_frag.sidecar_script_dir |
"scripts" |
Where sidecar workers are resolved from (used by rescore/mbr). |
search_seed.top_n_peaks |
300 |
Seed-only MS2 peak cap (keep 300 on AIF, docs/18 A3). |
extract.bucket_size |
(see ExtractConfig) |
Fragment-index bucket width; search-seed reads it too (main.rs:473). |
rt_im_train.calibration_method |
loess |
RT calibration; none is rejected by validation. |
rt_im_train.q_train |
(see RtImTrainConfig) |
Confident-seed q cutoff for the DeepLC fine-tune and the align reference set. |
rt_im_train.finetune_deeplc |
false |
Enables the per-run DeepLC fine-tune of iRT (best workflow). |
rt_im_train.finetune_epochs / finetune_patience |
(see config) | DeepLC fine-tune schedule (run.rs:259-260). |
rt_im_train.finetune_batch |
0 |
0 auto-scales the fine-tune batch to seed size. |
extract.min_frag_corr |
0.2 |
Spectral-agreement gate threshold; loose default suits nn_torch. |
extract.gate_mode |
apex_pearson |
Which spectral score the gate thresholds (GateMode, config.rs:591). |
extract.retain_top_peaks |
1 |
>1 writes the <psms>.peaks.parquet sidecar (not yet scored). |
extract.emit_candidate_audit |
false |
Emits candidate_audit.parquet in run. |
extract.emit_gate_diagnostics |
false |
Adds the four gate_* columns to psms_extracted. |
features.set |
minimal |
minimal (14) / rich (44) / extended (381, per the feature_sets_sized test features.rs:1590-1598); dia profile sets extended. |
compete.mode |
winner_take_all |
Within-group competition resolution (CompetitionMode). |
rescore.classifier |
native_tda |
Rescorer; nn_torch is the best lever, needs rescore.python. |
rescore.python |
None |
Interpreter for mokapot / nn_torch / entrapment sidecars. |
mbr.strategy |
none |
MBR mode; none makes the standalone mumdia mbr command error out (main.rs:680). |
mbr.python |
None |
mbr_worker.py interpreter; required when strategy != none (main.rs:688). |
quant.q_threshold |
0.01 (QuantConfig) |
q-value threshold for the human-readable report. |
The config surface was recently pruned of dead or unwired fields (see
docs/02_config_and_data_model.md): threads, extract.{k_select, max_fragment_charge, scan_scale, scan_window_mode} + ScanWindowMode, digest.decoy.{ratio, source}
DecoySource,search_seed.precursor_tol_ppm,rt_im_train.tolerance_regimeToleranceRegime,FeatureSet::Custom,MatcherKind::Naive, andCompetitionMode::from_tokenwere removed. Do not reintroduce them. Kept deliberately as documented hooks:DecoyStrategy::DiannShift(deferred, license-checked; rejected by validation),CalibrationMethod::None(rejected by validation with a clear message), and the wholembrsection, which is wired to the standalonemumdia mbrcommand (main.rs:671, callsmbr_worker.py) but is not part of therunchain and needs >=2 runs.
- Determinism is required (docs/18 B2, the determinism contract): seed the RNG,
keep numeric summation order fixed. A HashMap f32 sum shifting the apex once broke
reproducibility; use ordered maps or sorted iteration where floats are summed.
The DeepLC fine-tune is the one deliberate exception (nondeterministic; no fixed
torch seed), so a
runwithfinetune_deeplc = trueis not bit-reproducible. candidate_idmust be a contiguous 0-based index sorted by precursor m/z; the fragment index build in extract depends on it. An imported library must be re-indexed bymake_reverse_decoys.pyto satisfy this.- q-value columns in
psms_scoredare independent per level, not a rollup (docs/18 A5):q_value(==experiment_psm_q),run_psm_q(per source),precursor_q(peptidoform+charge),peptide_q_value(stripped sequence),pg_q_value. Coarser grouping pools evidence, so peptide counts can exceed precursor counts. Report at the correct context:precursor_qfor a precursor matrix,run_psm_qfor cross-run library-mode quant. Do not threshold PSM q then deduplicate. - Every default-off knob keeps the production chain byte-identical when unset;
retain_top_peaks=1,emit_candidate_audit=false,emit_gate_diagnostics=false, and the other sensitivity-program toggles all default to the legacy path. - The extraction gate is a training-pool and null-curation lever, not feature work (docs/18 A2). Loosening it floods the pool with decoy-enriched junk that corrupts a linear rescorer; a nonlinear rescorer absorbs it. The gate optimum therefore inverts by rescorer.
- The selected apex is the strongest peak only ~48-52% of the time (docs/18 A6);
the
<psms>.peaks.parquettop-K sidecar exists to expose alternative peaks but is not yet promoted through features/rescore.
- To add a stage: implement it as
stages/<name>.rswith arun(Params)that reads path-addressable inputs and writes Parquet through theCol/Tablelayer plus an<artifact>.report.json; add a(logical name, version)toschema.rs; add aCmdarm inmain.rs; and thread it intorun.rswith arecord_artifactcall. Keep the stage runnable standalone. - To add an artifact column: add the
Col::in the writing stage, update the reading stage's typed getter, and bump the schema version inschema.rsif the change is not backward-compatible (PSMS_SCOREDis already at version 3). - To add a tuning profile: extend the
matchinConfig::apply_profile(config.rs:1107); it should only set existing typed config fields, never add plumbing. - To add a library source: extend the
matchon(lib_precursors, lib_fragments)inrun.rs:98and the correspondingpreflightbranch (run.rs:43). The rest of the chain consumes(lib_p, lib_f)unchanged. - To wire a new sidecar: follow the positional-CLI file contract
(
sidecar::resolve_script+ a worker inscripts/), gate it behind a config field, and check its prerequisites inpreflightanddoctor. - Stubs and unwired areas to be aware of:
mbrhas a wired standalone command (mumdia mbr->mbr_worker.py) but is a partial sidecar (not the full experiment-wide flow) and is not in therunchain (needs >=2 runs);alignis likewise standalone-only (needs >=2 runs); ion mobility / 4D is a data-model stub only (IM columns are always null in 3D); vendor readers other than mzML do not exist. Do not document these as complete.