Skip to content

Adding remaining Koina models#1073

Open
pcruzparri wants to merge 26 commits into
smith-chem-wisc:masterfrom
pcruzparri:KoinaRemainingModels
Open

Adding remaining Koina models#1073
pcruzparri wants to merge 26 commits into
smith-chem-wisc:masterfrom
pcruzparri:KoinaRemainingModels

Conversation

@pcruzparri

@pcruzparri pcruzparri commented May 25, 2026

Copy link
Copy Markdown
Contributor

TLDR

Add 21 Koina prediction models, refactor model layer, introduce null/empty parameter convention

Summary

Adds 21 new concrete Koina model implementations across fragment intensity, CCS, crosslink, detectability, and retention time categories. Refactors the shared model infrastructure: deduplicates request batching, adds neutral loss parsing, switches from positional to name-based output extraction, and introduces a three-tier null/empty/populated convention for parameter validation (collision energy, instrument type, fragmentation type). The spline models are not included in this PR due to 1) lack of necessity and 2) different output convention.

Models Added

Fragment Intensity (16)

Model Koina Model Name Required Inputs Notes
Prosit2019Intensity Prosit_2019_intensity seq, charge, CE CE any value
Prosit2020IntensityHCD Prosit_2020_intensity_HCD seq, charge, CE CE any value
Prosit2020IntensityCID Prosit_2020_intensity_CID seq, charge Fixed NCE=35
Prosit2020IntensityTMT Prosit_2020_intensity_TMT seq, charge, CE, frag_type Frag: HCD, CID
Prosit2023IntensityTimsTOF Prosit_2023_intensity_timsTOF seq, charge, CE CE any value
Prosit2024IntensityCit Prosit_2024_intensity_cit seq, charge, CE, frag_type Frag: HCD, CID
Prosit2024IntensityPTMsGl Prosit_2024_intensity_PTMs_gl seq, charge, CE, frag_type Frag: HCD, CID; 34 UNIMODs
Prosit2025IntensityLac Prosit_2025_intensity_lac seq, charge, CE, frag_type, inst_type Frag: HCD/CID; Inst: eclipse/astral/lumos
Prosit2025Intensity22PTM Prosit_2025_intensity_22PTM seq, charge, CE, frag_type All UNIMOD accepted
Prosit2025Intensity40PTM Prosit_2025_intensity_40PTM seq, charge, CE, frag_type All UNIMOD accepted
Prosit2025IntensityPtm2 Prosit_2025_intensity_ptm2 seq, charge, CE, frag_type Frag: HCD, CID
Altimeter2024Intensities Altimeter_2024_intensities seq, charge, CE (20–40) CE range-restricted; ^ charge delimiter
UniSpec UniSpec seq, charge, CE, inst_type Inst: QE/QEHFX/LUMOS/ELITE/VELOS/NONE
AlphaPeptDeepMs2Generic AlphaPeptDeep_ms2_generic seq, charge, CE, inst_type Inst: QE/LUMOS/TIMSTOF/SCIEXTOF
Ms2Pip (7 models) ms2pip_* seq, charge Fixed CE, no CE input

CCS (2)

Model Koina Model Name Required Inputs Notes
AlphaPeptDeepCCSGeneric AlphaPeptDeep_ccs_generic seq, charge No charge constraint
IM2Deep IM2Deep seq, charge No charge constraint

Crosslink (3)

Model Koina Model Name Required Inputs Notes
Prosit2023IntensityXLCMS2 Prosit_2023_intensity_XL_CMS2 alpha_seq, beta_seq, charge, CE CE any value
Prosit2024IntensityXLNMS2 Prosit_2024_intensity_XL_NMS2 alpha_seq, beta_seq, charge, CE CE any value
Prosit2023IntensityXLCMS3 Prosit_2023_intensity_XL_CMS3 seq (alpha slot), charge Fixed NCE=35; single sequence

Detectability (1)

Model Koina Model Name Required Inputs Notes
PFly2024FineTuned pfly_2024_fine_tuned seq only No modifications

Retention Time (10, 9 new + existing Prosit2019iRT updated)

Model Koina Model Name Required Inputs Notes
Prosit2019iRT Prosit_2019_irt seq only iRT
Prosit2020iRTTMT Prosit_2020_irt_TMT seq only iRT; requires N-term TMT/iTRAQ
Prosit2024iRTCit Prosit_2024_irt_cit seq only iRT
Prosit2024iRTPTMsGl Prosit_2024_irt_PTMs_gl seq only iRT; 34 UNIMODs
Prosit2025iRTLac Prosit_2025_irt_lac seq only iRT
Prosit2025iRT22PTM Prosit_2025_irt_22_PTM seq only iRT; all UNIMOD
Prosit2025iRT40PTM Prosit_2025_irt_40_PTM seq only iRT; all UNIMOD
AlphaPeptDeepRTGeneric AlphaPeptDeep_rt_generic seq only iRT; seq up to 500
DeeplcHelaHf Deeplc_hela_hf seq only iRT
ChronologerRT Chronologer_RT seq only Absolute RT (not iRT)

