{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "ecc68823", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "sbml_model = \"\"\"\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Davg\n", " \n", " \n", " k\n", " \n", " \n", " \n", " \n", " \n", " a\n", " \n", " \n", " \n", " \n", " Davg\n", " \n", " \n", " k\n", " \n", " \n", " \n", " \n", " \n", " N\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " v\n", " \n", " \n", " \n", " \n", " \n", " \n", " 1\n", " \n", " \n", " \n", " \n", " 1\n", " \n", " \n", " \n", " \n", " b\n", " \n", " \n", " \n", " \n", " N\n", " \n", " \n", " h\n", " \n", " \n", " \n", " \n", " \n", " \n", " D\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "5c20e439", "metadata": {}, "outputs": [], "source": [ "import tissue_forge as tf\n", "\n", "\n", "class PolarType(tf.ParticleTypeSpec):\n", " \"\"\"A polarized cell type\"\"\"\n", " dynamics = tf.Overdamped\n", " radius = 0.25\n", " species = ['D', 'N']\n", " style = {'colormap': {'species': 'D', 'range': (0, 1)}}\n", "\n", "\n", "class FieldType(tf.ParticleTypeSpec):\n", " \"\"\"A particle type representing portions of a field\"\"\"\n", " frozen = True\n", " species = ['F']\n", " radius = PolarType.radius * 0.5\n", " style = {'colormap': {'species': 'F', 'range': (0, 1)}}\n", "\n", "\n", "class BoundaryFieldType(FieldType):\n", " \"\"\"A particle type representing portions of the boundary of a field\"\"\"\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "id": "de1fd002", "metadata": {}, "outputs": [], "source": [ "# Number of field points modeling diffusion of an environmental signal\n", "num_field_pts = 30\n", "\n", "tf.init(dim=[6, 20, 6],\n", " cells=[3, 10, 3],\n", " dt=1.0,\n", " cutoff=20 / (num_field_pts - 1) * 1.1,\n", " bc={'x': 'free_slip', 'y': 'free_slip', 'z': 'free_slip'})\n", "\n", "\n", "polar_type = PolarType.get()\n", "polar_type.frozen_z = True\n", "field_type = FieldType.get()\n", "bfield_type = BoundaryFieldType.get()\n", "\n", "bfield_type.species.F.constant = True" ] }, { "cell_type": "code", "execution_count": null, "id": "3d5da489", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from roadrunner import RoadRunner\n", "from tissue_forge.models.center import cell_polarity\n", "\n", "# Running time according to SBML model instances\n", "cell_model_time = 0.0\n", "# Storage for SBML model instances\n", "cell_models = dict()\n", "\n", "\n", "def register_cell(ph: tf.ParticleHandle):\n", " \"\"\"Register a newly created cell with polarity and SBML models\"\"\"\n", " global cell_models\n", " cell_polarity.registerParticle(ph)\n", " dn_model = RoadRunner(sbml_model)\n", " dn_model['D'] = np.random.uniform(0.9, 1.0)\n", " dn_model['N'] = np.random.uniform(0.9, 1.0)\n", " cell_models[ph.id] = dn_model\n", " ph.species.D = dn_model['D']\n", " ph.species.N = dn_model['N']\n", "\n", "\n", "def timestep_sbml():\n", " \"\"\"Integrate all SBML models\"\"\"\n", " global cell_model_time\n", " t1 = cell_model_time\n", " t2 = t1 + 0.2\n", " for rr in cell_models.values():\n", " rr.simulate(t1, t2)\n", " cell_model_time = t2" ] }, { "cell_type": "code", "execution_count": null, "id": "730233a5", "metadata": {}, "outputs": [], "source": [ "def neighbor_delta(ph: tf.ParticleHandle):\n", " \"\"\"Get the average Delta value of all cell neighbors\"\"\"\n", " delta_tot = 0.0\n", " neighbors = ph.neighbors(distance=1.5 * polar_type.radius, types=[polar_type])\n", " nn = len(neighbors)\n", " if nn == 0:\n", " return delta_tot\n", " for neighbor in neighbors:\n", " delta_tot += cell_models[neighbor.id]['D']\n", " return delta_tot / nn\n", "\n", "\n", "def update_cell_models():\n", " \"\"\"Update all cell models\"\"\"\n", " for ph in polar_type:\n", " ph_cell_model = cell_models[ph.id]\n", " ph_cell_model['Davg'] = neighbor_delta(ph)\n", " ph.species.D = ph_cell_model['D']\n", " ph.species.N = ph_cell_model['N']\n", " timestep_sbml()" ] }, { "cell_type": "code", "execution_count": null, "id": "420f3c74", "metadata": {}, "outputs": [], "source": [ "import ipywidgets as ipw\n", "\n", "field_control = 0.0\n", "\n", "def field_model():\n", " \"\"\"Do the field model. Delta-Notch signaling depends on local concentrations of a diffusive field\"\"\"\n", " nbs_cutoff = field_disc_len - polar_type.radius - field_type.radius\n", " for ph in polar_type:\n", " ph: tf.ParticleHandle\n", " f_tot = 0.0\n", " nbs = ph.neighbors(distance=nbs_cutoff, types=[field_type])\n", " for n in nbs:\n", " cf = 1 - ph.relativePosition(n.position).length() / field_disc_len\n", " f_tot += n.species.F.value * cf * cf\n", " f_inh = min(1.0, f_tot / len(nbs))\n", " cell_models[ph.id]['a'] = 0.01 * (1 + field_control * (f_inh - 1))\n", "\n", "# Create a slider to interactively control regulation by the environment\n", "field_control_slider = ipw.FloatSlider(\n", " value=field_control, \n", " min=0, \n", " max=1, \n", " step=0.01, \n", " description='Field control:',\n", " disabled=False,\n", " continuous_update=True,\n", " orientation='horizontal'\n", ")\n", "def update_field_model(change):\n", " global field_control\n", " field_control = change.new\n", "field_control_slider.observe(update_field_model, names='value')" ] }, { "cell_type": "code", "execution_count": null, "id": "2620d8e2", "metadata": {}, "outputs": [], "source": [ "# Set up cell polarity module\n", "cell_polarity.load()\n", "cell_polarity.setArrowScale(0.0) # Hide arrows\n", "cell_polarity.setArrowLength(0.0)\n", "cell_polarity.registerType(pType=polar_type, initMode=\"value\",\n", " initPolarAB=tf.fVector3(0, 0, 1), initPolarPCP=tf.fVector3(1, 0, 0))" ] }, { "cell_type": "code", "execution_count": null, "id": "8c4d26fa", "metadata": {}, "outputs": [], "source": [ "# Add random motility\n", "f_random = tf.Force.random(1E-2, 0)\n", "tf.bind.force(f_random, polar_type)" ] }, { "cell_type": "code", "execution_count": null, "id": "e749985d", "metadata": {}, "outputs": [], "source": [ "# Add volume exclusion and anisotropic intercellular adhesion\n", "pot_contact = tf.Potential.morse(d=5E-4, a=5, r0=2*PolarType.radius, min=0, max=2*PolarType.radius, shifted=False)\n", "pot_polar = cell_polarity.createContactPotential(cutoff=2.5*polar_type.radius, mag=2.5E-3, rate=0,\n", " distanceCoeff=10.0*polar_type.radius, couplingFlat=1.0)\n", "tf.bind.types(pot_contact + pot_polar, polar_type, polar_type)" ] }, { "cell_type": "code", "execution_count": null, "id": "3e8d6715", "metadata": {}, "outputs": [], "source": [ "# Initialize cells in a hexagonal monolayer\n", "num_cells = 50\n", "num_cells_dim = int(np.sqrt(num_cells))\n", "estimated_dist_cf = 0.85\n", "agg_width = (tf.Universe.dim[0],\n", " np.sqrt(3) * num_cells * polar_type.radius * estimated_dist_cf)\n", "agg_pos = tf.Universe.center - tf.FVector3(agg_width[0] / 2 - polar_type.radius,\n", " agg_width[1] / 2 - polar_type.radius,\n", " 0)\n", "if agg_pos[0] < 0 or agg_pos[1] < 0:\n", " raise ValueError('Too many cells')\n", "for x in range(int(agg_width[0] / (2 * polar_type.radius * estimated_dist_cf))):\n", " yc = 0\n", " for y in range(num_cells):\n", " ph = polar_type(agg_pos + tf.FVector3((2 * x - (yc % 2)) * polar_type.radius * estimated_dist_cf,\n", " np.sqrt(3) * y * polar_type.radius * estimated_dist_cf, 0))\n", " register_cell(ph)\n", " yc += 1" ] }, { "cell_type": "code", "execution_count": null, "id": "1dab2341", "metadata": {}, "outputs": [], "source": [ "# Initialize the field as a regular grid\n", "field_offset = 1E-2\n", "field_width = tf.Universe.dim.xy() - tf.FVector2(2 * field_offset)\n", "npts = tf.iVector2(int(num_field_pts * tf.Universe.dim[0] / tf.Universe.dim[1]), num_field_pts)\n", "field_dx = tf.FVector2(field_width[0] / (npts[0] - 1), field_width[1] / (npts[1] - 1))\n", "field_disc_len = field_dx.length()\n", "field_origin = tf.Universe.dim.xy() - field_width\n", "for i in range(npts.x()):\n", " for j in range(npts.y()):\n", " p = field_type(position=[i * field_dx[0], j * field_dx[1], tf.Universe.center[2]])\n", " if j == 0:\n", " p.become(bfield_type)\n", " elif j == npts.y() - 1:\n", " p.become(bfield_type)\n", " p.species.F = 1.0" ] }, { "cell_type": "code", "execution_count": null, "id": "8f8f1faf", "metadata": {}, "outputs": [], "source": [ "# Add fluxes between field particles\n", "tf.Fluxes.flux(field_type, field_type, 'F', 0.5)\n", "tf.Fluxes.flux(field_type, bfield_type, 'F', 0.5)" ] }, { "cell_type": "code", "execution_count": null, "id": "e9b23014", "metadata": {}, "outputs": [], "source": [ "# Add models\n", "tf.event.on_time(period=tf.Universe.dt, invoke_method=lambda e: update_cell_models())\n", "tf.event.on_time(period=tf.Universe.dt, invoke_method=lambda e: field_model())" ] }, { "cell_type": "code", "execution_count": null, "id": "01bd2ebf", "metadata": {}, "outputs": [], "source": [ "from IPython.display import display\n", "display(field_control_slider)\n", "# Set the standard top view and show\n", "tf.system.camera_view_top()\n", "tf.show()" ] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 5 }