Skip to content

fix: let koina.constraints.unsupported_residues reach a loaded calibrator - #253

Open
BioGeek wants to merge 1 commit into
mainfrom
fix/koina-unsupported-residues-override
Open

BioGeek wants to merge 1 commit into
mainfrom
fix/koina-unsupported-residues-override

Conversation

@BioGeek

@BioGeek BioGeek commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #252.

koina.constraints.unsupported_residues is documented and interpolated into both Koina-backed features by calibrator.yaml, but ProbabilityCalibrator.load() rebuilds its features from the saved config.json rather than from Hydra. On the pretrained path — the default, and the one the docs describe — the key is parsed and then discarded, and the shipped three-entry default applies no matter what you pass.

That matters because a residue Prosit has no embedding for fails the entire Koina request server-side, not just that peptide. Scoring predictions from a checkpoint whose vocabulary is wider than the calibrator's is therefore impossible to configure around.

Approach

Apply the override after load, alongside the collision-energy and fragmentation-type overrides that already work exactly this way via apply_koina_model_input_overrides.

It applies only when explicitly given, detected with the existing hydra_override_keys helper, the same way user_specified_column distinguishes a deliberate setting from a shipped default. A saved list is the one that calibrator was fitted with; replacing it unconditionally with the shipped default would silently narrow the exclusions for anyone who trained against a wider residue set. So there is no behaviour change unless you ask for one.

Changes

  • ProbabilityCalibrator.apply_unsupported_residues_override() — sets the list on every feature that filters by one, returns their names. None is a no-op; an empty list is honoured as a deliberate "exclude nothing".
  • apply_koina_intensity_config() — takes hydra_overrides, parses koina.constraints.unsupported_residues, applies it when overridden, and logs what changed.
  • main.py — passes the overrides it already has through to the apply step.

Verification

Against the real published InstaDeepAI/winnow-general-model:

before Fragment Match Features: ['[UNIMOD:5]', '[UNIMOD:385]', '(+25.98)']
before iRT Feature:             ['[UNIMOD:5]', '[UNIMOD:385]', '(+25.98)']

-- no override flag passed --
after  Fragment Match Features: ['[UNIMOD:5]', '[UNIMOD:385]', '(+25.98)']   (unchanged)
after  iRT Feature:             ['[UNIMOD:5]', '[UNIMOD:385]', '(+25.98)']   (unchanged)

-- override flag passed --
after  Fragment Match Features: ['P[UNIMOD:425]', 'K[UNIMOD:259]', '[UNIMOD:1009]']
after  iRT Feature:             ['P[UNIMOD:425]', 'K[UNIMOD:259]', '[UNIMOD:1009]']

End to end on real data — a 133-residue InstaNovo checkpoint's predictions over 8,853 bovine whey spectra, with a 41-residue exclusion list — the Koina stage now reports the list it was given and completes:

Constraints applied:
  - unsupported_residues: ['C[UNIMOD:368]', 'C[UNIMOD:382]', 'C[UNIMOD:55]']...
Filtered 2274 spectra that do not satisfy the validity constraints

Prosit_2025_intensity_22PTM:: 100%|##########| 7/7
Prosit_2025_irt_22PTM::       100%|##########| 1/1

The same inputs on main fail with InferenceServerException: At least one request failed, preceded by KeyError for residues that were in the list passed.

8 new tests (4 on the calibrator method, 4 on the config layer); 684 pass, no regressions. All pre-commit hooks pass.

Not covered here

With the Koina stage passing, that run then fails further downstream in Mass Error (Da) — mass_error.py:44 does a bare residue_masses[residue] lookup against a 68-entry table and raises KeyError: 'W[UNIMOD:35]'. Separate issue, same family as #251; happy to file it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C8sMwNQJTyWwJB5Qn7vjuW

…ator

Which residues a Koina model can represent depends on the model and on the
vocabulary the predictions use, so it is a runtime setting, not a learned
parameter. `calibrator.yaml` interpolates the config key into each
Koina-backed feature, but `ProbabilityCalibrator.load` rebuilds its features
from the saved `config.json`, so on the pretrained path -- the default, and
the one the docs describe -- the key has no effect at all. Predictions over a
residue set wider than the one the calibrator was fitted on are then sent to
Koina regardless, and the server fails the whole request on the first residue
it has no embedding for.

Apply the override after load, alongside the collision-energy and
fragmentation-type overrides that already work this way, and only when it was
given explicitly: a saved list is the one that calibrator was fitted with, and
replacing it with the shipped default would silently narrow the exclusions for
anyone who trained against a wider residue set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C8sMwNQJTyWwJB5Qn7vjuW
@github-actions

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py00100% 
data_types.py40100% 
calibration
   __init__.py00100% 
   calibration_features.py90100% 
   calibrator.py5063293%61, 64, 79, 84–85, 203, 302–303, 305, 308, 353–354, 495–496, 552, 646–647, 651, 656, 722–725, 1099–1100, 1251, 1255, 1281, 1285, 1294, 1311, 1316
   diagnostics.py1384468%71, 156–171, 214–215, 219, 260, 262–277, 288–294