Breaking Changes

Null/empty convention for Allowed* parameter collections

AllowedCollisionEnergies, AllowedInstrumentTypes, and AllowedFragmentationTypes changed from HashSet<T> to HashSet<T>?. Previously an empty set meant "bypass validation entirely." Now:

Collection state Meaning
null Parameter not applicable — validation skipped
{} (empty) Parameter IS required — must be non-null, any value accepted
{a, b, c} Parameter IS required — value must be in the set
Models that don't send a parameter to Koina have its allowed set = null (e.g., Ms2Pip models for CE). Models that do send to Koina use {} (Prosit2019-2025 intensity models, UniSpec, AlphaPeptDeep) or a populated set (Altimeter with range 20–40, Prosit TMT/Cit/Lac/PTM with specific fragmentation types).

Existing models updated

  • PFly2024FineTuned: namespace fix; null-forgiving operator in ToBatchedRequests
  • Prosit2019iRT, Prosit2020iRTTMT: updated to use new base infrastructure
  • RetentionTimeModel: removed unnecessary .ToArray() call

Infrastructure Changes

  • KoinaModelBase: new BuildBatchedRequest helper and InputField record eliminates duplicated request construction across 20+ models
  • Output extraction: by output name ("ccs", "annotation", etc.) instead of positional Outputs[0] — more resilient to API changes
  • Neutral loss: ParseFragmentAnnotation handles NL suffixes (-H2O, -NH3); BaseFragmentIdentifier uses first-dash stripping
  • Thread-safety docs: documented on all abstract model types
  • Switch exhaustiveness: all ParameterHandlingMode switches include default: throw
  • Crosslink beta validation: moved null-check into ValidateModelSpecificInputs

…e, unrestricted CE

- Add ParsedFragmentAnnotation record with neutral loss parsing
- Extract API outputs by name instead of positional index
- Fix intensity variable bug (was using m/z instead of intensities)
- Support neutral loss mass calculation in fragment ion matching
- Add CreateUnimodConverterAcceptAll for models accepting any modification
- Make KoinaModelBase members protected for subclass extensibility
- Remove collision energy restrictions on Prosit2020IntensityHCD
- Move DetectabilityModel to AbstractClasses namespace
…veness, fix validation

- Add BuildBatchedRequest helper + InputField record to KoinaModelBase,
  eliminating ~770 lines of duplicated dictionary construction across 37 models
- Remove Guid.NewGuid() from batch IDs (simpler counter-based IDs);
  server ignores the id field for deduplication
- Add 'default: throw' to all ParameterHandlingMode switches for
  future-proofing against new enum values
- Replace positional Outputs[0] lookup in CollisionalCrossSectionModel
  with name-based "ccs" output resolution
- Move crosslink beta-null check from AsyncThrottledPredictor loop into
  ValidateModelSpecificInputs for consistent validation
- Remove stale Outputs.Count != 3 guards (ExtractOutputs validates by name)
- Add virtual NumberOfPredictedFragmentIons to FragmentIntensityModel base;
  override in 19 concrete models for polymorphic access
…, thread-safety docs, RT ToArray

- Fix BaseFragmentIdentifier computed property: use IndexOf('-') instead of
  string-length arithmetic that broke for multi-character neutral loss formulas
  (e.g. H3PO4 would include the trailing dash in the base identifier)
- Add missing null-forgiving operator on ValidatedFullSequence in
  PFly2024FineTuned.ToBatchedRequests (consistent with all other models)
- Document thread-safety caveat on FragmentIntensityModel,
  CollisionalCrossSectionModel, CrosslinkFragmentIntensityModel,
  and DetectabilityModel (previously only on RetentionTimeModel)
- Remove unnecessary .ToArray() in RetentionTimeModel.ResponseToPredictions
  (List<string> already implements IReadOnlyList<string>)
…rt charge guard

- Remove Contains('+') pre-filter in GenerateLibrarySpectraFromPredictions
  that silently dropped Altimeter (^ delimiter) and UniSpec annotations.
  Parse failures are now caught by try/catch around ParseFragmentAnnotation
  in the second loop, matching the ResponseToPredictions pattern.
