{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 20. Lateral Inhibition: Spontaneous symmetry allows spontaneous developmental patterning\n", "\n", "
\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"
\\n\"+\n", " \"\\n\"+\n",
" \"from bokeh.resources import INLINE\\n\"+\n",
" \"output_notebook(resources=INLINE)\\n\"+\n",
" \"\\n\"+\n",
" \"\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"
\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"Sliders are inactive. To get active sliders, re-run notebook with\\n fully_interactive_plots = True\\n in the first code cell.
\\n \"}}]}}]}}]}};\n", " const render_items = [{\"docid\":\"30f17674-9e24-41a9-98b3-18e5152fb44c\",\"roots\":{\"p1175\":\"b1fffe79-dee5-4fa7-992f-b8d424bc8008\"},\"root_ids\":[\"p1175\"]}];\n", " void root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", " }\n", " if (root.Bokeh !== undefined) {\n", " embed_document(root);\n", " } else {\n", " let attempts = 0;\n", " const timer = setInterval(function(root) {\n", " if (root.Bokeh !== undefined) {\n", " clearInterval(timer);\n", " embed_document(root);\n", " } else {\n", " attempts++;\n", " if (attempts > 100) {\n", " clearInterval(timer);\n", " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", " }\n", " }\n", " }, 10, root)\n", " }\n", "})(window);" ], "application/vnd.bokehjs_exec.v0+json": "" }, "metadata": { "application/vnd.bokehjs_exec.v0+json": { "id": "p1175" } }, "output_type": "display_data" } ], "source": [ "def LI_2cell_rhs(x, t, betaN, betaD, betaR, nD, nR, kRS, tau):\n", " Ni, Di, Ri, Nj, Dj, Rj = x\n", " \n", " return np.array(\n", " [\n", " (betaN - Ni - Ni*Dj)/tau,\n", " (betaD/(1+Ri**nD) - Di - Nj*Di)/tau,\n", " betaR*(Ni*Dj)**nR / (kRS**nR + (Ni*Dj)**nR) - Ri,\n", " (betaN - Nj - Nj*Di)/tau,\n", " (betaD/(1+Rj**nD) - Dj - Ni*Dj)/tau,\n", " betaR*(Nj*Di)**nR / (kRS**nR + (Nj*Di)**nR) - Rj,\n", " ]\n", " )\n", "\n", "# Parameters (from Sprinzak et al)\n", "betaN = 10 \n", "betaD = 100 \n", "betaR = 1e6\n", "nD = 1\n", "nR = 3\n", "kRS = 3e5 ** (1/nR) \n", "tau = 1\n", "\n", "# initial conditions\n", "Ni0_slider = bokeh.models.Slider(title=\"Log Initial Ni\", start=-3, end=3, step=0.1, value=1)\n", "Di0_slider = bokeh.models.Slider(title=\"Log Initial Di\", start=-3, end=3, step=0.1, value=-3)\n", "Nj0_slider = bokeh.models.Slider(title=\"Log Initial Nj\", start=-3, end=3, step=0.1, value=1)\n", "Dj0_slider = bokeh.models.Slider(title=\"Log Initial Dj\", start=-3, end=3, step=0.1, value=-2.9)\n", "nR_slider = bokeh.models.Slider(title=\"Cooperativity (nR)\", start=0.1, end=10, step=0.1, value=3)\n", "\n", "x0 = [10**Ni0_slider.value,\n", " 10**Di0_slider.value,\n", " 0,\n", " 10**Nj0_slider.value,\n", " 10**Dj0_slider.value,\n", " 0]\n", "\n", "# Solve ODEs\n", "t = np.linspace(0, 20, 1000)\n", "args = (betaN, betaD, betaR, nD, nR_slider.value, kRS, tau)\n", "x = scipy.integrate.odeint(LI_2cell_rhs, x0, t, args=args)\n", "x = x.transpose()\n", "\n", "cds = bokeh.models.ColumnDataSource(data=dict(t=t, Ni=x[0,:], Di=x[1,:], Ri=x[2,:],\n", " Nj=x[3,:], Dj=x[4,:], Rj=x[5,:]))\n", "\n", "# Set up the plots\n", "pN = bokeh.plotting.figure(\n", " frame_width=275, frame_height=150,\n", " x_axis_label=\"dimensionless time\", \n", " y_axis_label=\"dimensionless\\nNotch concentration\", y_axis_type=\"log\",\n", ")\n", "pD = bokeh.plotting.figure(\n", " frame_width=275, frame_height=150,\n", " x_axis_label=\"dimensionless time\", \n", " y_axis_label=\"dimensionless\\nDelta concentration\", y_axis_type=\"log\",\n", ")\n", "pR = bokeh.plotting.figure(\n", " frame_width=275, frame_height=150,\n", " x_axis_label=\"dimensionless time\", \n", " y_axis_label=\"dimensionless\\nReporter concentration\", y_axis_type=\"log\",\n", ")\n", "\n", "pN.line(source=cds, x=\"t\", y=\"Ni\", color=\"blue\", width=3, legend_label=\"Cell i\")\n", "pN.line(source=cds, x=\"t\", y=\"Nj\", color=\"orange\", width=3, legend_label=\"Cell j\")\n", "pD.line(source=cds, x=\"t\", y=\"Di\", color=\"blue\", width=3, legend_label=\"Cell i\")\n", "pD.line(source=cds, x=\"t\", y=\"Dj\", color=\"orange\", width=3, legend_label=\"Cell j\")\n", "pR.line(source=cds, x=\"t\", y=\"Ri\", color=\"blue\", width=3, legend_label=\"Cell i\")\n", "pR.line(source=cds, x=\"t\", y=\"Rj\", color=\"orange\", width=3, legend_label=\"Cell j\")\n", "\n", "LI_2cell_layout = bokeh.layouts.row(\n", " bokeh.layouts.column(\n", " pN,\n", " pD,\n", " pR,\n", " ),\n", " bokeh.layouts.column(\n", " Ni0_slider,\n", " Di0_slider,\n", " Nj0_slider,\n", " Dj0_slider,\n", " nR_slider,\n", " width=150,\n", " )\n", ")\n", "\n", "# Display\n", "if interactive_python_plots:\n", " # Set up callbacks\n", " def _callback(attr, old, new):\n", " x0 = [10**Ni0_slider.value,\n", " 10**Di0_slider.value,\n", " 0,\n", " 10**Nj0_slider.value,\n", " 10**Dj0_slider.value,\n", " 0]\n", " args = (betaN, betaD, betaR, nD, nR_slider.value, kRS, tau)\n", " x = scipy.integrate.odeint(LI_2cell_rhs, x0, t, args=args)\n", " x = x.transpose()\n", " cds.data = dict(t=t, Ni=x[0,:], Di=x[1,:], Ri=x[2,:], Nj=x[3,:], Dj=x[4,:], Rj=x[5,:])\n", "\n", " Ni0_slider.on_change(\"value\", _callback)\n", " Di0_slider.on_change(\"value\", _callback)\n", " Nj0_slider.on_change(\"value\", _callback)\n", " Dj0_slider.on_change(\"value\", _callback)\n", " nR_slider.on_change(\"value\", _callback)\n", " \n", " # Build the app\n", " def LI_2cell_app(doc):\n", " doc.add_root(LI_2cell_layout)\n", " \n", " bokeh.io.show(LI_2cell_app, notebook_url=notebook_url)\n", "else:\n", " Ni0_slider.disabled = True\n", " Di0_slider.disabled = True\n", " Nj0_slider.disabled = True\n", " Dj0_slider.disabled = True\n", " nR_slider.disabled = True\n", "\n", " # Build layout\n", " LI_2cell_layout = bokeh.layouts.row(\n", " bokeh.layouts.column(\n", " pN,\n", " pD,\n", " pR,\n", " ),\n", " bokeh.layouts.column(\n", " bokeh.layouts.column(\n", " Ni0_slider,\n", " Di0_slider,\n", " Nj0_slider,\n", " Dj0_slider,\n", " width=150\n", " ),\n", " bokeh.models.Div(\n", " text=\"\"\"\n", "Sliders are inactive. To get active sliders, re-run notebook with\n", " fully_interactive_plots = True\n", " in the first code cell.
\n", " \"\"\",\n", " width=250,\n", " ),\n", " )\n", " )\n", "\n", " \n", " bokeh.io.show(LI_2cell_layout)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These plots show that the two-cell system can evolve into anti-correlated states, in which each cell takes on either high or low Delta expression. When the initial Notch concentrations of the two cells are equal, this divergence follows a \"winner-takes-all\" process where the cell that starts with a higher Delta concentration ends up in the high-Delta state. \n", "\n", "By using the sliders above to alter the initial values of the Notch concentration, you can see that the full relationship is a bit more subtle. Is it always guaranteed that the two cells will end up in divergent cell states? No— try moving around the slider for the $n_R$ parameter to see how the system behaves both in regions of low ultrasensitivity _and_ in regimes of high ultrasensitivity. Overall, we see that cells can reach opposite states, which is a prerequisite for patterning. However, this behavior is not guaranteed for _all_ parameters." ] }, { "cell_type": "markdown", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ "### Expanding beyond a two-cell system\n", "\n", "The model demonstrates that lateral inhibition can produce opposite fates in two neighboring cells. What happens when we take this model beyond two cells to a whole two-dimensional field of cells? In a field of cells, a Notch receptors on cell $i$, $N_i$, can interact with Delta ligands on multiple adjacent cells, $D_j$, to form a variety of distinct $T_{ij}$ complexes. All of these complexes can then contribute to the formation of $S_i$ within the receiver cell. Thus, \n", "\n", "\\begin{align}\n", "S_i = \\frac{k_S}{\\gamma_S} \\sum_{j=]i[ } T_{ij} = \\frac{1}{\\gamma_S}\\frac{k_S k_D^+}{k_D^- + k_S}\\sum_{j=]i[ } N_iD_j.\n", "\\end{align}\n", "\n", "Here, the notation $\\sum_{j = ]i[}$ means the index $j$ is iterated over all cells that are adjacent to cell $i$ (but not including $i$ itself), and the resulting set is summed together. \n", "\n", "Carrying this through the simplifcations and nondimensionalizations given above, we obtain the following nondimensionalized ODE system to accomodate arbitray spatial arrangements of cells:\n", "\n", "\\begin{align}\n", "\\tau \\frac{\\mathrm{d}N_i}{\\mathrm{d}t} &= \\beta_N - N_i - \\sum_{j = ]i[} N_i D_j \\\\\n", "\\tau \\frac{\\mathrm{d}D_i}{\\mathrm{d}t} &= \\beta_D \\frac{1}{1 + R_i^{n_D}} - D_i - \\sum_{j = ]i[} N_j D_i \\\\\n", "\\frac{\\mathrm{d}R_i}{\\mathrm{d}t} &= \\beta_R \\frac{\\left(\\sum_{j = ]i[} N_i D_j / k_{RS}\\right)^{n_R}}{1 +\\left(\\sum_{j = ]i[} N_i D_j / k_{RS}\\right)^{n_R} } - R_i\n", "\\end{align}\n", "\n", "Note the way that the summations affect the $T_{ij}$ formation terms in the $N_i$ and $D_i$ ODEs. Although the expressions now look quite a bit more complicated with all these sums, we did not add any new parameters to our system. Visually comparing the above ODE system with the previous ODE system for the two-cell system should reveal how the addition of additional neighbors did not change the fundamental nature of a given species' dynamics.\n", "\n", "\n", "\n", "We now note two features of this model:\n", "\n", "* First, it can represent the behavior any number of cells by expanding the size of the ODE system— a system of $i$ cells can be modeled by $3i$ equations following the above formula. While such a system would be impractical to solve by hand, using numerical approaches we can solve large ODE systems without much additional difficulty. \n", "\n", "* Second, the model can represent arbitrary geometric arrangements of cells because the assignment of neighbors to each cell is left arbitrary. Thus we could easily switch between, for example, a square or hexagonal lattice, or even the dimensionality of the space, by changing the neighbor-assignment rule for a given cell. However, the fact that the ODEs do not natively encode the neighbor-assignment rule means that we will have to write an additional function to go inside our ODE solver that identifies the appropriate neighbors for a given cell.\n", "\n", "In the technical appendix, we show how to simulate the ODEs on a 1-dimensional line of cells and on a hexagonal two-dimensional lattice of cells. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Patterning in one dimension—the \"dotted line.\"\n", "\n", "Let's expand our two-cell system to a one-dimensional line of cells. In this arrangement, the neighbors of cell $i$ are cell $i-1$ and $i+1$. But remember that we are only simulating a finite number, $N$, of cells. Cell $0$ has no leftward neighbor and cell $N-1$ has no rightward neighbor. (We will use 0-based indexing, like Python does.) Different choices of how to handle these boundaries, shown below, could all be equally valid depending on the situation being modeled. \n", "\n", "\n", "\n", "