Assessing a modelled complex#
Important
Final models use fixed empirical contact potentials with explicit provenance. Gaussian/GLM, fitted Potts and transition models, and reference-estimated Q/T/S are historical readouts, excluded from the final specification. Nothing fitted at scoring time does not mean fit-free. See Final fit-free calculations.
A co-folding model builds a confident TCR:pMHC complex for any receptor–peptide pair, binding or
not. tcren.score reads the coordinates it produced and answers four separate questions about
them, and every answer is defined for a single structure: the transform, the class means and
the covariance are all frozen on a hold-out that ships with the package, so nothing is estimated
from the rows you are scoring and a score does not change depending on what was scored beside it.
Two commands#
$ tcren features -s models/ --all -o feats.tsv
$ tcren assess --features feats.tsv -o scores.tsv
The first is the expensive pass and runs once. The second is arithmetic over that table.
Pass --peptide when the peptide is what varies across the structures being compared, as in a
combinatorial library or a mutational scan. Otherwise the five descriptors computed without the
receptor are marginalized out, because they are constant across every structure of one epitope on
one allele and a model reading them reaches the cohort’s name without reading an interface.
What each column is for#
column |
tier |
what it answers |
|---|---|---|
|
1 |
Is this the kind of interface real complexes make? A one-class distance to the manifold hold-out binders occupy. Binder moments use labelled positives, and preprocessing uses both classes. Tier 1 names the one-class readout, not negative-independent training. |
|
2 |
Log-odds that the complex is a genuine recognition interface. |
|
2 |
The same log-odds, marginalized to one descriptor family, so a number can be attributed. |
|
0 |
The poly-alanine-referenced recognition energy. Nothing is fitted in it. This ranks peptides against a fixed receptor, and it reads below chance on a receptor benchmark — a property of the reference frame, not a defect. |
|
1 |
The reported logit(ipTM) minus its binder-conditional mean on that axis. A large positive residual is a model the generator is more certain of than its own geometry warrants. |
|
2 |
|
Higher is better throughout.
The five channels#
A marginal of a Gaussian is a sub-block of its covariance — exact, closed form, no re-fit — so reading one family of descriptors costs an index and nothing else:
placementwhere the receptor sits in the groove frame.
interfacehow much interface it makes, and of what chemistry.
shapethe footprint’s shape, free of its size.
energeticsthe contact chemistry on its statistical potential scale. The conventional kT label denotes a reduced scale; it does not establish a calibrated binding free energy.
mechanicsthe interface read as a network of breakable springs.
They do not sum to binder_score and should not: the whole model also reads the correlations
between channels, which a per-channel view cannot show. What they give you is attribution — and,
sometimes, a better instrument. A channel beats the whole model where the whole model dilutes it:
on template-free cohorts channel_shape reads 0.637 against the full posterior’s 0.615, and on a
combinatorial peptide library channel_energetics reads 0.700 against 0.542.
Which residue carries it#
The five channels say which part of the structure a score comes from. residue_deltas says
which residue:
tcren explain -s model.pdb --score binder -o deltas.tsv
Each row is one interface residue, and delta is
the score of the complex minus the score of the same complex with that residue’s atoms removed.
Positive means the residue carries the score. It is defined for every read-out and every channel,
so one table colours a figure by any of them — pass it straight to
tcren.viz.pymol.importance_scene().
peptide_score is the exception and does not need it: that score is a sum over contacts, so
tcren.energetics.scoring.position_profile() already returns an exact per-position
decomposition that sums back to the score. Use the exact one where it exists. Leave-one-out is for
the four read-outs that have no exact split, because each is a function of ~149 whole-structure
scalars through a full covariance and no residue owns a share of one.
Cost is one descriptor pass per interface residue — a few minutes for a complex. Chain typing and the MHC call are mmseqs searches and run once for the whole structure, never per mask. A masked row whose descriptors go undefined is reported as a null delta rather than imputed.
chain_types restricts reported residues without changing the score. The Python API accepts
receptor=False to explain the full peptide-presentation score; the default and CLI explain
the receptor marginal. Noncontact residues are omitted, without claiming their contributions
to whole-chain geometry descriptors vanish.
Reproducing the frozen model#
The coefficients are frozen, and the inputs they were frozen against are named:
$ tcren fetch-data # the structure sets the manifest names
$ tcren features -s <those structures> -o hold.tsv
$ tcren fit-holdout --features hold.tsv -o refit.npz
The frozen numerical parameters retain their original training catalogue. The 2026-10-14
metadata migration records corrected definitions and changed descriptor semantics without
refitting; it does not establish a bit-for-bit refit under corrected feature extraction.
A fresh refit writes a separate model and requires a matched comparison before promotion. holdout_manifest() returns the 8,292
structures with their dataset, epitope, label and ipTM. Refit on your own hold-out by passing
--manifest, then read it back with tcren assess --model.
Confidence probability scale#
Let z = logit(ipTM) and let m(x) be its Gaussian conditional mean given transformed
structural coordinates x. The library residual is z - m(x). Applying sigmoid to
m(x) gives a plug-in original-scale prediction, not E[ipTM | x]: nonlinear expectation
and back-transformation generally do not commute. The benchmark owns its fitted GLM and
operating-point correction producers; those are separate from the library conditional residual.
Future confidence fitting validates that the joint covariance is positive semidefinite via
the Schur complement, without modifying the shipped arrays.
The predecessor tier#
tcren.cohort.q_score(), tcren.reliability.t_score() and
tcren.reliability.s_score() are the reference-estimated directional scores this layer generalises, and
they are still supported. S leads the functionally validated receptor screen on its own, and it
composes with binder_score rather than being replaced by it.
API#
- tcren.score.pose_score(table, *, model=None)[source]#
-
- Parameters:
model (ScoreModel | None)
- Return type:
ndarray
- tcren.score.binder_score(table, *, receptor=True, model=None)[source]#
See
ScoreModel.binder_score().- Parameters:
receptor (bool)
model (ScoreModel | None)
- Return type:
ndarray
- tcren.score.channel_scores(table, *, receptor=True, model=None)[source]#
See
ScoreModel.channel_scores().- Parameters:
receptor (bool)
model (ScoreModel | None)
- Return type:
dict[str, ndarray]
- tcren.score.confidence_residual(table, iptm, *, model=None)[source]#
See
ScoreModel.confidence_residual().- Parameters:
model (ScoreModel | None)
- Return type:
ndarray
- tcren.score.peptide_score(table)[source]#
Tier 0, and nothing is fitted anywhere in it.
The poly-alanine-referenced recognition energy over the two interfaces the peptide is part of, sign-flipped so higher is better. This is the instrument for peptide ranking against a fixed receptor, and it is not the instrument for receptor ranking: on a receptor benchmark it reads below chance, which is a property of the reference frame rather than a defect.
- Return type:
ndarray
- tcren.score.residue_deltas(source, *, score='binder', model=None, organism='human', receptor=True, chain_types=None, interfaces=('tcr_peptide', 'tcr_mhc', 'peptide_mhc'), radii=(7.0, 8.0))[source]#
Leave-one-residue-out contribution to a whole-structure score.
- Parameters:
source – a path to a structure, or an already-parsed
Structure.score (str) –
"binder","pose","peptide", or a channel name ("placement","interface","shape","energetics","mechanics").model – overrides
tcren.score.holdout_model().organism (str) – passed to chain typing when the structure is not already annotated.
receptor (bool) – scoring mode for binder/channel readouts, as in
binder_score; independent of which residues are selected. False includes peptide-presentation coordinates.chain_types (tuple[str, ...] | None) – restrict to residues of these chain types, e.g.
("PEPTIDE",)to colour a peptide only.Nonetakes every residue on either side ofinterfaces.interfaces (tuple[str, ...]) – which interfaces define the residue set.
radii (tuple[float, ...]) – footprint radii, as in
tcren.descriptors.table.recognition_table().
- Returns:
chain.id,residue.index(seq index),residue.pdb,residue.aa,score.full,score.withoutanddelta= full − without.deltais null where masking the residue left a descriptor undefined.- Return type:
One row per masked residue
- tcren.score.score_table(table, *, receptor=True, iptm=None, model=None)[source]#
Every read-out for a
tcren featurestable, as one polars frame.Each numeric readout has a corresponding
<name>_availableflag.assessment_availablealiasesbinder_score_available.missing_descriptorsis a JSON list of nonfinite model descriptors present in each row. Entirely absent columns are marginalized out; individual readouts can remain available when the missing values occur only outside their coordinate subset.Adds
binder_iptmwheniptmis supplied: a coefficient-free heuristic sum of binder log-odds and logit confidence, without a calibrated likelihood-ratio or independence claim. It stays defined for a single structure. On the functionally validated receptor screen it reads 0.771 against the posterior’s 0.768 and ipTM’s 0.795, and it is the recommended read when a confidence is available.- Parameters:
receptor (bool)
model (ScoreModel | None)
- tcren.score.holdout_model(path=None)[source]#
The frozen hold-out fit that ships with the package.
Raises a message naming
tcren fit-holdoutrather than a bareFileNotFoundError, because a source checkout that has never run the fitter is the common way to arrive here.- Parameters:
path (str | None)
- Return type:
- tcren.score.holdout_manifest()[source]#
The structures the shipped model was fitted on: id, dataset, epitope and binder label.
This is what makes the frozen coefficients reproducible rather than merely stated. The descriptors themselves are not shipped – 8,292 rows by 147 columns is 19 MB and a wheel is not the place for it – but they are recomputable:
tcren fetch-databrings down the structure sets these ids name,tcren featureswrites the table, andtcren fit-holdoutfits a separate output from the two. Corrected descriptor semantics require a matched comparison before replacing the historical frozen model.
- class tcren.score.ScoreModel(transform, joint, receptor_names, conf_mu, conf_cov, conf_var, catalogue_digest, n_pos, n_neg, n_epitopes, tcren_version, confidence_available=True)[source]#
The frozen hold-out fit. Load it with
holdout_model(), never construct it by hand.- Parameters:
transform (Transformer)
joint (Joint)
receptor_names (tuple[str, ...])
conf_mu (float)
conf_cov (ndarray)
conf_var (float)
catalogue_digest (str)
n_pos (int)
n_neg (int)
n_epitopes (int)
tcren_version (str)
confidence_available (bool)
- receptor_names: tuple[str, ...]#
the five descriptors computed without the receptor are constant across every structure of one epitope on one allele, so a model reading them reaches the cohort label without reading an interface.
- Type:
the coordinates a receptor-ranking read-out may use
- conf_mu: float#
E[logit ipTM | x] comes from this row of the binder covariance.
- Type:
binder-class augmentation
- MIN_COORDINATES = 20#
below this many usable coordinates a covariance read-out is not worth reporting.
- coordinates(table, *, coordinate_names=None)[source]#
(complete-case mask, coordinates, the coordinate names they carry).Two kinds of absence, handled differently and both deliberately.
A column the table does not have is marginalized out. That is exact: a marginal of a Gaussian is a sub-block of its covariance, so a table without the
pottsfamily is scored by the model restricted to what it does have, not by a model with a hole in it. This is what letstcren features -i placement,interface,topologystill produce a binder score.A row missing one of the columns that are present is dropped rather than imputed: the whole construction is a covariance, and a filled-in value is a fabricated correlation. With
coordinate_names, only the requested marginal’s descriptors determine missingness.- Return type:
tuple[ndarray, ndarray, list[str]]
- available(table)[source]#
The model coordinates this table can supply, in model order.
- Return type:
list[str]
- pose_score(table)[source]#
Tier 1. How far this interface sits from the manifold real binders occupy.
A partial Mahalanobis to the binder Gaussian. Binder moments use label-selected positives; the frozen preprocessing was fitted on both classes. Tier 1 describes the readout, not negative-independent or label-free training. Higher is more plausible: the sign is flipped from the raw distance so that every score in this module reads “higher is better”.
- Return type:
ndarray
- binder_score(table, *, receptor=True)[source]#
Tier 2. Log-odds that this complex is a genuine recognition interface.
receptor=True(the default) marginalizes out the five descriptors computed without the receptor. Set it False only when the peptide is what varies across the structures being compared, as in a combinatorial peptide library, where the presentation energy is signal rather than the cohort’s name.- Parameters:
receptor (bool)
- Return type:
ndarray
- channel_scores(table, *, receptor=True)[source]#
Tier 2, one log-odds per channel: which part of the structure says so.
Each is an exact marginal of the same covariance, so the five are on one scale and can be read against each other. They do not sum to
binder_score(), and should not: the whole model also reads the correlations between channels, which is the part a per-channel view cannot show.- Parameters:
receptor (bool)
- Return type:
dict[str, ndarray]
- confidence_residual(table, iptm)[source]#
Tier 1. Reported confidence minus what the coordinates say it should have been.
E[logit ipTM | x]under the binder Gaussian, subtracted from the reported value. A large positive residual is a model the generator is more certain of than its own geometry and chemistry warrant. Training uses label-selected binders and shared preprocessing fitted on both classes. Returns NaN when the model was fitted without enough valid confidence observations.- Return type:
ndarray
- tcren.reliability.artefact_directions(binder_coords, crystal_coords, *, tolerance=0.05)[source]#
Which directions of a binder manifold are the generator’s regularity, not the interface’s.
No binding label enters this. The argument is that a direction real complexes are free to vary along, while modelled binders are pinned to it, is a property of the model that produced them. A physical constraint holds on a crystal at least as tightly as on a prediction of one; a generator’s habit does not.
- Parameters:
binder_coords –
(n, p)transformed coordinates for modelled binders – the population whose covariance defines the directions.crystal_coords –
(m, p)the same coordinates for experimentally solved complexes.tolerance (float) – the residual s.d. below which a direction counts as tight.
- Returns:
{"index", "sd_binder", "sd_crystal", "ratio", "is_artefact"}, one entry per direction, ordered stiffest first.is_artefactis a tight direction that crystals break harder than binders do.- Return type:
dict
Reading it: a large
ratioon a tight direction means the constraint is the generator’s. Drop those coordinates, or use a covariance estimator that suppresses them – shrinkage does it for free, which is why the shipped model does not need this call to be safe.