"""
Implementations for the ``Readout`` block in a :py:class:`Model
<mtenn.model.Model>` or :py:class:`GroupedModel <mtenn.model.GroupedModel>`.
This class is intended to contain only simple arithmetic, converting a
model-predicted :math:`\mathrm{\Delta G}` value (in implicit kT units) into some
readout value that can be experimentally measured, eg :math:`\mathrm{pIC_{50}}`.
"""
import abc
import torch
from typing import Optional
[docs]
class Readout(torch.nn.Module, abc.ABC):
"""
Abstract base class for the ``Readout`` block. Any subclass needs to implement
the ``forward`` method in order to be used.
"""
[docs]
@abc.abstractmethod
def forward(self, delta_g):
"""
For any readout class, this function should take the predicted
:math:`\mathrm{\Delta G}` value as input, and return whatever transformed value
the class is implementing.
"""
raise NotImplementedError("Must implement the `forward` method.")
def __str__(self):
return repr(self)
[docs]
class PIC50Readout(Readout):
"""
Readout implementation to convert :math:`\Delta \mathrm{G}` values to
:math:`\mathrm{pIC_{50}}` values. This new implementation assumes implicit energy
units, **WHICH WILL INVALIDATE MODELS TRAINED PRIOR TO v0.3.0**.
Assuming implicit energy units:
.. math::
\Delta \mathrm{G} &= \mathrm{ln}(\mathrm{K_i})
\mathrm{K_i} &= \mathrm{exp}(\Delta \mathrm{G})
Using the Cheng-Prusoff equation:
.. math::
\mathrm{K_i} &= \mathrm{\\frac{IC_{50}}{1 + [S]/K_m}}
\mathrm{exp(\Delta G)} &= \\frac{\mathrm{IC_{50}}}{\mathrm{1 + [S]/K_m}}
\mathrm{IC_{50}} &= \mathrm{exp(\Delta G) (1 + [S]/K_m)}
\mathrm{pIC_{50}} &= \mathrm{-log10(exp(\Delta G) * (1 + [S]/K_m))}
\mathrm{pIC_{50}} &= \mathrm{-log10(exp(\Delta G)) - log10(1 + [S]/K_m)}
\mathrm{pIC_{50}} &= \mathrm{-\\frac{ln(exp(\Delta G))}{ln(10)} -
log10(1 + [S]/K_m)}
\mathrm{pIC_{50}} &= \mathrm{-\\frac{\Delta G}{ln(10)} - log10(1 + [S]/K_m)}
Alternatively, estimating :math:`\mathrm{K_i}` as the :math:`\mathrm{IC_{50}}`
value:
.. math::
\mathrm{K_i} &= \mathrm{IC_{50}}
\mathrm{IC_{50}} &= \mathrm{exp(\Delta G)}
\mathrm{pIC_{50}} &= \mathrm{-log10(exp(\Delta G))}
\mathrm{pIC_{50}} &= \mathrm{-\\frac{ln(exp(\Delta G))}{ln(10)}}
\mathrm{pIC_{50}} &= \mathrm{-\\frac{\Delta G}{ln(10)}}
"""
[docs]
def __init__(self, substrate: Optional[float] = None, Km: Optional[float] = None):
"""
Initialize conversion with specified substrate concentration and
:math:`\mathrm{K_m}`. If either is left blank, the :math:`\mathrm{IC_{50}}`
approximation will be used.
Parameters
----------
substrate : float, optional
Substrate concentration for use in the Cheng-Prusoff equation. Assumed to be
in the same units as :math:`\mathrm{K_m}`
Km : float, optional
:math:`\mathrm{K_m}` value for use in the Cheng-Prusoff equation. Assumed to
be in the same units as substrate
"""
super(PIC50Readout, self).__init__()
self.substrate = substrate
self.Km = Km
if substrate and Km:
self.cp_val = 1 + substrate / Km
else:
self.cp_val = None
def __repr__(self):
return f"PIC50Readout(substrate={self.substrate}, Km={self.Km})"
[docs]
def forward(self, delta_g):
"""
Method to convert a predicted :math:`\Delta \mathrm{G}` value into a
:math:`\mathrm{pIC_{50}}` value.
Parameters
----------
delta_g : torch.Tensor
Input :math:`\Delta \mathrm{G}` value.
Returns
-------
torch.Tensor
Calculated :math:`\mathrm{pIC_{50}}` value.
"""
pic50 = -delta_g / torch.log(torch.tensor(10, dtype=delta_g.dtype))
# Using Cheng-Prusoff
if self.cp_val:
pic50 -= torch.log10(torch.tensor(self.cp_val, dtype=delta_g.dtype))
return pic50
[docs]
class PKiReadout(Readout):
"""
Readout implementation to convert :math:`\Delta \mathrm{G}` values to
:math:`\mathrm{pK_i}` values. This new implementation assumes implicit energy units,
**WHICH WILL INVALIDATE MODELS TRAINED PRIOR TO v0.3.0**.
Assuming implicit energy units:
.. math::
\mathrm{\Delta G} &= \mathrm{ln(K_i)}
\mathrm{K_i} &= \mathrm{exp(\Delta G)}
\mathrm{pK_i} &= \mathrm{-log10(K_i)}
\mathrm{pK_i} &= \mathrm{-log10(exp(\Delta G))}
\mathrm{pK_i} &= \\frac{\mathrm{-ln(exp(\Delta G))}}{\mathrm{ln(10)}}
\mathrm{pK_i} &= \\frac{\mathrm{-\Delta G}}{\mathrm{ln(10)}}
"""
def __repr__(self):
return "PKiReadout()"
[docs]
def forward(self, delta_g):
"""
Method to convert a predicted :math:`\Delta \mathrm{G}` value into a
:math:`\mathrm{pK_i}` value.
Parameters
----------
delta_g : torch.Tensor
Input :math:`\Delta \mathrm{G}` value.
Returns
-------
torch.Tensor
Calculated :math:`\mathrm{pK_i}` value.
"""
pki = -delta_g / torch.log(torch.tensor(10, dtype=delta_g.dtype))
return pki