- Replace tpLookup indexer (KeyNotFoundException risk) with TryGetValue
  + continue, consistent with ResponseToPredictions at line 458.
- Add ParameterWarning fallback in CollisionalCrossSectionModel realignment
  (was SequenceWarning-only, unlike the other two model types).
- Change BaseFragmentIdentifier LastIndexOf('-') to IndexOf('-') to match
  the dash-finding logic in ParseFragmentAnnotation.
- Revert the charge <= 0 guard in CollisionalCrossSectionModel.ValidateModelSpecificInputs
  and add a TODO to revisit what empty AllowedPrecursorCharges means.
Phase 1 — Validation logic change:
- Change AllowedCollisionEnergies, AllowedInstrumentTypes, AllowedFragmentationTypes
  base property types from HashSet<T> to HashSet<T>? with default null
- null = parameter not applicable to this model (skip validation entirely)
- empty HashSet = parameter IS required but any value accepted
- populated HashSet = parameter IS required AND value must be in set
- Split the combined 'missing required params' check into per-parameter checks
  with individual error messages

Phase 2 — Model migration:
- Models that send collision_energies to Koina keep empty HashSet (required, any value):
  All Prosit fragment models (except CID), UniSpec, AlphaPeptDeep, XLCMS2, XLNMS2
- Models that don't send collision_energies change to null (not applicable):
  Prosit2020IntensityCID, all 7 Ms2Pip models, Prosit2023IntensityXLCMS3
- Altimeter keeps its populated range (already correct)
- XLCMS2 and XLNMS2 add explicit AllowedCollisionEnergies = {} override
  (previously inherited now-changed base default)
- Add XML doc comments to AllowedPrecursorCharges, AllowedCollisionEnergies,
  AllowedInstrumentTypes, and AllowedFragmentationTypes in
  FragmentIntensityModel documenting the three-tier convention
- Add same docs to AllowedPrecursorCharges and AllowedCollisionEnergies
  in CrosslinkFragmentIntensityModel
- Update AllowedUnimodIds doc in KoinaModelBase to clarify it belongs to
  the converter layer (not parameter validation) with empty = accept none
- Update TODO in CollisionalCrossSectionModel to reference the new
  null/empty convention and note AllowedPrecursorCharges isn't yet migrated
@pcruzparri pcruzparri marked this pull request as draft May 25, 2026 15:28

@trishorts trishorts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated code review - 3 finding(s) at severity >= Major.

pcruzparri and others added 4 commits May 25, 2026 17:51
- Fix MapToInputFullSequence desynchronization bug in FragmentIntensityModel.cs
- Correct Prosit2024IntensityXLNMS2 fragment ion count from 174 to 348
- Fix Prosit2020IntensityTMT TryCleanSequence return value
…nd batch type mismatches

- FragmentIntensityModel: require non-empty constraint sets before rejecting null params
- CrosslinkModelTests: handle null AllowedCollisionEnergies for CMS3
- Ms2PipImmunoHCD: correct MaxPeptideLength from 15 to 30
- CollisionalCrossSectionModel: add non-physical charge guard
- CrosslinkFragmentIntensityModel: fix sequence validation warning propagation
- CollisionEnergy: cast to float across all model impls (int? -> float)
- UniSpec: case-insensitive instrument type matching
- Ms2PipCIDTMT: N-terminal TMT/iTRAQ label validation
- Crosslink benchmark: generate independent beta peptides
- Prosit2024IntensityXLNMS2: correct fragment ion count to 348
@pcruzparri pcruzparri requested a review from trishorts May 26, 2026 13:49
…raints to empty sets

The TestFragmentIntensityModel constructor defaulted unspecified constraints
(AllowedCollisionEnergies, AllowedInstrumentTypes, AllowedFragmentationTypes)
to empty HashSets. Per the validation logic in FragmentIntensityModel.cs:
- null = skip validation (not applicable)
- empty set = parameter IS required but any value accepted

This caused 4 tests to fail because empty sets made untested constraints
required, while the test inputs provided null for those fields.

Fix: default to null instead of empty sets, so only explicitly-passed
constraints are validated.
@codecov

codecov Bot commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.61499% with 205 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.89%. Comparing base (6b685a6) to head (883d8a6).

