Assessing a modelled complex#
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/ -i placement,interface,topology,energetics -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. No negative and no binder label enter it — the same standing as
|
|
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 ipTM minus what the coordinates say it should have been. 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, in kT.
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.
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
refit.npz matches the shipped model bit for bit. 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.
The predecessor tier#
tcren.cohort.q_score(), tcren.reliability.t_score() and
tcren.reliability.s_score() are the fit-free 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.score_table(table, *, receptor=True, iptm=None, model=None)[source]#
Every read-out for a
tcren featurestable, as one polars frame.Adds
binder_iptmwheniptmis supplied: the naive-Bayes sum of two log-odds, which needs no coefficient because both terms are already on that scale and 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-holdoutreturns the shipped model from the two.
- class tcren.score.ScoreModel(transform, joint, receptor_names, conf_mu, conf_cov, conf_var, catalogue_digest, n_pos, n_neg, n_epitopes, tcren_version)[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)
- 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)[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.
- 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. No negative and no binder label enter it, which is the same standing as the shipped
q_scoreandt_score. 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, which is the failure mode a confident non-binder presents as. No binder label enters it.- 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.