calibration/features
   __init__.py100100% 
   base.py26196%84
   beam.py900100% 
   chimeric.py82198%213
   constants.py90100% 
   fragment_match.py78198%203
   mass_error.py68494%17, 21, 37–38
   retention_time.py206896%111, 114–115, 226, 237, 351, 354, 435
   sequence.py190100% 
   token_score.py37197%82
   utils.py261398%96, 368, 594
compat
   __init__.py00100% 
   instanovo.py10640%12, 14–15, 17, 24–25
datasets
   __init__.py00100% 
   calibration_dataset.py1441589%228, 238, 247, 260, 340, 342–343, 349–352, 354–357
   feature_dataset.py84495%21, 88, 183–184
   interfaces.py30100% 
   psm_dataset.py250100% 
datasets/data_loaders
   __init__.py60100% 
   instanovo.py1051684%90, 93, 119, 142, 168–169, 172–174, 176–177, 179, 182–184, 191
   mztab.py1793083%103, 106, 157, 161, 210–211, 223, 268, 271, 283–284, 317–318, 320–321, 323, 325, 331, 334–335, 342–344, 351, 353–356, 360, 501
   pointnovo.py90100% 
   primenovo.py1211091%121, 124, 211, 242, 254, 261–262, 373, 384, 389
   utils.py186398%25, 170, 441
   winnow.py41587%55–56, 86, 100–101
fdr
   __init__.py00100% 
   base.py581574%81, 85–86, 91, 98–99, 105, 126, 129–130, 135, 137–138, 144, 186
   database_grounded.py37197%100
   nonparametric.py26484%64, 70–71, 74
scripts
   __init__.py00100% 
   main.py47035424%23–26, 59, 61–62, 67–68, 78, 90, 95–97, 99–100, 102, 116, 121–122, 124–125, 127–128, 150, 154, 168–169, 184–185, 188, 197–198, 200, 217–218, 235, 254–255, 257–259, 261–262, 264–266, 268, 273, 298, 300–304, 325–326, 328–329, 332–333, 338, 343–344, 378–380, 382, 384, 389, 391, 395–397, 399, 401–403, 405, 407, 409–410, 413, 418, 420, 427–429, 434–437, 439–440, 442, 444, 459–460, 462–464, 466–471, 473, 487–488, 490–492, 494–495, 497–498, 500, 502–503, 510–513, 527, 529, 531–532, 534, 542–544, 546, 551, 557, 571, 573, 575–576, 578, 586–588, 590–591, 593–597, 599, 635–637, 639–642, 644, 648–651, 729, 750, 764, 782–786, 788–791, 817–819, 821, 823, 828, 830, 834–836, 838, 841, 843–846, 848, 856–858, 860, 862, 864, 867–869, 871–873, 880, 897–899, 902, 905, 910, 912, 915, 919–921, 923, 927–929, 931, 933–934, 939, 946, 954, 956, 958–959, 961, 963, 971–973, 975, 977–978, 980–981, 983–984, 986–987, 994–996, 1002, 1011–1012, 1014–1015, 1022–1024, 1026, 1028, 1033, 1035, 1038, 1042–1044, 1046–1047, 1050, 1053, 1055, 1061–1062, 1064–1065, 1069, 1076, 1084, 1086, 1088–1090, 1097–1099, 1101–1103, 1106, 1111, 1116, 1118–1119, 1121–1123, 1126, 1131, 1141, 1148, 1150, 1154, 1156–1157, 1161–1162, 1164, 1173–1176, 1178–1179, 1181, 1183, 1188, 1190–1192, 1194, 1197, 1199, 1205–1208, 1210–1211, 1213–1214, 1216–1217, 1219, 1225, 1232–1234, 1236, 1275–1276, 1317–1318, 1359–1360, 1390–1391, 1419–1420, 1450–1451, 1478–1479, 1509–1510, 1534–1535, 1559–1560
utils
   __init__.py40100% 
   config_formatter.py534024%29, 37–38, 40–42, 44, 55, 58–60, 62–63, 66–69, 72–74, 77–78, 80, 91, 102, 113, 127–128, 130–132, 145–147, 150, 153–154, 157–158, 160
   config_path.py76593%24–26, 117–118
   hydra_overrides.py12283%12, 15
   irt_calibration_config.py895735%13–14, 21–24, 32–39, 43–44, 52–55, 57–60, 62, 64–65, 77–78, 96–103, 114–117, 124–125, 140–143, 146, 148–149, 153, 155–156, 163, 165–166, 197
   koina_intensity_config.py1101784%44, 70, 80, 97, 101, 127, 130, 150, 182–185, 187, 191, 201, 209, 222
   paths.py210100% 
   peptide.py69395%37–38, 94
   proteome.py1151289%41, 48, 68, 79, 84, 88, 119, 144–146, 174, 180
   rich_console.py70100% 
TOTAL360369480% 

Tests Skipped Failures Errors Time
680 0 💤 0 ❌ 0 🔥 35.576s ⏱️

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

koina.constraints.unsupported_residues has no effect when a pretrained calibrator is loaded

1 participant