Files with missing lines Patch % Lines
...AbstractClasses/CrosslinkFragmentIntensityModel.cs 85.55% 24 Missing and 15 partials ⚠️
...ts/Koina/AbstractClasses/FragmentIntensityModel.cs 77.03% 20 Missing and 11 partials ⚠️
...na/AbstractClasses/CollisionalCrossSectionModel.cs 88.27% 13 Missing and 4 partials ⚠️
.../FragmentIntensityModels/Prosit2020IntensityTMT.cs 79.41% 11 Missing and 3 partials ⚠️
...SupportedModels/FragmentIntensityModels/UniSpec.cs 86.79% 10 Missing and 4 partials ⚠️
...rtedModels/FragmentIntensityModels/Ms2PipCIDTMT.cs 84.74% 7 Missing and 2 partials ⚠️
.../FragmentIntensityModels/Prosit2024IntensityCit.cs 85.71% 5 Missing and 1 partial ⚠️
...agmentIntensityModels/Prosit2024IntensityPTMsGl.cs 86.95% 5 Missing and 1 partial ⚠️
...ragmentIntensityModels/Prosit2025Intensity22PTM.cs 85.36% 5 Missing and 1 partial ⚠️
...ragmentIntensityModels/Prosit2025Intensity40PTM.cs 85.36% 5 Missing and 1 partial ⚠️
... and 25 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1073      +/-   ##
==========================================
+ Coverage   86.76%   86.89%   +0.12%     
==========================================
  Files         389      424      +35     
  Lines       50833    52655    +1822     
  Branches     6154     6305     +151     
==========================================
+ Hits        44107    45752    +1645     
- Misses       5533     5669     +136     
- Partials     1193     1234      +41     
Files with missing lines Coverage Δ
...lients/Koina/AbstractClasses/DetectabilityModel.cs 95.79% <100.00%> (+5.28%) ⬆️
...ionClients/Koina/AbstractClasses/KoinaModelBase.cs 95.39% <100.00%> (+0.95%) ⬆️
...lients/Koina/AbstractClasses/RetentionTimeModel.cs 96.11% <100.00%> (+0.05%) ⬆️
...sslinkIntensityModels/Prosit2023IntensityXLCMS2.cs 100.00% <100.00%> (ø)
...sslinkIntensityModels/Prosit2023IntensityXLCMS3.cs 100.00% <100.00%> (ø)
...sslinkIntensityModels/Prosit2024IntensityXLNMS2.cs 100.00% <100.00%> (ø)
...portedModels/FlyabilityModels/PFly2024FineTuned.cs 100.00% <100.00%> (ø)
...pportedModels/RetentionTimeModels/Prosit2019iRT.cs 100.00% <100.00%> (ø)
...rtedModels/RetentionTimeModels/Prosit2020iRTTMT.cs 92.72% <100.00%> (-1.40%) ⬇️
...pportedModels/CCSModels/AlphaPeptDeepCCSGeneric.cs 96.87% <96.87%> (ø)
... and 34 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

pcruzparri and others added 5 commits June 17, 2026 14:28
…ient, add coverage

Audit follow-ups for the Koina prediction client:

- FragmentIntensityModel: route the MapToInputFullSequence branch through the
  overridable ParseFragmentAnnotation and compute neutral-loss-aware m/z, matching
  GenerateLibrarySpectraFromPredictions. Fixes a crash on non-'+' annotations
  (Altimeter '^', UniSpec internal ions) and wrong m/z for neutral-loss ions.
- HTTP: back with a single process-wide HttpClient; per-request CancellationToken
  timeout replaces the per-session client to avoid socket exhaustion under repeated
  Predict() calls. Updated the five AsyncThrottledPredictor call sites.
- CollisionalCrossSectionModel: make AllowedPrecursorCharges nullable to follow the
  null/empty/populated constraint convention; the charge >= 1 physical guard still
  applies. Removes the standing TODO.
- DetectabilityModel: map ValidatedFullSequence from requestInputs rather than the
  full ModelInputs list, so the reported sequence is correct when inputs are filtered.
- Prosit_2025_intensity_lac: send instrument types uppercased and accept them
  case-insensitively. Koina's Prosit_Preprocess_instrument_types matches uppercase
  and silently defaults unknown casing to LUMOS, so lowercase astral/eclipse were
  silently mispredicted.

Tests (no network; run on every PR build):
- New unit fixtures for crosslink, CCS, detectability, fragment-intensity, and HTTP
  covering TryCleanSequence, ValidateModelSpecificInputs, ResponseToPredictions,
  the all-invalid realignment path, GenerateLibrarySpectraFromPredictions, and the
  MapToInputFullSequence/neutral-loss parsing.
- Moved crosslink client-side validation tests out of the [Category("Koina")] fixture
  so they count toward coverage.

PredictionClients line coverage ~49% -> ~66%; HTTP 0 -> 100%,
CrosslinkFragmentIntensityModel ~5% -> ~89%.
@pcruzparri pcruzparri marked this pull request as ready for review June 18, 2026 06:26

