Ligand binding#
Usually one or more of the transitions in a receptor model involve ligand binding. For example, consider a receptor model with two sequential transitions that involve ligand binding.
var('R RL RLL kap kam kbp kbm L')
G = DiGraph({R: {RL:kap*L}, RL: {R:kam, RLL:kbp*L}, RLL: {RL:kbm}})
pos = {R: (0, 0), RL: (2, 0), RLL: (4, 0)}
G.plot(figsize=8,edge_labels=True,pos=pos,graph_border=True,vertex_size=1000)
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.2.6 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.
If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
Traceback (most recent call last): File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/usr/lib/python3/dist-packages/sage/repl/ipython_kernel/__main__.py", line 3, in <module>
IPKernelApp.launch_instance(kernel_class=SageKernel)
File "/usr/lib/python3/dist-packages/traitlets/config/application.py", line 846, in launch_instance
app.start()
File "/usr/lib/python3/dist-packages/ipykernel/kernelapp.py", line 677, in start
self.io_loop.start()
File "/usr/lib/python3/dist-packages/tornado/platform/asyncio.py", line 199, in start
self.asyncio_loop.run_forever()
File "/usr/lib/python3.10/asyncio/base_events.py", line 603, in run_forever
self._run_once()
File "/usr/lib/python3.10/asyncio/base_events.py", line 1909, in _run_once
handle._run()
File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 461, in dispatch_queue
await self.process_one()
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 450, in process_one
await dispatch(*args)
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 357, in dispatch_shell
await result
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 652, in execute_request
reply_content = await reply_content
File "/usr/lib/python3/dist-packages/ipykernel/ipkernel.py", line 353, in do_execute
res = shell.run_cell(code, store_history=store_history, silent=silent)
File "/usr/lib/python3/dist-packages/ipykernel/zmqshell.py", line 532, in run_cell
return super().run_cell(*args, **kwargs)
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 2914, in run_cell
result = self._run_cell(
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 2960, in _run_cell
return runner(coro)
File "/usr/lib/python3/dist-packages/IPython/core/async_helpers.py", line 78, in _pseudo_sync_runner
coro.send(None)
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3185, in run_cell_async
has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3377, in run_ast_nodes
if (await self.run_code(code, result, async_=asy)):
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3457, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "/tmp/ipykernel_11773/3628585111.py", line 4, in <module>
G.plot(figsize=Integer(8),edge_labels=True,pos=pos,graph_border=True,vertex_size=Integer(1000))
File "/usr/lib/python3/dist-packages/IPython/core/displayhook.py", line 262, in __call__
format_dict, md_dict = self.compute_format_data(result)
File "/usr/lib/python3/dist-packages/IPython/core/displayhook.py", line 151, in compute_format_data
return self.shell.display_formatter.format(result)
File "/usr/lib/python3/dist-packages/sage/repl/display/formatter.py", line 181, in format
sage_format, sage_metadata = self.dm.displayhook(obj)
File "/usr/lib/python3/dist-packages/sage/repl/rich_output/display_manager.py", line 825, in displayhook
plain_text, rich_output = self._rich_output_formatter(obj, dict())
File "/usr/lib/python3/dist-packages/sage/repl/rich_output/display_manager.py", line 643, in _rich_output_formatter
rich_output = self._call_rich_repr(obj, rich_repr_kwds)
File "/usr/lib/python3/dist-packages/sage/repl/rich_output/display_manager.py", line 603, in _call_rich_repr
return obj._rich_repr_(self)
File "/usr/lib/python3/dist-packages/sage/plot/graphics.py", line 1000, in _rich_repr_
return display_manager.graphics_from_save(
File "/usr/lib/python3/dist-packages/sage/repl/rich_output/display_manager.py", line 731, in graphics_from_save
save_function(filename, **kwds)
File "/usr/lib/python3/dist-packages/sage/misc/decorators.py", line 410, in wrapper
return func(*args, **kwds)
File "/usr/lib/python3/dist-packages/sage/plot/graphics.py", line 3296, in save
from matplotlib import rcParams
File "/usr/lib/python3/dist-packages/matplotlib/__init__.py", line 109, in <module>
from . import _api, _version, cbook, docstring, rcsetup
File "/usr/lib/python3/dist-packages/matplotlib/rcsetup.py", line 27, in <module>
from matplotlib.colors import Colormap, is_color_like
File "/usr/lib/python3/dist-packages/matplotlib/colors.py", line 56, in <module>
from matplotlib import _api, cbook, scale
File "/usr/lib/python3/dist-packages/matplotlib/scale.py", line 23, in <module>
from matplotlib.ticker import (
File "/usr/lib/python3/dist-packages/matplotlib/ticker.py", line 136, in <module>
from matplotlib import transforms as mtransforms
File "/usr/lib/python3/dist-packages/matplotlib/transforms.py", line 46, in <module>
from matplotlib._path import (
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: _ARRAY_API not found
/usr/lib/python3/dist-packages/sage/repl/rich_output/display_manager.py:608: RichReprWarning: Exception in _rich_repr_ while displaying object: numpy.core.multiarray failed to import
warnings.warn(
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.2.6 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.
If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
Traceback (most recent call last): File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/usr/lib/python3/dist-packages/sage/repl/ipython_kernel/__main__.py", line 3, in <module>
IPKernelApp.launch_instance(kernel_class=SageKernel)
File "/usr/lib/python3/dist-packages/traitlets/config/application.py", line 846, in launch_instance
app.start()
File "/usr/lib/python3/dist-packages/ipykernel/kernelapp.py", line 677, in start
self.io_loop.start()
File "/usr/lib/python3/dist-packages/tornado/platform/asyncio.py", line 199, in start
self.asyncio_loop.run_forever()
File "/usr/lib/python3.10/asyncio/base_events.py", line 603, in run_forever
self._run_once()
File "/usr/lib/python3.10/asyncio/base_events.py", line 1909, in _run_once
handle._run()
File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 461, in dispatch_queue
await self.process_one()
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 450, in process_one
await dispatch(*args)
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 357, in dispatch_shell
await result
File "/usr/lib/python3/dist-packages/ipykernel/kernelbase.py", line 652, in execute_request
reply_content = await reply_content
File "/usr/lib/python3/dist-packages/ipykernel/ipkernel.py", line 353, in do_execute
res = shell.run_cell(code, store_history=store_history, silent=silent)
File "/usr/lib/python3/dist-packages/ipykernel/zmqshell.py", line 532, in run_cell
return super().run_cell(*args, **kwargs)
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 2914, in run_cell
result = self._run_cell(
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 2960, in _run_cell
return runner(coro)
File "/usr/lib/python3/dist-packages/IPython/core/async_helpers.py", line 78, in _pseudo_sync_runner
coro.send(None)
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3185, in run_cell_async
has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3377, in run_ast_nodes
if (await self.run_code(code, result, async_=asy)):
File "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py", line 3457, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "/tmp/ipykernel_11773/3628585111.py", line 4, in <module>
G.plot(figsize=Integer(8),edge_labels=True,pos=pos,graph_border=True,vertex_size=Integer(1000))
File "/usr/lib/python3/dist-packages/IPython/core/displayhook.py", line 262, in __call__
format_dict, md_dict = self.compute_format_data(result)
File "/usr/lib/python3/dist-packages/IPython/core/displayhook.py", line 151, in compute_format_data
return self.shell.display_formatter.format(result)
File "/usr/lib/python3/dist-packages/sage/repl/display/formatter.py", line 186, in format
if (not isinstance(obj, (IPYTHON_NATIVE_TYPES, Figure)) and
File "/usr/lib/python3/dist-packages/matplotlib/__init__.py", line 109, in <module>
from . import _api, _version, cbook, docstring, rcsetup
File "/usr/lib/python3/dist-packages/matplotlib/rcsetup.py", line 27, in <module>
from matplotlib.colors import Colormap, is_color_like
File "/usr/lib/python3/dist-packages/matplotlib/colors.py", line 56, in <module>
from matplotlib import _api, cbook, scale
File "/usr/lib/python3/dist-packages/matplotlib/scale.py", line 23, in <module>
from matplotlib.ticker import (
File "/usr/lib/python3/dist-packages/matplotlib/ticker.py", line 136, in <module>
from matplotlib import transforms as mtransforms
File "/usr/lib/python3/dist-packages/matplotlib/transforms.py", line 46, in <module>
from matplotlib._path import (
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: _ARRAY_API not found
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
/tmp/ipykernel_11773/3628585111.py in <module>
2 G = DiGraph({R: {RL:kap*L}, RL: {R:kam, RLL:kbp*L}, RLL: {RL:kbm}})
3 pos = {R: (Integer(0), Integer(0)), RL: (Integer(2), Integer(0)), RLL: (Integer(4), Integer(0))}
----> 4 G.plot(figsize=Integer(8),edge_labels=True,pos=pos,graph_border=True,vertex_size=Integer(1000))
/usr/lib/python3/dist-packages/IPython/core/displayhook.py in __call__(self, result)
260 self.start_displayhook()
261 self.write_output_prompt()
--> 262 format_dict, md_dict = self.compute_format_data(result)
263 self.update_user_ns(result)
264 self.fill_exec_result(result)
/usr/lib/python3/dist-packages/IPython/core/displayhook.py in compute_format_data(self, result)
149
150 """
--> 151 return self.shell.display_formatter.format(result)
152
153 # This can be set to True by the write_output_prompt method in a subclass
/usr/lib/python3/dist-packages/sage/repl/display/formatter.py in format(self, obj, include, exclude)
184 # use Sage rich output for any except those native to IPython, but only
185 # if it is not plain and dull
--> 186 if (not isinstance(obj, (IPYTHON_NATIVE_TYPES, Figure)) and
187 not set(sage_format.keys()).issubset([PLAIN_TEXT])):
188 return sage_format, sage_metadata
/usr/lib/python3/dist-packages/sage/misc/lazy_import.pyx in sage.misc.lazy_import.LazyImport.__instancecheck__ (build/cythonized/sage/misc/lazy_import.c:7695)()
914 True
915 """
--> 916 return isinstance(x, self.get_object())
917
918 def __subclasscheck__(self, x):
/usr/lib/python3/dist-packages/sage/misc/lazy_import.pyx in sage.misc.lazy_import.LazyImport.get_object (build/cythonized/sage/misc/lazy_import.c:2612)()
215 if likely(self._object is not None):
216 return self._object
--> 217 return self._get_object()
218
219 cpdef _get_object(self):
/usr/lib/python3/dist-packages/sage/misc/lazy_import.pyx in sage.misc.lazy_import.LazyImport._get_object (build/cythonized/sage/misc/lazy_import.c:3073)()
255 if self._feature:
256 raise FeatureNotPresentError(self._feature, reason=f'Importing {self._name} failed: {e}')
--> 257 raise
258
259 name = self._as_name
/usr/lib/python3/dist-packages/sage/misc/lazy_import.pyx in sage.misc.lazy_import.LazyImport._get_object (build/cythonized/sage/misc/lazy_import.c:2935)()
251
252 try:
--> 253 self._object = getattr(__import__(self._module, {}, {}, [self._name]), self._name)
254 except ImportError as e:
255 if self._feature:
/usr/lib/python3/dist-packages/matplotlib/__init__.py in <module>
107 # cbook must import matplotlib only within function
108 # definitions, so it is safe to import from it here.
--> 109 from . import _api, _version, cbook, docstring, rcsetup
110 from matplotlib.cbook import MatplotlibDeprecationWarning, sanitize_sequence
111 from matplotlib.cbook import mplDeprecation # deprecated
/usr/lib/python3/dist-packages/matplotlib/rcsetup.py in <module>
25 from matplotlib import _api, cbook
26 from matplotlib.cbook import ls_mapper
---> 27 from matplotlib.colors import Colormap, is_color_like
28 from matplotlib.fontconfig_pattern import parse_fontconfig_pattern
29 from matplotlib._enums import JoinStyle, CapStyle
/usr/lib/python3/dist-packages/matplotlib/colors.py in <module>
54 import matplotlib as mpl
55 import numpy as np
---> 56 from matplotlib import _api, cbook, scale
57 from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS
58
/usr/lib/python3/dist-packages/matplotlib/scale.py in <module>
21 import matplotlib as mpl
22 from matplotlib import _api, docstring
---> 23 from matplotlib.ticker import (
24 NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter,
25 NullLocator, LogLocator, AutoLocator, AutoMinorLocator,
/usr/lib/python3/dist-packages/matplotlib/ticker.py in <module>
134 import matplotlib as mpl
135 from matplotlib import _api, cbook
--> 136 from matplotlib import transforms as mtransforms
137
138 _log = logging.getLogger(__name__)
/usr/lib/python3/dist-packages/matplotlib/transforms.py in <module>
44
45 from matplotlib import _api
---> 46 from matplotlib._path import (
47 affine_transform, count_bboxes_overlapping_bbox, update_path_extents)
48 from .path import Path
ImportError: numpy.core.multiarray failed to import
The receptor model’s state-transition diagram (above) has the topology of a symmetric directed path graph on 3 vertices.
The transition R to RL occurs at rate kap*L where L is ligand concentration and kap is an association rate constant with physical dimensions of \(\mbox{time}^{-1} \mbox{conc}^{-1}\) where \(\mbox{conc}\) is concentration (i.e., number density, \(\mbox{amount}/\mbox{length}^3\)). The transition RL to L that involves unbinding of ligand is unimolecular, i.e., physical dimensions of rate (\(\mbox{time}^{-1}\)). The interpretation of the edge weights kbp*L and kbm are similar.
Note
The characters kap and kam stand for \(k_a^+\) and \(k_a^-\) (p for plus and m for minus). Similarly for kbp and kbm. The product of a bimolecular rate constant and ligand concentration kap*L stands for \(k_a^+ [{\rm L}]\) where \({\rm L}\) is a chemical species and the brackets indicate the concentration (number density) of that species.
Analysis of sequential binding#
As described in the section Equilibrium Formalism, a straightforward algebraic calculation reveals that the steady-state fraction of receptors in each of the three states (R, RL, and RLL) is given by
Plotting of a binding curve#
Using the above expressions, we can substitute values for the four rate constants and plot the resulting binding curve(s). Each binding curve gives the fraction of receptors in a particular state (R, RL, RLL) as a function of ligand concentration (L)
At low ligand concentration most receptors are in the unbound form (R, red). At high concentrations most receptors are in the doubly bound form (RLL, blue).
The expressions being plotted above are
Note that for any given ligand concentration, the fraction of receptors in each of the three states sums to 1. This can be shown by asking SageMath to solve for the values of L that satisfy R+RL+RLL == 1, as follows:
solve(R+RL+RLL == 1,L)
The answer indicates that R+RL+RLL == 1 whenever [L == L], that is, for any value of L.
Equilibrium association constants#
The receptor model presented above has the property that the fraction of receptors in each state satisfy detailed balance (i.e., for both reaction a and b, the forward and reverse rates balance. As a consequence, the fraction of receptors in each state can be written in terms of the equilibrium association constants ka=kap/kam and kb=kbp/kbm which have physical dimensions of inverse concentration.
To see this, divide the numerator and denominator of the expressions for R, RL, RLL by kam*kbm to obtain
var('ka kb')
z_R = 1; z_RL = ka*L; z_RLL = ka*L*kb*L; z_T = z_R+z_RL+z_RLL
R = z_R/z_T; RL = z_RL/z_T; RLL = z_RLL/z_T
print('R =',R,'','RL =',RL,'','RLL =',RLL)
The filled circles on the plot below show that these expressions give the same three binding curves for R, RL, and RLL as a function of L.
params = {ka:1,kb:10}
R = R.subs(params); RL = RL.subs(params); RLL = RLL.subs(params)
print('R =',R,'','RL =',RL,'','RLL =',RLL)
X = [0.01,0.03,0.1,0.3,1,3,10,30,100]
vReq = [(x, R(L=x)) for x in X]
pReq = points(vReq, rgbcolor=(0.5,0,0), pointsize=50)
vRLeq = [(x, RL(L=x)) for x in X]
pRLeq = points(vRLeq, rgbcolor=(0.5,0.5,0), pointsize=50)
vRLLeq = [(x, RLL(L=x)) for x in X]
pRLLeq = points(vRLLeq, rgbcolor=(0,0,0.5), pointsize=50)
show(pR + pRL + pRLL + pReq + pRLeq + pRLLeq)
Equilibrium binding curves and arborescences#
Receptor models and equilibrium binding curves can be compactly specified as an arborescence with directed edges (arcs) weighted by association constants and ligand concentrations. In graph theory, an arborescence is a directed graph having a distinguished vertex u (called the root) such that, for any other vertex v, there is exactly one directed path from u to v.
T = DiGraph({'R': {'RL':ka*L}, 'RL': {'RLL':kb*L}})
pos = {'R': (0, 0), 'RL': (2, 0), 'RLL': (4, 0)}
T.plot(figsize=8,edge_labels=True,pos=pos,graph_border=True,vertex_size=1000)
In the arborescence shown above, the root is state R. The edge label ka*L is the product of an equilibrium association constant ka and ligand concentration L, and similarly for reaction b. Comparing to the diagram above, we see that kappa_a = ka*L and kappa_b = kb*L.
Equilibrium binding curves and rooted spanning trees#
An arborescence is a rooted spanning tree of the state-transition diagram with arrows reversed.
When specifying a receptor model and equilibrium binding curve as a rooted spanning tree, the orientation of the arcs specify the reverse direction of each transition (reactant \(\leftarrow\) product).
T = DiGraph({'RL': {'R':ka*L}, 'RLL': {'RL':kb*L}})
pos = {'R': (0, 0), 'RL': (2, 0), 'RLL': (4, 0)}
T.plot(figsize=8,edge_labels=True,pos=pos,graph_border=True,vertex_size=1000)
arborescence vs. rooted spanning tree
A receptor model and its equilibrium binding curve can be specified either as an arborescence or a rooted spanning tree. The two approaches are equivalent.
However, the rooted spanning tree formulation is helpful in the analysis of conformation coupling of receptors. For this reason, in the sections that follow, we will specify receptor models as rooted spanning trees.