@nbollis nbollis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Synthesis

Major (2)

  • mzLib/PredictionClients/Koina/AbstractClasses/CrosslinkFragmentIntensityModel.cs:88 Crosslink mapping mode is configurable but never applied — the new crosslink base class adds a FragmentIonMappingMode contract, but the implementation never branches on it, so callers can request MapToInputFullSequence without getting distinct behavior.
  • Summary-only: mzLib/PredictionClients/Koina/AbstractClasses/CrosslinkFragmentIntensityModel.cs:383 Crosslink response parsing still accepts mismatched output arrays — the new parser checks only annotation divisibility before indexing annotations, mz, and intensities in lockstep; it does not validate equal array lengths or enforce the fixed-size NumberOfPredictedFragmentIons contract.

Minor (2)

  • mzLib/Test/KoinaTests/KoinaRequestBuildingTests.cs:91 Crosslink request-building smoke test bypasses the new model-specific UNIMOD validation — the test seeds Validated*Sequence directly with UNIMOD:1896, so it never exercises XLNMS2's actual {4, 35, 1898} validation path.
  • mzLib/Test/KoinaTests/KoinaRequestBuildingTests.cs:112 Only the negative TMT label path is protected offline — the new offline TMT test covers rejection only, not the successful labeled/request-building path.

Additional summary-only concern

  • mzLib/PredictionClients/Koina/AbstractClasses/FragmentIntensityModel.cs:737 Generated library spectra keep the original sequence after masses were rebuilt from the validated sequence — under MapToValidatedFullSequence, precursor and fragment masses are rebuilt from ValidatedFullSequence, but LibrarySpectrum.Sequence/Name still use FullSequence, which can desynchronize the library identity from the actual spectral content.

Grouping: 5 approved concerns total → 3 inline findings submitted | 2 summary-only findings

Comment thread mzLib/Test/KoinaTests/KoinaRequestBuildingTests.cs
Comment thread mzLib/Test/KoinaTests/KoinaRequestBuildingTests.cs
- Remove unused FragmentIonMappingMode from the crosslink model hierarchy
- Guard equal annotation/mz/intensity output array lengths in ResponseToPredictions
- Label library spectra with the sequence the masses were built from
- Add offline tests for XLNMS2 and TMT validation, TMT fragmentation type, and library labeling
# Conflicts:
#	mzLib/PredictionClients/Koina/AbstractClasses/FragmentIntensityModel.cs
#	mzLib/Test/KoinaTests/FragmentIntensityPrediction/Prosit2020IntensityHCDTests.cs

@trishorts trishorts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated code review - 3 finding(s) at severity >= Major, posted as inline comments.

Comment thread mzLib/PredictionClients/Koina/Client/HTTP.cs Outdated
…name discovery test

- Wrap CCS/Crosslink/RetentionTime/Detectability Predict in lock + Task.Run to avoid the documented single-threaded SynchronizationContext deadlock
- Require a CancellationToken on HTTP requests and add a coarse backstop client timeout
- Add deadlock regression tests and a live ModelName-resolves-to-Koina-endpoint test
@pcruzparri pcruzparri requested a review from trishorts June 21, 2026 05:11
nbollis
nbollis previously approved these changes Jun 24, 2026
nbollis
nbollis previously approved these changes Jun 29, 2026

@trishorts trishorts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated code review - 5 finding(s) at severity >= Major, posted as inline comments.

Comment thread mzLib/PredictionClients/Koina/Client/HTTP.cs Outdated
@trishorts

Copy link
Copy Markdown
Contributor

@pcruzparri Thanks for the work on the 06-30 commits — the neutral-loss base-product fix (11a47cc4), the bounded HTTP timeout, and the deadlock test all look good from my side.

I did want to check on two of the Major threads that are now marked resolved but whose files don't appear in the latest commits (afba1ca1..523e3df1), so I couldn't see what addressed them:

  • Altimeter ParseFragmentAnnotation (thread) — the stripped baseId still looks computed-then-discarded in Altimeter2024Intensities.cs. Does the base-class fix in FragmentIntensityModel now compensate, making the override a no-op, or is there a separate change I'm missing?
  • DeeplcHelaHf.ModelName (thread) — still reads "Deeplc_hela_hf" vs the PR table's DeepLC_Helacell_HF. Was the name confirmed correct against the live Koina registry (i.e. the table is the typo)?

No rush — if these are genuinely no-ops or already-correct, a one-line note on each thread explaining why would let me close them out with confidence. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants