From af557244e47dd4ec16ca8272fb0d4ca3dc5ba203 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Mon, 30 Oct 2023 21:22:04 +0100 Subject: [PATCH 01/23] Make example run --- ca2+-examples/dendritic_spine.ipynb | 482 +++++++++++++++------------- 1 file changed, 263 insertions(+), 219 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index b8cd32e..883531a 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -1,9 +1,8 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", - "id": "f65f18d7", + "id": "78f7062b", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,8 +29,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "cc398816", + "execution_count": null, + "id": "a4e5b17d", "metadata": {}, "outputs": [], "source": [ @@ -40,7 +39,6 @@ "import numpy as np\n", "import pathlib\n", "import logging\n", - "import gmsh # must be imported before pyvista if dolfin is imported first\n", "\n", "from smart import config, mesh, model, mesh_tools, visualization\n", "from smart.units import unit\n", @@ -50,24 +48,19 @@ " Reaction,\n", " Species,\n", " SpeciesContainer,\n", - " ParameterContainer,\n", " CompartmentContainer,\n", - " ReactionContainer,\n", - " sbmodel_from_locals\n", + " sbmodel_from_locals,\n", ")\n", "\n", "from matplotlib import pyplot as plt\n", - "import matplotlib.image as mpimg\n", - "from matplotlib import rcParams\n", "\n", "logger = logging.getLogger(\"smart\")\n", "logger.setLevel(logging.DEBUG)" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "95b9d865", + "id": "c2802b1c", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -75,8 +68,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "4f4023cf", + "execution_count": null, + "id": "223427f5", "metadata": {}, "outputs": [], "source": [ @@ -99,7 +92,7 @@ }, { "cell_type": "markdown", - "id": "6e9f51db", + "id": "42cd87db", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -109,40 +102,23 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "c49bff44", + "execution_count": null, + "id": "4313d74e", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[36m\u001b[0m \u001b[97m2023-10-19 23:27:04,626 smart.mesh - INFO - HDF5 mesh, \"parent_mesh\", successfully loaded from file: ellipseSpine_mesh/ellipseSpine_mesh.h5! (mesh.py:220)\u001b[0m \u001b[36m\u001b[0m\n" - ] - }, - { - "ename": "NameError", - "evalue": "name 'z_PSD' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/root/shared/gitrepos/smart-comp-sci/ca2+-examples/dendritic_spine.ipynb Cell 6\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 34\u001b[0m integrateDomain \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39mMeshFunction(\u001b[39m\"\u001b[39m\u001b[39msize_t\u001b[39m\u001b[39m\"\u001b[39m, spine_mesh, \u001b[39m2\u001b[39m, \u001b[39m0\u001b[39m)\n\u001b[1;32m 35\u001b[0m \u001b[39mfor\u001b[39;00m f \u001b[39min\u001b[39;00m d\u001b[39m.\u001b[39mfacets(spine_mesh):\n\u001b[0;32m---> 36\u001b[0m integrateDomain[f] \u001b[39m=\u001b[39m \u001b[39m1\u001b[39m \u001b[39mif\u001b[39;00m (f\u001b[39m.\u001b[39mmidpoint()\u001b[39m.\u001b[39mz() \u001b[39m>\u001b[39m z_PSD) \u001b[39melse\u001b[39;00m \u001b[39m0\u001b[39m\n\u001b[1;32m 37\u001b[0m ds \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39mMeasure(\u001b[39m\"\u001b[39m\u001b[39mds\u001b[39m\u001b[39m\"\u001b[39m, domain\u001b[39m=\u001b[39mspine_mesh, subdomain_data\u001b[39m=\u001b[39mintegrateDomain)\n\u001b[1;32m 38\u001b[0m A_PSD \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39massemble(\u001b[39m1.0\u001b[39m\u001b[39m*\u001b[39mds(\u001b[39m1\u001b[39m))\n", - "\u001b[0;31mNameError\u001b[0m: name 'z_PSD' is not defined" - ] - } - ], + "outputs": [], "source": [ - "# spine_rad = 0.237\n", - "# SA_rad = .08\n", - "# ar_list = [27,15,7]\n", - "# ar_1 = (1/((ar_list[1]/ar_list[0])*(ar_list[2]/ar_list[0])))**(1/3)\n", - "# ar_2 = ar_1 * ar_list[1]/ar_list[0]\n", - "# ar_3 = ar_1 * ar_list[2]/ar_list[0]\n", - "# z_PSD = 0.8 * spine_rad*ar_3\n", - "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids((spine_rad*ar_1,spine_rad*ar_2,spine_rad*ar_3), \n", - "# (SA_rad*ar_1, SA_rad*ar_2, SA_rad*ar_3), \n", - " # hEdge=0.02)\n", + "spine_rad = 0.237\n", + "SA_rad = 0.08\n", + "ar_list = [27, 15, 7]\n", + "ar_1 = (1 / ((ar_list[1] / ar_list[0]) * (ar_list[2] / ar_list[0]))) ** (1 / 3)\n", + "ar_2 = ar_1 * ar_list[1] / ar_list[0]\n", + "ar_3 = ar_1 * ar_list[2] / ar_list[0]\n", + "z_PSD = 0.8 * spine_rad * ar_3\n", + "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids(\n", + "# (spine_rad * ar_1, spine_rad * ar_2, spine_rad * ar_3),\n", + "# (SA_rad * ar_1, SA_rad * ar_2, SA_rad * ar_3),\n", + "# hEdge=0.02,\n", + "# )\n", "# Load mesh\n", "# spine_mesh = d.Mesh('spine_mesh.xml')\n", "cur_dir = pathlib.Path.cwd()\n", @@ -153,7 +129,7 @@ "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", "facet_array = facet_markers.array()[:]\n", "for i in range(len(facet_array)):\n", - " if facet_array[i] == 11: # this indicates PSD\n", + " if facet_array[i] == 11: # this indicates PSD\n", " facet_array[i] = 10\n", "\n", "mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", @@ -169,16 +145,17 @@ "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", "# for f in d.facets(spine_mesh):\n", "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", - "integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", + "# breakpoint()\n", + "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", + "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", - "A_PSD = d.assemble(1.0*ds(11))\n", + "A_PSD = d.assemble(1.0 * ds(11))\n", "visualization.plot_dolfin_mesh(spine_mesh, cell_markers)" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "46582d26", + "id": "a14c9ccf", "metadata": {}, "source": [ "## Model generation\n", @@ -191,7 +168,7 @@ { "cell_type": "code", "execution_count": null, - "id": "02a000f2", + "id": "aa879ee8", "metadata": {}, "outputs": [], "source": [ @@ -199,17 +176,16 @@ "PM = Compartment(\"PM\", 2, um, 10)\n", "SA = Compartment(\"SA\", 3, um, 2)\n", "SAm = Compartment(\"SAm\", 2, um, 12)\n", - "PM.specify_nonadjacency(['SAm', 'SA'])\n", - "SAm.specify_nonadjacency(['PM'])\n", + "PM.specify_nonadjacency([\"SAm\", \"SA\"])\n", + "SAm.specify_nonadjacency([\"PM\"])\n", "\n", "cc = CompartmentContainer()\n", "cc.add([Cyto, PM, SA, SAm])" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "f9121840", + "id": "f3265cbb", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -218,29 +194,34 @@ { "cell_type": "code", "execution_count": null, - "id": "09079b17", + "id": "f9c034f4", "metadata": {}, "outputs": [], "source": [ "Ca = Species(\"Ca\", 0.1, vol_unit, 220.0, D_unit, \"Cyto\")\n", - "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", + "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", "NMDAR_loc = f\"(1 + sign(z - {z_PSD}))/2\"\n", - "NMDAR = Species(\"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\") # specify species to localize NMDAR calcium influx to PSD\n", - "VSCC_zThresh = -10#0.3 #-0.25 for single spine, 0.3 for 2 spine\n", + "NMDAR = Species(\n", + " \"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # specify species to localize NMDAR calcium influx to PSD\n", + "VSCC_zThresh = -10 # 0.3 #-0.25 for single spine, 0.3 for 2 spine\n", "VSCC_loc = f\"(1 + sign(z - {VSCC_zThresh}))/2\"\n", - "VSCC = Species(\"VSCC\", VSCC_loc, dimensionless, 0.0, D_unit, \"PM\") # specify species to localize VSCC calcium influx to spine body and neck\n", + "VSCC = Species(\n", + " \"VSCC\", VSCC_loc, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # specify species to localize VSCC calcium influx to spine body and neck\n", "\n", - "Bf = Species(\"Bf\", 78.7*n_PMr, vol_unit*um, 0.0, D_unit, \"PM\")\n", + "Bf = Species(\"Bf\", 78.7 * n_PMr, vol_unit * um, 0.0, D_unit, \"PM\")\n", "Bm = Species(\"Bm\", 20.0, vol_unit, 20.0, D_unit, \"Cyto\")\n", - "CaSA = Species(\"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\") # effective D due to buffering\n", + "CaSA = Species(\n", + " \"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\"\n", + ") # effective D due to buffering\n", "sc = SpeciesContainer()\n", "sc.add([Ca, NMDAR, Bf, Bm, CaSA])" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "3c56e840", + "id": "1baa8cf6", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -261,7 +242,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2e1f6882", + "id": "36b02f4a", "metadata": {}, "outputs": [], "source": [ @@ -269,103 +250,171 @@ "Vrest_expr = -65\n", "Vrest = Parameter(\"Vrest\", Vrest_expr, voltage_unit)\n", "bpmax = 38\n", - "Ibsf, tbsf, Ibss, tbss = 0.75, 0.003, 0.25, 0.025 \n", + "Ibsf, tbsf, Ibss, tbss = 0.75, 0.003, 0.25, 0.025\n", "tdelaybp, tdelay = 0.002, 0.0\n", "sterm = 25\n", - "tep1, tep2 = 0.05, 0.005 \n", + "tep1, tep2 = 0.05, 0.005\n", "t = sym.symbols(\"t\")\n", - "BPAP = bpmax * (Ibsf * sym.exp(-(t-tdelaybp)/tbsf) +\n", - " Ibss * sym.exp(-(t-tdelaybp)/tbss)) * (1+sym.sign(t))/2\n", - "EPSP = sterm * (sym.exp(-(t-tdelay)/tep1) - sym.exp(-(t-tdelay)/tep2)) * (1+sym.sign(t))/2\n", + "BPAP = (\n", + " bpmax\n", + " * (Ibsf * sym.exp(-(t - tdelaybp) / tbsf) + Ibss * sym.exp(-(t - tdelaybp) / tbss))\n", + " * (1 + sym.sign(t))\n", + " / 2\n", + ")\n", + "EPSP = (\n", + " sterm\n", + " * (sym.exp(-(t - tdelay) / tep1) - sym.exp(-(t - tdelay) / tep2))\n", + " * (1 + sym.sign(t))\n", + " / 2\n", + ")\n", "Vm_expr = Vrest_expr + BPAP + EPSP\n", "Vm = Parameter.from_expression(\n", - " \"Vm\", Vm_expr, voltage_unit, use_preintegration=False,\n", - " )\n", + " \"Vm\",\n", + " Vm_expr,\n", + " voltage_unit,\n", + " use_preintegration=False,\n", + ")\n", "# Define known constants\n", - "N_A = 6.022e23 # molecules per mole\n", - "F = 96485.332 # Faraday's constant (Coulombs per mole)\n", - "Q = 1.602e-19 # Coulombs per elementary charge\n", + "N_A = 6.022e23 # molecules per mole\n", + "F = 96485.332 # Faraday's constant (Coulombs per mole)\n", + "Q = 1.602e-19 # Coulombs per elementary charge\n", "\n", "n_PMr = Parameter(\"n_PMr\", 0.1011, um)\n", "# NMDAR calcium influx\n", "P0 = 0.5\n", - "CaEC = 2 #mM\n", - "h = 11.3 #pS/mM\n", - "G0 = 65.6 #pS\n", + "CaEC = 2 # mM\n", + "h = 11.3 # pS/mM\n", + "G0 = 65.6 # pS\n", "r = 0.5\n", - "ginf = 15.2 #pS\n", - "convert_factor = 1e-15 # A/mV per pS\n", - "zeta_i = (G0 + r*CaEC*h)/(1 + r*CaEC*h / ginf) # single channel conductance in pS\n", - "G_NMDARVal = convert_factor*zeta_i / (2*Q)\n", - "G_NMDAR = Parameter(\"G_NMDAR\", G_NMDARVal, molecule/(voltage_unit*sec))\n", + "ginf = 15.2 # pS\n", + "convert_factor = 1e-15 # A/mV per pS\n", + "zeta_i = (G0 + r * CaEC * h) / (\n", + " 1 + r * CaEC * h / ginf\n", + ") # single channel conductance in pS\n", + "G_NMDARVal = convert_factor * zeta_i / (2 * Q)\n", + "G_NMDAR = Parameter(\"G_NMDAR\", G_NMDARVal, molecule / (voltage_unit * sec))\n", "If, tau_f, Is, tau_s = 0.5, 0.05, 0.5, 0.2\n", - "Km = 0.092 #(1/mV)\n", - "Mg, MgScale = 1, 3.57 #mM\n", - "B_V = 1 / (1 + sym.exp(-Km*Vm_expr*Mg/MgScale))\n", - "gamma_i_scale = P0 * (If*sym.exp(-t/tau_f) + Is*sym.exp(-t/tau_s))*B_V * 1#(1+sym.sign(t))/2\n", + "Km = 0.092 # (1/mV)\n", + "Mg, MgScale = 1, 3.57 # mM\n", + "B_V = 1 / (1 + sym.exp(-Km * Vm_expr * Mg / MgScale))\n", + "gamma_i_scale = (\n", + " P0 * (If * sym.exp(-t / tau_f) + Is * sym.exp(-t / tau_s)) * B_V * 1\n", + ") # (1+sym.sign(t))/2\n", "beta_NMDAR = 85\n", "# A_PSD = 2*np.pi*spine_rad*(spine_rad - z_PSD)\n", - "J0_NMDAR_expr = gamma_i_scale / (beta_NMDAR*A_PSD)\n", + "J0_NMDAR_expr = gamma_i_scale / (beta_NMDAR * A_PSD)\n", "J0_NMDAR = Parameter.from_expression(\n", - " \"J0_NMDAR\", J0_NMDAR_expr, 1/um**2, use_preintegration=False,\n", - " )\n", - "a1 = Reaction(\"a1\", [], [\"Ca\"], species_map={\"NMDAR\":\"NMDAR\"},\n", - " param_map={\"J0\": \"J0_NMDAR\", \"G_NMDAR\":\"G_NMDAR\", \"Vm\":\"Vm\", \"Vrest\":\"Vrest\"},\n", - " eqn_f_str=\"J0*NMDAR*G_NMDAR*(Vm - Vrest)\", explicit_restriction_to_domain=\"PM\")\n", - "#VSCC calcium influx\n", - "gamma = 3.72 #pS\n", - "k_Ca = -convert_factor * gamma * Vm_expr * N_A * (0.393 - sym.exp(-Vm_expr/80.36)) / (2*F*(1 - sym.exp(Vm_expr/80.36)))\n", + " \"J0_NMDAR\",\n", + " J0_NMDAR_expr,\n", + " 1 / um**2,\n", + " use_preintegration=False,\n", + ")\n", + "a1 = Reaction(\n", + " \"a1\",\n", + " [],\n", + " [\"Ca\"],\n", + " species_map={\"NMDAR\": \"NMDAR\"},\n", + " param_map={\"J0\": \"J0_NMDAR\", \"G_NMDAR\": \"G_NMDAR\", \"Vm\": \"Vm\", \"Vrest\": \"Vrest\"},\n", + " eqn_f_str=\"J0*NMDAR*G_NMDAR*(Vm - Vrest)\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# VSCC calcium influx\n", + "gamma = 3.72 # pS\n", + "k_Ca = (\n", + " -convert_factor\n", + " * gamma\n", + " * Vm_expr\n", + " * N_A\n", + " * (0.393 - sym.exp(-Vm_expr / 80.36))\n", + " / (2 * F * (1 - sym.exp(Vm_expr / 80.36)))\n", + ")\n", "alpha4, beta4 = 34700, 3680\n", - "VSCC_biexp = (sym.exp(-alpha4*t) - sym.exp(-beta4*t)) * (1 + sym.sign(t))/2\n", - "VSCCNum = 2 # molecules/um^2\n", + "VSCC_biexp = (sym.exp(-alpha4 * t) - sym.exp(-beta4 * t)) * (1 + sym.sign(t)) / 2\n", + "VSCCNum = 2 # molecules/um^2\n", "J_VSCC = Parameter.from_expression(\n", - " \"J_VSCC\", VSCCNum*k_Ca*VSCC_biexp, molecule/(um**2 * sec), use_preintegration=False,\n", - " )\n", - "a2 = Reaction(\"a2\", [], [\"Ca\"], species_map={\"VSCC\":\"VSCC\"}, param_map={\"J\": \"J_VSCC\"},\n", - " eqn_f_str=\"J*VSCC\", explicit_restriction_to_domain=\"PM\")\n", - "#PMCA\n", + " \"J_VSCC\",\n", + " VSCCNum * k_Ca * VSCC_biexp,\n", + " molecule / (um**2 * sec),\n", + " use_preintegration=False,\n", + ")\n", + "a2 = Reaction(\n", + " \"a2\",\n", + " [],\n", + " [\"Ca\"],\n", + " species_map={\"VSCC\": \"VSCC\"},\n", + " param_map={\"J\": \"J_VSCC\"},\n", + " eqn_f_str=\"J*VSCC\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# PMCA\n", "Prtote = Parameter(\"Prtote\", 191, vol_unit)\n", "Kme = Parameter(\"Kme\", 2.43, vol_unit)\n", "Kmx = Parameter(\"Kmx\", 0.139, vol_unit)\n", - "Vmax_lr23 = Parameter(\"Vmax_lr23\", 0.113, vol_unit/sec)\n", + "Vmax_lr23 = Parameter(\"Vmax_lr23\", 0.113, vol_unit / sec)\n", "Km_lr23 = Parameter(\"Km_lr23\", 0.442, vol_unit)\n", - "Vmax_hr23 = Parameter(\"Vmax_hr23\", 0.59, vol_unit/sec)\n", + "Vmax_hr23 = Parameter(\"Vmax_hr23\", 0.59, vol_unit / sec)\n", "Km_hr23 = Parameter(\"Km_hr23\", 0.442, vol_unit)\n", "beta_PMCA = 100\n", - "beta_i_str = \"(1 + Prtote*Kme/(Kme+c)**2 + Prtote*Kmx/(Kmx+c)**2)**(-1)\" # buffering term\n", + "beta_i_str = (\n", + " \"(1 + Prtote*Kme/(Kme+c)**2 + Prtote*Kmx/(Kmx+c)**2)**(-1)\" # buffering term\n", + ")\n", "PMCA_str = \"Vmax_lr23*c**2/(Km_lr23**2 + c**2) + Vmax_hr23*c**5/(Km_hr23**5 + c**5)\"\n", - "a3 = Reaction(\"a3\", [\"Ca\"], [],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_lr23\":\"Vmax_lr23\", \"Km_lr23\":\"Km_lr23\",\n", - " \"Vmax_hr23\":\"Vmax_hr23\", \"Km_hr23\":\"Km_hr23\", \"n_PMr\":\"n_PMr\"},\n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_PMCA}*({beta_i_str})*({PMCA_str})*n_PMr\",\n", - " explicit_restriction_to_domain=\"PM\")\n", - "#NCX\n", - "Vmax_r22 = Parameter(\"Vmax_r22\", 0.1, vol_unit/sec)\n", - "Km_r22 = Parameter(\"Km_r22\", 1, vol_unit) #uM\n", + "a3 = Reaction(\n", + " \"a3\",\n", + " [\"Ca\"],\n", + " [],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_lr23\": \"Vmax_lr23\",\n", + " \"Km_lr23\": \"Km_lr23\",\n", + " \"Vmax_hr23\": \"Vmax_hr23\",\n", + " \"Km_hr23\": \"Km_hr23\",\n", + " \"n_PMr\": \"n_PMr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_PMCA}*({beta_i_str})*({PMCA_str})*n_PMr\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# NCX\n", + "Vmax_r22 = Parameter(\"Vmax_r22\", 0.1, vol_unit / sec)\n", + "Km_r22 = Parameter(\"Km_r22\", 1, vol_unit) # uM\n", "beta_NCX = 1000\n", "NCX_str = \"Vmax_r22*c/(Km_r22 + c)\"\n", - "a4 = Reaction(\"a4\", [\"Ca\"], [],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_r22\":\"Vmax_r22\", \"Km_r22\":\"Km_r22\", \"n_PMr\":\"n_PMr\"}, \n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_NCX}*({beta_i_str})*({NCX_str})*n_PMr\",\n", - " explicit_restriction_to_domain=\"PM\")\n", - "#Immobilized buffers\n", - "kBf_on = Parameter(\"kBf_on\", 1, 1/(uM*sec))\n", - "kBf_off = Parameter(\"kBf_off\", 2, 1/sec)\n", - "Bf_tot = Parameter(\"Bf_tot\", 78.7*n_PMr.value, vol_unit*um)\n", - "a5 = Reaction(\"a5\",[\"Ca\", \"Bf\"], [], \n", - " {\"kon\":\"kBf_on\",\"koff\":\"kBf_off\",\"Bf_tot\":\"Bf_tot\"},\n", - " eqn_f_str=\"kon*Ca*Bf - koff*(Bf_tot - Bf)\",\n", - " explicit_restriction_to_domain=\"PM\")" + "a4 = Reaction(\n", + " \"a4\",\n", + " [\"Ca\"],\n", + " [],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_r22\": \"Vmax_r22\",\n", + " \"Km_r22\": \"Km_r22\",\n", + " \"n_PMr\": \"n_PMr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_NCX}*({beta_i_str})*({NCX_str})*n_PMr\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# Immobilized buffers\n", + "kBf_on = Parameter(\"kBf_on\", 1, 1 / (uM * sec))\n", + "kBf_off = Parameter(\"kBf_off\", 2, 1 / sec)\n", + "Bf_tot = Parameter(\"Bf_tot\", 78.7 * n_PMr.value, vol_unit * um)\n", + "a5 = Reaction(\n", + " \"a5\",\n", + " [\"Ca\", \"Bf\"],\n", + " [],\n", + " {\"kon\": \"kBf_on\", \"koff\": \"kBf_off\", \"Bf_tot\": \"Bf_tot\"},\n", + " eqn_f_str=\"kon*Ca*Bf - koff*(Bf_tot - Bf)\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "901cc4f9", + "id": "95266776", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -374,36 +423,35 @@ { "cell_type": "code", "execution_count": null, - "id": "e7aa0975", + "id": "3151827b", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "from sympy.utilities.lambdify import lambdify\n", - "Vm_func = lambdify(t, Vm_expr, 'numpy') # returns a numpy-ready function\n", + "\n", + "Vm_func = lambdify(t, Vm_expr, \"numpy\") # returns a numpy-ready function\n", "tArray = np.linspace(-0.01, 0.05, 600)\n", "fig, ax = plt.subplots(3, 1)\n", "fig.set_size_inches(15, 10)\n", "ax[0].plot(tArray, Vm_func(tArray))\n", - "ax[0].set(xlabel='Time (s)',\n", - " ylabel='Membrane voltage\\n(mV)')\n", + "ax[0].set(xlabel=\"Time (s)\", ylabel=\"Membrane voltage\\n(mV)\")\n", "\n", - "NMDAR_func = lambdify(t, A_PSD*J0_NMDAR_expr*G_NMDARVal*(Vm_expr - Vrest_expr), 'numpy')\n", + "NMDAR_func = lambdify(\n", + " t, A_PSD * J0_NMDAR_expr * G_NMDARVal * (Vm_expr - Vrest_expr), \"numpy\"\n", + ")\n", "ax[1].plot(tArray, NMDAR_func(tArray))\n", - "ax[1].set(xlabel='Time (s)',\n", - " ylabel='NMDAR flux\\n(molecules/(um^2*s))')\n", + "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/(um^2*s))\")\n", "\n", - "VSCC_func = lambdify(t, .8057*VSCCNum*k_Ca*VSCC_biexp, 'numpy')\n", + "VSCC_func = lambdify(t, 0.8057 * VSCCNum * k_Ca * VSCC_biexp, \"numpy\")\n", "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", - "ax[2].set(xlabel='Time (s)',\n", - " ylabel='VSCC flux\\n(molecules/(um^2*s))')" + "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "ab82d24a", + "id": "0ea208f1", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -412,22 +460,26 @@ { "cell_type": "code", "execution_count": null, - "id": "f8111aff", + "id": "4cb92100", "metadata": {}, "outputs": [], "source": [ "# calcium buffering in the cytosol\n", - "kBm_on = Parameter(\"kBm_on\", 1, 1/(uM*sec))\n", - "kBm_off = Parameter(\"kBm_off\", 1, 1/sec)\n", + "kBm_on = Parameter(\"kBm_on\", 1, 1 / (uM * sec))\n", + "kBm_off = Parameter(\"kBm_off\", 1, 1 / sec)\n", "Bm_tot = Parameter(\"Bm_tot\", 20, vol_unit)\n", - "b1 = Reaction(\"b1\", [\"Ca\",\"Bm\"], [], param_map={\"kon\":\"kBm_on\",\"koff\":\"kBm_off\",\"Bm_tot\":\"Bm_tot\"},\n", - " eqn_f_str=\"kon*Ca*Bm - koff*(Bm_tot - Bm)\")" + "b1 = Reaction(\n", + " \"b1\",\n", + " [\"Ca\", \"Bm\"],\n", + " [],\n", + " param_map={\"kon\": \"kBm_on\", \"koff\": \"kBm_off\", \"Bm_tot\": \"Bm_tot\"},\n", + " eqn_f_str=\"kon*Ca*Bm - koff*(Bm_tot - Bm)\",\n", + ")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "08a028d1", + "id": "f89984db", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -438,39 +490,53 @@ { "cell_type": "code", "execution_count": null, - "id": "40fede1d", + "id": "e2f549c0", "metadata": {}, "outputs": [], "source": [ "# SERCA flux\n", - "n_SAr = Parameter(\"n_SAr\", .0113, um)\n", - "Vmax_r19 = Parameter(\"Vmax_r19\", 113, vol_unit/sec)\n", + "n_SAr = Parameter(\"n_SAr\", 0.0113, um)\n", + "Vmax_r19 = Parameter(\"Vmax_r19\", 113, vol_unit / sec)\n", "KP_r19 = Parameter(\"KP_r19\", 0.2, vol_unit)\n", "beta_SERCA = 1000\n", "VmaxSERCA_str = \"Vmax_r19*c**2/(KP_r19**2 + c**2)\"\n", - "c1 = Reaction(\"c1\", [\"Ca\"], [\"CaSA\"],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_r19\":\"Vmax_r19\", \"KP_r19\":\"KP_r19\", \"n_SAr\":\"n_SAr\"}, \n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_SERCA}*({beta_i_str})*({VmaxSERCA_str})*n_SAr\",\n", - " explicit_restriction_to_domain=\"SAm\")\n", + "c1 = Reaction(\n", + " \"c1\",\n", + " [\"Ca\"],\n", + " [\"CaSA\"],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_r19\": \"Vmax_r19\",\n", + " \"KP_r19\": \"KP_r19\",\n", + " \"n_SAr\": \"n_SAr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_SERCA}*({beta_i_str})*({VmaxSERCA_str})*n_SAr\",\n", + " explicit_restriction_to_domain=\"SAm\",\n", + ")\n", "# calcium leak out of the SA\n", - "k_leak = Parameter(\"k_leak\", 0.1608, 1/sec)\n", - "c2 = Reaction(\"c2\", [\"CaSA\"], [\"Ca\"], {\"k_leak\":\"k_leak\", \"n_SAr\":\"n_SAr\"}, \n", - " {\"c\":\"Ca\", \"cSA\":\"CaSA\"},\n", - " eqn_f_str=f\"k_leak*(cSA - c)*n_SAr\",\n", - " explicit_restriction_to_domain=\"SAm\")\n", + "k_leak = Parameter(\"k_leak\", 0.1608, 1 / sec)\n", + "c2 = Reaction(\n", + " \"c2\",\n", + " [\"CaSA\"],\n", + " [\"Ca\"],\n", + " {\"k_leak\": \"k_leak\", \"n_SAr\": \"n_SAr\"},\n", + " {\"c\": \"Ca\", \"cSA\": \"CaSA\"},\n", + " eqn_f_str=\"k_leak*(cSA - c)*n_SAr\",\n", + " explicit_restriction_to_domain=\"SAm\",\n", + ")\n", "\n", - "xi = 0.0227272727 # scaling factor to account for rapid buffering in SA\n", + "xi = 0.0227272727 # scaling factor to account for rapid buffering in SA\n", "for c in [c1, c2]:\n", - " c.flux_scaling = {'CaSA': xi}\n", + " c.flux_scaling = {\"CaSA\": xi}\n", " c.__post_init__()" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "e4edeebc", + "id": "448f76df", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -479,7 +545,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7b2643a6", + "id": "4d33b363", "metadata": {}, "outputs": [], "source": [ @@ -491,9 +557,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "0943588e", + "id": "b342d334", "metadata": {}, "source": [ "Initialize model and solver." @@ -502,7 +567,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ac88bdec", + "id": "48a946b3", "metadata": {}, "outputs": [], "source": [ @@ -515,7 +580,7 @@ " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", - " \"print_assembly\": False,\n", + " # \"print_assembly\": False,\n", " }\n", ")\n", "\n", @@ -559,9 +624,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "5d5aacbd", + "id": "fa275dec", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -570,13 +634,13 @@ { "cell_type": "code", "execution_count": null, - "id": "b54d28ca", + "id": "590ce5e1", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "result_folder = pathlib.Path(f\"results_ellipseinellipse\")\n", + "result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", @@ -589,7 +653,7 @@ "# Set loglevel to warning in order not to pollute notebook output\n", "# logger.setLevel(logging.WARNING)\n", "\n", - "concVec = np.array([model_cur.sc['Ca'].initial_condition])\n", + "concVec = np.array([model_cur.sc[\"Ca\"].initial_condition])\n", "tvec = np.array([0.0])\n", "# Solve\n", "displayed = False\n", @@ -599,16 +663,16 @@ " # Save results for post processing\n", " for species_name, species in model_cur.sc.items:\n", " results[species_name].write(model_cur.sc[species_name].u[\"u\"], model_cur.t)\n", - " cytoMesh = model_cur.cc['Cyto'].dolfin_mesh\n", + " cytoMesh = model_cur.cc[\"Cyto\"].dolfin_mesh\n", " dx = d.Measure(\"dx\", domain=cytoMesh)\n", - " int_val = d.assemble(model_cur.sc['Ca'].u['u']*dx)\n", - " volume = d.assemble(1.0*dx)\n", + " int_val = d.assemble(model_cur.sc[\"Ca\"].u[\"u\"] * dx)\n", + " volume = d.assemble(1.0 * dx)\n", " curConc = np.array([int_val / volume])\n", " concVec = np.concatenate((concVec, curConc))\n", " tvec = np.concatenate((tvec, np.array([float(model_cur.t)])))\n", - " np.savetxt(result_folder / f\"tvec.txt\", np.array(model_cur.tvec).astype(np.float32))\n", - " if model_cur.t > .025 and not displayed: # display first time after .025 s\n", - " visualization.plot(model_cur.sc['Ca'].u['u'])\n", + " np.savetxt(result_folder / \"tvec.txt\", np.array(model_cur.tvec).astype(np.float32))\n", + " if model_cur.t > 0.025 and not displayed: # display first time after .025 s\n", + " visualization.plot(model_cur.sc[\"Ca\"].u[\"u\"])\n", " displayed = True\n", " # End if we've passed the final time\n", " if model_cur.t >= model_cur.final_t:\n", @@ -616,9 +680,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "ead460cb", + "id": "8ff63890", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -627,19 +690,22 @@ { "cell_type": "code", "execution_count": null, - "id": "7d17ded6", - "metadata": {}, + "id": "a0dc9d3a", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ - "plt.plot(tvec, concVec)\n", - "plt.xlabel('Time (s)')\n", - "plt.ylabel('Cytosolic calcium (μM)')" + "fig, ax = plt.subplots()\n", + "ax.plot(tvec, concVec)\n", + "ax.set_xlabel(\"Time (s)\")\n", + "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", + "fig.savefig(\"ca2+-example.png\")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "713f0c16", + "id": "cbfa8954", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -648,7 +714,7 @@ { "cell_type": "code", "execution_count": null, - "id": "dc6f88f7", + "id": "acb51e12", "metadata": {}, "outputs": [], "source": [ @@ -662,28 +728,6 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" - }, - "kernelspec": { - "display_name": "Python 3", - "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.10.6" - }, - "vscode": { - "interpreter": { - "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" - } } }, "nbformat": 4, From 3ec39fb66e6e716e4e52aa7a3fbbec35153fb58d Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Mon, 30 Oct 2023 21:22:04 +0100 Subject: [PATCH 02/23] Make example run --- ca2+-examples/dendritic_spine.ipynb | 480 +++++++++++++++------------- 1 file changed, 263 insertions(+), 217 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index b8cd32e..dd08d9f 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -1,9 +1,8 @@ { "cells": [ { - "attachments": {}, "cell_type": "markdown", - "id": "f65f18d7", + "id": "78f7062b", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,8 +29,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "cc398816", + "execution_count": null, + "id": "a4e5b17d", "metadata": {}, "outputs": [], "source": [ @@ -40,7 +39,6 @@ "import numpy as np\n", "import pathlib\n", "import logging\n", - "import gmsh # must be imported before pyvista if dolfin is imported first\n", "\n", "from smart import config, mesh, model, mesh_tools, visualization\n", "from smart.units import unit\n", @@ -50,24 +48,19 @@ " Reaction,\n", " Species,\n", " SpeciesContainer,\n", - " ParameterContainer,\n", " CompartmentContainer,\n", - " ReactionContainer,\n", - " sbmodel_from_locals\n", + " sbmodel_from_locals,\n", ")\n", "\n", "from matplotlib import pyplot as plt\n", - "import matplotlib.image as mpimg\n", - "from matplotlib import rcParams\n", "\n", "logger = logging.getLogger(\"smart\")\n", "logger.setLevel(logging.DEBUG)" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "95b9d865", + "id": "c2802b1c", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -75,8 +68,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "4f4023cf", + "execution_count": null, + "id": "223427f5", "metadata": {}, "outputs": [], "source": [ @@ -99,7 +92,7 @@ }, { "cell_type": "markdown", - "id": "6e9f51db", + "id": "42cd87db", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -109,40 +102,23 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "c49bff44", + "execution_count": null, + "id": "4313d74e", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[36m\u001b[0m \u001b[97m2023-10-19 23:27:04,626 smart.mesh - INFO - HDF5 mesh, \"parent_mesh\", successfully loaded from file: ellipseSpine_mesh/ellipseSpine_mesh.h5! (mesh.py:220)\u001b[0m \u001b[36m\u001b[0m\n" - ] - }, - { - "ename": "NameError", - "evalue": "name 'z_PSD' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/root/shared/gitrepos/smart-comp-sci/ca2+-examples/dendritic_spine.ipynb Cell 6\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 34\u001b[0m integrateDomain \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39mMeshFunction(\u001b[39m\"\u001b[39m\u001b[39msize_t\u001b[39m\u001b[39m\"\u001b[39m, spine_mesh, \u001b[39m2\u001b[39m, \u001b[39m0\u001b[39m)\n\u001b[1;32m 35\u001b[0m \u001b[39mfor\u001b[39;00m f \u001b[39min\u001b[39;00m d\u001b[39m.\u001b[39mfacets(spine_mesh):\n\u001b[0;32m---> 36\u001b[0m integrateDomain[f] \u001b[39m=\u001b[39m \u001b[39m1\u001b[39m \u001b[39mif\u001b[39;00m (f\u001b[39m.\u001b[39mmidpoint()\u001b[39m.\u001b[39mz() \u001b[39m>\u001b[39m z_PSD) \u001b[39melse\u001b[39;00m \u001b[39m0\u001b[39m\n\u001b[1;32m 37\u001b[0m ds \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39mMeasure(\u001b[39m\"\u001b[39m\u001b[39mds\u001b[39m\u001b[39m\"\u001b[39m, domain\u001b[39m=\u001b[39mspine_mesh, subdomain_data\u001b[39m=\u001b[39mintegrateDomain)\n\u001b[1;32m 38\u001b[0m A_PSD \u001b[39m=\u001b[39m d\u001b[39m.\u001b[39massemble(\u001b[39m1.0\u001b[39m\u001b[39m*\u001b[39mds(\u001b[39m1\u001b[39m))\n", - "\u001b[0;31mNameError\u001b[0m: name 'z_PSD' is not defined" - ] - } - ], + "outputs": [], "source": [ - "# spine_rad = 0.237\n", - "# SA_rad = .08\n", - "# ar_list = [27,15,7]\n", - "# ar_1 = (1/((ar_list[1]/ar_list[0])*(ar_list[2]/ar_list[0])))**(1/3)\n", - "# ar_2 = ar_1 * ar_list[1]/ar_list[0]\n", - "# ar_3 = ar_1 * ar_list[2]/ar_list[0]\n", - "# z_PSD = 0.8 * spine_rad*ar_3\n", - "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids((spine_rad*ar_1,spine_rad*ar_2,spine_rad*ar_3), \n", - "# (SA_rad*ar_1, SA_rad*ar_2, SA_rad*ar_3), \n", - " # hEdge=0.02)\n", + "spine_rad = 0.237\n", + "SA_rad = 0.08\n", + "ar_list = [27, 15, 7]\n", + "ar_1 = (1 / ((ar_list[1] / ar_list[0]) * (ar_list[2] / ar_list[0]))) ** (1 / 3)\n", + "ar_2 = ar_1 * ar_list[1] / ar_list[0]\n", + "ar_3 = ar_1 * ar_list[2] / ar_list[0]\n", + "z_PSD = 0.8 * spine_rad * ar_3\n", + "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids(\n", + "# (spine_rad * ar_1, spine_rad * ar_2, spine_rad * ar_3),\n", + "# (SA_rad * ar_1, SA_rad * ar_2, SA_rad * ar_3),\n", + "# hEdge=0.02,\n", + "# )\n", "# Load mesh\n", "# spine_mesh = d.Mesh('spine_mesh.xml')\n", "cur_dir = pathlib.Path.cwd()\n", @@ -153,7 +129,7 @@ "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", "facet_array = facet_markers.array()[:]\n", "for i in range(len(facet_array)):\n", - " if facet_array[i] == 11: # this indicates PSD\n", + " if facet_array[i] == 11: # this indicates PSD\n", " facet_array[i] = 10\n", "\n", "mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", @@ -169,16 +145,16 @@ "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", "# for f in d.facets(spine_mesh):\n", "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", - "integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", + "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", + "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", - "A_PSD = d.assemble(1.0*ds(11))\n", + "A_PSD = d.assemble(1.0 * ds(11))\n", "visualization.plot_dolfin_mesh(spine_mesh, cell_markers)" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "46582d26", + "id": "a14c9ccf", "metadata": {}, "source": [ "## Model generation\n", @@ -191,7 +167,7 @@ { "cell_type": "code", "execution_count": null, - "id": "02a000f2", + "id": "aa879ee8", "metadata": {}, "outputs": [], "source": [ @@ -199,17 +175,16 @@ "PM = Compartment(\"PM\", 2, um, 10)\n", "SA = Compartment(\"SA\", 3, um, 2)\n", "SAm = Compartment(\"SAm\", 2, um, 12)\n", - "PM.specify_nonadjacency(['SAm', 'SA'])\n", - "SAm.specify_nonadjacency(['PM'])\n", + "PM.specify_nonadjacency([\"SAm\", \"SA\"])\n", + "SAm.specify_nonadjacency([\"PM\"])\n", "\n", "cc = CompartmentContainer()\n", "cc.add([Cyto, PM, SA, SAm])" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "f9121840", + "id": "f3265cbb", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -218,29 +193,34 @@ { "cell_type": "code", "execution_count": null, - "id": "09079b17", + "id": "f9c034f4", "metadata": {}, "outputs": [], "source": [ "Ca = Species(\"Ca\", 0.1, vol_unit, 220.0, D_unit, \"Cyto\")\n", - "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", + "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", "NMDAR_loc = f\"(1 + sign(z - {z_PSD}))/2\"\n", - "NMDAR = Species(\"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\") # specify species to localize NMDAR calcium influx to PSD\n", - "VSCC_zThresh = -10#0.3 #-0.25 for single spine, 0.3 for 2 spine\n", + "NMDAR = Species(\n", + " \"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # specify species to localize NMDAR calcium influx to PSD\n", + "VSCC_zThresh = -10 # 0.3 #-0.25 for single spine, 0.3 for 2 spine\n", "VSCC_loc = f\"(1 + sign(z - {VSCC_zThresh}))/2\"\n", - "VSCC = Species(\"VSCC\", VSCC_loc, dimensionless, 0.0, D_unit, \"PM\") # specify species to localize VSCC calcium influx to spine body and neck\n", + "VSCC = Species(\n", + " \"VSCC\", VSCC_loc, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # specify species to localize VSCC calcium influx to spine body and neck\n", "\n", - "Bf = Species(\"Bf\", 78.7*n_PMr, vol_unit*um, 0.0, D_unit, \"PM\")\n", + "Bf = Species(\"Bf\", 78.7 * n_PMr, vol_unit * um, 0.0, D_unit, \"PM\")\n", "Bm = Species(\"Bm\", 20.0, vol_unit, 20.0, D_unit, \"Cyto\")\n", - "CaSA = Species(\"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\") # effective D due to buffering\n", + "CaSA = Species(\n", + " \"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\"\n", + ") # effective D due to buffering\n", "sc = SpeciesContainer()\n", "sc.add([Ca, NMDAR, Bf, Bm, CaSA])" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "3c56e840", + "id": "1baa8cf6", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -261,7 +241,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2e1f6882", + "id": "36b02f4a", "metadata": {}, "outputs": [], "source": [ @@ -269,103 +249,171 @@ "Vrest_expr = -65\n", "Vrest = Parameter(\"Vrest\", Vrest_expr, voltage_unit)\n", "bpmax = 38\n", - "Ibsf, tbsf, Ibss, tbss = 0.75, 0.003, 0.25, 0.025 \n", + "Ibsf, tbsf, Ibss, tbss = 0.75, 0.003, 0.25, 0.025\n", "tdelaybp, tdelay = 0.002, 0.0\n", "sterm = 25\n", - "tep1, tep2 = 0.05, 0.005 \n", + "tep1, tep2 = 0.05, 0.005\n", "t = sym.symbols(\"t\")\n", - "BPAP = bpmax * (Ibsf * sym.exp(-(t-tdelaybp)/tbsf) +\n", - " Ibss * sym.exp(-(t-tdelaybp)/tbss)) * (1+sym.sign(t))/2\n", - "EPSP = sterm * (sym.exp(-(t-tdelay)/tep1) - sym.exp(-(t-tdelay)/tep2)) * (1+sym.sign(t))/2\n", + "BPAP = (\n", + " bpmax\n", + " * (Ibsf * sym.exp(-(t - tdelaybp) / tbsf) + Ibss * sym.exp(-(t - tdelaybp) / tbss))\n", + " * (1 + sym.sign(t))\n", + " / 2\n", + ")\n", + "EPSP = (\n", + " sterm\n", + " * (sym.exp(-(t - tdelay) / tep1) - sym.exp(-(t - tdelay) / tep2))\n", + " * (1 + sym.sign(t))\n", + " / 2\n", + ")\n", "Vm_expr = Vrest_expr + BPAP + EPSP\n", "Vm = Parameter.from_expression(\n", - " \"Vm\", Vm_expr, voltage_unit, use_preintegration=False,\n", - " )\n", + " \"Vm\",\n", + " Vm_expr,\n", + " voltage_unit,\n", + " use_preintegration=False,\n", + ")\n", "# Define known constants\n", - "N_A = 6.022e23 # molecules per mole\n", - "F = 96485.332 # Faraday's constant (Coulombs per mole)\n", - "Q = 1.602e-19 # Coulombs per elementary charge\n", + "N_A = 6.022e23 # molecules per mole\n", + "F = 96485.332 # Faraday's constant (Coulombs per mole)\n", + "Q = 1.602e-19 # Coulombs per elementary charge\n", "\n", "n_PMr = Parameter(\"n_PMr\", 0.1011, um)\n", "# NMDAR calcium influx\n", "P0 = 0.5\n", - "CaEC = 2 #mM\n", - "h = 11.3 #pS/mM\n", - "G0 = 65.6 #pS\n", + "CaEC = 2 # mM\n", + "h = 11.3 # pS/mM\n", + "G0 = 65.6 # pS\n", "r = 0.5\n", - "ginf = 15.2 #pS\n", - "convert_factor = 1e-15 # A/mV per pS\n", - "zeta_i = (G0 + r*CaEC*h)/(1 + r*CaEC*h / ginf) # single channel conductance in pS\n", - "G_NMDARVal = convert_factor*zeta_i / (2*Q)\n", - "G_NMDAR = Parameter(\"G_NMDAR\", G_NMDARVal, molecule/(voltage_unit*sec))\n", + "ginf = 15.2 # pS\n", + "convert_factor = 1e-15 # A/mV per pS\n", + "zeta_i = (G0 + r * CaEC * h) / (\n", + " 1 + r * CaEC * h / ginf\n", + ") # single channel conductance in pS\n", + "G_NMDARVal = convert_factor * zeta_i / (2 * Q)\n", + "G_NMDAR = Parameter(\"G_NMDAR\", G_NMDARVal, molecule / (voltage_unit * sec))\n", "If, tau_f, Is, tau_s = 0.5, 0.05, 0.5, 0.2\n", - "Km = 0.092 #(1/mV)\n", - "Mg, MgScale = 1, 3.57 #mM\n", - "B_V = 1 / (1 + sym.exp(-Km*Vm_expr*Mg/MgScale))\n", - "gamma_i_scale = P0 * (If*sym.exp(-t/tau_f) + Is*sym.exp(-t/tau_s))*B_V * 1#(1+sym.sign(t))/2\n", + "Km = 0.092 # (1/mV)\n", + "Mg, MgScale = 1, 3.57 # mM\n", + "B_V = 1 / (1 + sym.exp(-Km * Vm_expr * Mg / MgScale))\n", + "gamma_i_scale = (\n", + " P0 * (If * sym.exp(-t / tau_f) + Is * sym.exp(-t / tau_s)) * B_V * 1\n", + ") # (1+sym.sign(t))/2\n", "beta_NMDAR = 85\n", "# A_PSD = 2*np.pi*spine_rad*(spine_rad - z_PSD)\n", - "J0_NMDAR_expr = gamma_i_scale / (beta_NMDAR*A_PSD)\n", + "J0_NMDAR_expr = gamma_i_scale / (beta_NMDAR * A_PSD)\n", "J0_NMDAR = Parameter.from_expression(\n", - " \"J0_NMDAR\", J0_NMDAR_expr, 1/um**2, use_preintegration=False,\n", - " )\n", - "a1 = Reaction(\"a1\", [], [\"Ca\"], species_map={\"NMDAR\":\"NMDAR\"},\n", - " param_map={\"J0\": \"J0_NMDAR\", \"G_NMDAR\":\"G_NMDAR\", \"Vm\":\"Vm\", \"Vrest\":\"Vrest\"},\n", - " eqn_f_str=\"J0*NMDAR*G_NMDAR*(Vm - Vrest)\", explicit_restriction_to_domain=\"PM\")\n", - "#VSCC calcium influx\n", - "gamma = 3.72 #pS\n", - "k_Ca = -convert_factor * gamma * Vm_expr * N_A * (0.393 - sym.exp(-Vm_expr/80.36)) / (2*F*(1 - sym.exp(Vm_expr/80.36)))\n", + " \"J0_NMDAR\",\n", + " J0_NMDAR_expr,\n", + " 1 / um**2,\n", + " use_preintegration=False,\n", + ")\n", + "a1 = Reaction(\n", + " \"a1\",\n", + " [],\n", + " [\"Ca\"],\n", + " species_map={\"NMDAR\": \"NMDAR\"},\n", + " param_map={\"J0\": \"J0_NMDAR\", \"G_NMDAR\": \"G_NMDAR\", \"Vm\": \"Vm\", \"Vrest\": \"Vrest\"},\n", + " eqn_f_str=\"J0*NMDAR*G_NMDAR*(Vm - Vrest)\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# VSCC calcium influx\n", + "gamma = 3.72 # pS\n", + "k_Ca = (\n", + " -convert_factor\n", + " * gamma\n", + " * Vm_expr\n", + " * N_A\n", + " * (0.393 - sym.exp(-Vm_expr / 80.36))\n", + " / (2 * F * (1 - sym.exp(Vm_expr / 80.36)))\n", + ")\n", "alpha4, beta4 = 34700, 3680\n", - "VSCC_biexp = (sym.exp(-alpha4*t) - sym.exp(-beta4*t)) * (1 + sym.sign(t))/2\n", - "VSCCNum = 2 # molecules/um^2\n", + "VSCC_biexp = (sym.exp(-alpha4 * t) - sym.exp(-beta4 * t)) * (1 + sym.sign(t)) / 2\n", + "VSCCNum = 2 # molecules/um^2\n", "J_VSCC = Parameter.from_expression(\n", - " \"J_VSCC\", VSCCNum*k_Ca*VSCC_biexp, molecule/(um**2 * sec), use_preintegration=False,\n", - " )\n", - "a2 = Reaction(\"a2\", [], [\"Ca\"], species_map={\"VSCC\":\"VSCC\"}, param_map={\"J\": \"J_VSCC\"},\n", - " eqn_f_str=\"J*VSCC\", explicit_restriction_to_domain=\"PM\")\n", - "#PMCA\n", + " \"J_VSCC\",\n", + " VSCCNum * k_Ca * VSCC_biexp,\n", + " molecule / (um**2 * sec),\n", + " use_preintegration=False,\n", + ")\n", + "a2 = Reaction(\n", + " \"a2\",\n", + " [],\n", + " [\"Ca\"],\n", + " species_map={\"VSCC\": \"VSCC\"},\n", + " param_map={\"J\": \"J_VSCC\"},\n", + " eqn_f_str=\"J*VSCC\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# PMCA\n", "Prtote = Parameter(\"Prtote\", 191, vol_unit)\n", "Kme = Parameter(\"Kme\", 2.43, vol_unit)\n", "Kmx = Parameter(\"Kmx\", 0.139, vol_unit)\n", - "Vmax_lr23 = Parameter(\"Vmax_lr23\", 0.113, vol_unit/sec)\n", + "Vmax_lr23 = Parameter(\"Vmax_lr23\", 0.113, vol_unit / sec)\n", "Km_lr23 = Parameter(\"Km_lr23\", 0.442, vol_unit)\n", - "Vmax_hr23 = Parameter(\"Vmax_hr23\", 0.59, vol_unit/sec)\n", + "Vmax_hr23 = Parameter(\"Vmax_hr23\", 0.59, vol_unit / sec)\n", "Km_hr23 = Parameter(\"Km_hr23\", 0.442, vol_unit)\n", "beta_PMCA = 100\n", - "beta_i_str = \"(1 + Prtote*Kme/(Kme+c)**2 + Prtote*Kmx/(Kmx+c)**2)**(-1)\" # buffering term\n", + "beta_i_str = (\n", + " \"(1 + Prtote*Kme/(Kme+c)**2 + Prtote*Kmx/(Kmx+c)**2)**(-1)\" # buffering term\n", + ")\n", "PMCA_str = \"Vmax_lr23*c**2/(Km_lr23**2 + c**2) + Vmax_hr23*c**5/(Km_hr23**5 + c**5)\"\n", - "a3 = Reaction(\"a3\", [\"Ca\"], [],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_lr23\":\"Vmax_lr23\", \"Km_lr23\":\"Km_lr23\",\n", - " \"Vmax_hr23\":\"Vmax_hr23\", \"Km_hr23\":\"Km_hr23\", \"n_PMr\":\"n_PMr\"},\n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_PMCA}*({beta_i_str})*({PMCA_str})*n_PMr\",\n", - " explicit_restriction_to_domain=\"PM\")\n", - "#NCX\n", - "Vmax_r22 = Parameter(\"Vmax_r22\", 0.1, vol_unit/sec)\n", - "Km_r22 = Parameter(\"Km_r22\", 1, vol_unit) #uM\n", + "a3 = Reaction(\n", + " \"a3\",\n", + " [\"Ca\"],\n", + " [],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_lr23\": \"Vmax_lr23\",\n", + " \"Km_lr23\": \"Km_lr23\",\n", + " \"Vmax_hr23\": \"Vmax_hr23\",\n", + " \"Km_hr23\": \"Km_hr23\",\n", + " \"n_PMr\": \"n_PMr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_PMCA}*({beta_i_str})*({PMCA_str})*n_PMr\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# NCX\n", + "Vmax_r22 = Parameter(\"Vmax_r22\", 0.1, vol_unit / sec)\n", + "Km_r22 = Parameter(\"Km_r22\", 1, vol_unit) # uM\n", "beta_NCX = 1000\n", "NCX_str = \"Vmax_r22*c/(Km_r22 + c)\"\n", - "a4 = Reaction(\"a4\", [\"Ca\"], [],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_r22\":\"Vmax_r22\", \"Km_r22\":\"Km_r22\", \"n_PMr\":\"n_PMr\"}, \n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_NCX}*({beta_i_str})*({NCX_str})*n_PMr\",\n", - " explicit_restriction_to_domain=\"PM\")\n", - "#Immobilized buffers\n", - "kBf_on = Parameter(\"kBf_on\", 1, 1/(uM*sec))\n", - "kBf_off = Parameter(\"kBf_off\", 2, 1/sec)\n", - "Bf_tot = Parameter(\"Bf_tot\", 78.7*n_PMr.value, vol_unit*um)\n", - "a5 = Reaction(\"a5\",[\"Ca\", \"Bf\"], [], \n", - " {\"kon\":\"kBf_on\",\"koff\":\"kBf_off\",\"Bf_tot\":\"Bf_tot\"},\n", - " eqn_f_str=\"kon*Ca*Bf - koff*(Bf_tot - Bf)\",\n", - " explicit_restriction_to_domain=\"PM\")" + "a4 = Reaction(\n", + " \"a4\",\n", + " [\"Ca\"],\n", + " [],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_r22\": \"Vmax_r22\",\n", + " \"Km_r22\": \"Km_r22\",\n", + " \"n_PMr\": \"n_PMr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_NCX}*({beta_i_str})*({NCX_str})*n_PMr\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")\n", + "# Immobilized buffers\n", + "kBf_on = Parameter(\"kBf_on\", 1, 1 / (uM * sec))\n", + "kBf_off = Parameter(\"kBf_off\", 2, 1 / sec)\n", + "Bf_tot = Parameter(\"Bf_tot\", 78.7 * n_PMr.value, vol_unit * um)\n", + "a5 = Reaction(\n", + " \"a5\",\n", + " [\"Ca\", \"Bf\"],\n", + " [],\n", + " {\"kon\": \"kBf_on\", \"koff\": \"kBf_off\", \"Bf_tot\": \"Bf_tot\"},\n", + " eqn_f_str=\"kon*Ca*Bf - koff*(Bf_tot - Bf)\",\n", + " explicit_restriction_to_domain=\"PM\",\n", + ")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "901cc4f9", + "id": "95266776", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -374,36 +422,35 @@ { "cell_type": "code", "execution_count": null, - "id": "e7aa0975", + "id": "3151827b", "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "from sympy.utilities.lambdify import lambdify\n", - "Vm_func = lambdify(t, Vm_expr, 'numpy') # returns a numpy-ready function\n", + "\n", + "Vm_func = lambdify(t, Vm_expr, \"numpy\") # returns a numpy-ready function\n", "tArray = np.linspace(-0.01, 0.05, 600)\n", "fig, ax = plt.subplots(3, 1)\n", "fig.set_size_inches(15, 10)\n", "ax[0].plot(tArray, Vm_func(tArray))\n", - "ax[0].set(xlabel='Time (s)',\n", - " ylabel='Membrane voltage\\n(mV)')\n", + "ax[0].set(xlabel=\"Time (s)\", ylabel=\"Membrane voltage\\n(mV)\")\n", "\n", - "NMDAR_func = lambdify(t, A_PSD*J0_NMDAR_expr*G_NMDARVal*(Vm_expr - Vrest_expr), 'numpy')\n", + "NMDAR_func = lambdify(\n", + " t, A_PSD * J0_NMDAR_expr * G_NMDARVal * (Vm_expr - Vrest_expr), \"numpy\"\n", + ")\n", "ax[1].plot(tArray, NMDAR_func(tArray))\n", - "ax[1].set(xlabel='Time (s)',\n", - " ylabel='NMDAR flux\\n(molecules/(um^2*s))')\n", + "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/(um^2*s))\")\n", "\n", - "VSCC_func = lambdify(t, .8057*VSCCNum*k_Ca*VSCC_biexp, 'numpy')\n", + "VSCC_func = lambdify(t, 0.8057 * VSCCNum * k_Ca * VSCC_biexp, \"numpy\")\n", "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", - "ax[2].set(xlabel='Time (s)',\n", - " ylabel='VSCC flux\\n(molecules/(um^2*s))')" + "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "ab82d24a", + "id": "0ea208f1", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -412,22 +459,26 @@ { "cell_type": "code", "execution_count": null, - "id": "f8111aff", + "id": "4cb92100", "metadata": {}, "outputs": [], "source": [ "# calcium buffering in the cytosol\n", - "kBm_on = Parameter(\"kBm_on\", 1, 1/(uM*sec))\n", - "kBm_off = Parameter(\"kBm_off\", 1, 1/sec)\n", + "kBm_on = Parameter(\"kBm_on\", 1, 1 / (uM * sec))\n", + "kBm_off = Parameter(\"kBm_off\", 1, 1 / sec)\n", "Bm_tot = Parameter(\"Bm_tot\", 20, vol_unit)\n", - "b1 = Reaction(\"b1\", [\"Ca\",\"Bm\"], [], param_map={\"kon\":\"kBm_on\",\"koff\":\"kBm_off\",\"Bm_tot\":\"Bm_tot\"},\n", - " eqn_f_str=\"kon*Ca*Bm - koff*(Bm_tot - Bm)\")" + "b1 = Reaction(\n", + " \"b1\",\n", + " [\"Ca\", \"Bm\"],\n", + " [],\n", + " param_map={\"kon\": \"kBm_on\", \"koff\": \"kBm_off\", \"Bm_tot\": \"Bm_tot\"},\n", + " eqn_f_str=\"kon*Ca*Bm - koff*(Bm_tot - Bm)\",\n", + ")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "08a028d1", + "id": "f89984db", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -438,39 +489,53 @@ { "cell_type": "code", "execution_count": null, - "id": "40fede1d", + "id": "e2f549c0", "metadata": {}, "outputs": [], "source": [ "# SERCA flux\n", - "n_SAr = Parameter(\"n_SAr\", .0113, um)\n", - "Vmax_r19 = Parameter(\"Vmax_r19\", 113, vol_unit/sec)\n", + "n_SAr = Parameter(\"n_SAr\", 0.0113, um)\n", + "Vmax_r19 = Parameter(\"Vmax_r19\", 113, vol_unit / sec)\n", "KP_r19 = Parameter(\"KP_r19\", 0.2, vol_unit)\n", "beta_SERCA = 1000\n", "VmaxSERCA_str = \"Vmax_r19*c**2/(KP_r19**2 + c**2)\"\n", - "c1 = Reaction(\"c1\", [\"Ca\"], [\"CaSA\"],\n", - " {\"Prtote\":\"Prtote\", \"Kme\":\"Kme\", \"Kmx\":\"Kmx\",\n", - " \"Vmax_r19\":\"Vmax_r19\", \"KP_r19\":\"KP_r19\", \"n_SAr\":\"n_SAr\"}, \n", - " {\"c\": \"Ca\"},\n", - " eqn_f_str=f\"{beta_SERCA}*({beta_i_str})*({VmaxSERCA_str})*n_SAr\",\n", - " explicit_restriction_to_domain=\"SAm\")\n", + "c1 = Reaction(\n", + " \"c1\",\n", + " [\"Ca\"],\n", + " [\"CaSA\"],\n", + " {\n", + " \"Prtote\": \"Prtote\",\n", + " \"Kme\": \"Kme\",\n", + " \"Kmx\": \"Kmx\",\n", + " \"Vmax_r19\": \"Vmax_r19\",\n", + " \"KP_r19\": \"KP_r19\",\n", + " \"n_SAr\": \"n_SAr\",\n", + " },\n", + " {\"c\": \"Ca\"},\n", + " eqn_f_str=f\"{beta_SERCA}*({beta_i_str})*({VmaxSERCA_str})*n_SAr\",\n", + " explicit_restriction_to_domain=\"SAm\",\n", + ")\n", "# calcium leak out of the SA\n", - "k_leak = Parameter(\"k_leak\", 0.1608, 1/sec)\n", - "c2 = Reaction(\"c2\", [\"CaSA\"], [\"Ca\"], {\"k_leak\":\"k_leak\", \"n_SAr\":\"n_SAr\"}, \n", - " {\"c\":\"Ca\", \"cSA\":\"CaSA\"},\n", - " eqn_f_str=f\"k_leak*(cSA - c)*n_SAr\",\n", - " explicit_restriction_to_domain=\"SAm\")\n", + "k_leak = Parameter(\"k_leak\", 0.1608, 1 / sec)\n", + "c2 = Reaction(\n", + " \"c2\",\n", + " [\"CaSA\"],\n", + " [\"Ca\"],\n", + " {\"k_leak\": \"k_leak\", \"n_SAr\": \"n_SAr\"},\n", + " {\"c\": \"Ca\", \"cSA\": \"CaSA\"},\n", + " eqn_f_str=\"k_leak*(cSA - c)*n_SAr\",\n", + " explicit_restriction_to_domain=\"SAm\",\n", + ")\n", "\n", - "xi = 0.0227272727 # scaling factor to account for rapid buffering in SA\n", + "xi = 0.0227272727 # scaling factor to account for rapid buffering in SA\n", "for c in [c1, c2]:\n", - " c.flux_scaling = {'CaSA': xi}\n", + " c.flux_scaling = {\"CaSA\": xi}\n", " c.__post_init__()" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "e4edeebc", + "id": "448f76df", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -479,7 +544,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7b2643a6", + "id": "4d33b363", "metadata": {}, "outputs": [], "source": [ @@ -491,9 +556,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "0943588e", + "id": "b342d334", "metadata": {}, "source": [ "Initialize model and solver." @@ -502,7 +566,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ac88bdec", + "id": "48a946b3", "metadata": {}, "outputs": [], "source": [ @@ -515,7 +579,7 @@ " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", - " \"print_assembly\": False,\n", + " # \"print_assembly\": False,\n", " }\n", ")\n", "\n", @@ -559,9 +623,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "5d5aacbd", + "id": "fa275dec", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -570,13 +633,13 @@ { "cell_type": "code", "execution_count": null, - "id": "b54d28ca", + "id": "590ce5e1", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "result_folder = pathlib.Path(f\"results_ellipseinellipse\")\n", + "result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", @@ -589,7 +652,7 @@ "# Set loglevel to warning in order not to pollute notebook output\n", "# logger.setLevel(logging.WARNING)\n", "\n", - "concVec = np.array([model_cur.sc['Ca'].initial_condition])\n", + "concVec = np.array([model_cur.sc[\"Ca\"].initial_condition])\n", "tvec = np.array([0.0])\n", "# Solve\n", "displayed = False\n", @@ -599,16 +662,16 @@ " # Save results for post processing\n", " for species_name, species in model_cur.sc.items:\n", " results[species_name].write(model_cur.sc[species_name].u[\"u\"], model_cur.t)\n", - " cytoMesh = model_cur.cc['Cyto'].dolfin_mesh\n", + " cytoMesh = model_cur.cc[\"Cyto\"].dolfin_mesh\n", " dx = d.Measure(\"dx\", domain=cytoMesh)\n", - " int_val = d.assemble(model_cur.sc['Ca'].u['u']*dx)\n", - " volume = d.assemble(1.0*dx)\n", + " int_val = d.assemble(model_cur.sc[\"Ca\"].u[\"u\"] * dx)\n", + " volume = d.assemble(1.0 * dx)\n", " curConc = np.array([int_val / volume])\n", " concVec = np.concatenate((concVec, curConc))\n", " tvec = np.concatenate((tvec, np.array([float(model_cur.t)])))\n", - " np.savetxt(result_folder / f\"tvec.txt\", np.array(model_cur.tvec).astype(np.float32))\n", - " if model_cur.t > .025 and not displayed: # display first time after .025 s\n", - " visualization.plot(model_cur.sc['Ca'].u['u'])\n", + " np.savetxt(result_folder / \"tvec.txt\", np.array(model_cur.tvec).astype(np.float32))\n", + " if model_cur.t > 0.025 and not displayed: # display first time after .025 s\n", + " visualization.plot(model_cur.sc[\"Ca\"].u[\"u\"])\n", " displayed = True\n", " # End if we've passed the final time\n", " if model_cur.t >= model_cur.final_t:\n", @@ -616,9 +679,8 @@ ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "ead460cb", + "id": "8ff63890", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -627,19 +689,22 @@ { "cell_type": "code", "execution_count": null, - "id": "7d17ded6", - "metadata": {}, + "id": "a0dc9d3a", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ - "plt.plot(tvec, concVec)\n", - "plt.xlabel('Time (s)')\n", - "plt.ylabel('Cytosolic calcium (μM)')" + "fig, ax = plt.subplots()\n", + "ax.plot(tvec, concVec)\n", + "ax.set_xlabel(\"Time (s)\")\n", + "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", + "fig.savefig(\"ca2+-example.png\")" ] }, { - "attachments": {}, "cell_type": "markdown", - "id": "713f0c16", + "id": "cbfa8954", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -648,7 +713,7 @@ { "cell_type": "code", "execution_count": null, - "id": "dc6f88f7", + "id": "acb51e12", "metadata": {}, "outputs": [], "source": [ @@ -663,27 +728,8 @@ "main_language": "python", "notebook_metadata_filter": "-all" }, - "kernelspec": { - "display_name": "Python 3", - "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.10.6" - }, - "vscode": { - "interpreter": { - "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" - } + "name": "python" } }, "nbformat": 4, From cb0632b7fa213188b163a6b723b2f41cf0f5c610 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Mon, 30 Oct 2023 22:50:58 +0100 Subject: [PATCH 03/23] Add requirements --- requirements.in | 6 +++ requirements.txt | 118 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 requirements.in create mode 100644 requirements.txt diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..5b31401 --- /dev/null +++ b/requirements.in @@ -0,0 +1,6 @@ +fenics-smart +matplotlib +jupytext +sympy==1.4 # Pin version to whatever is installed on the cluster +numpy==1.21.5 # Pin version to whatever is installed on the cluster +mpmath==1.1.0 # Pin version to whatever is installed on the cluster \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6c60b17 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,118 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile requirements.in +# +attrs==23.1.0 + # via + # jsonschema + # referencing +cached-property==1.5.2 + # via fenics-smart +contourpy==1.1.1 + # via matplotlib +cycler==0.12.1 + # via matplotlib +dataclasses==0.6 + # via fenics-smart +fastjsonschema==2.18.1 + # via nbformat +fenics-smart==2.1.7 + # via -r requirements.in +fonttools==4.43.1 + # via matplotlib +importlib-resources==6.1.0 + # via + # jsonschema + # jsonschema-specifications + # matplotlib +jsonschema==4.19.2 + # via nbformat +jsonschema-specifications==2023.7.1 + # via jsonschema +jupyter-core==5.5.0 + # via nbformat +jupytext==1.15.2 + # via -r requirements.in +kiwisolver==1.4.5 + # via matplotlib +markdown-it-py==3.0.0 + # via + # jupytext + # mdit-py-plugins +matplotlib==3.7.3 + # via -r requirements.in +mdit-py-plugins==0.4.0 + # via jupytext +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.1.0 + # via + # -r requirements.in + # sympy +nbformat==5.9.2 + # via jupytext +numpy==1.21.5 + # via + # -r requirements.in + # contourpy + # fenics-smart + # matplotlib + # pandas + # scipy + # termplotlib +packaging==23.2 + # via matplotlib +pandas==2.0.3 + # via fenics-smart +pillow==10.1.0 + # via matplotlib +pint==0.21.1 + # via fenics-smart +pkgutil-resolve-name==1.3.10 + # via jsonschema +platformdirs==3.11.0 + # via jupyter-core +pyparsing==3.1.1 + # via matplotlib +python-dateutil==2.8.2 + # via + # matplotlib + # pandas +pytz==2023.3.post1 + # via pandas +pyyaml==6.0.1 + # via jupytext +referencing==0.30.2 + # via + # jsonschema + # jsonschema-specifications +rpds-py==0.10.6 + # via + # jsonschema + # referencing +scipy==1.10.1 + # via fenics-smart +six==1.16.0 + # via python-dateutil +sympy==1.4 + # via + # -r requirements.in + # fenics-smart +tabulate==0.9.0 + # via fenics-smart +termcolor==2.3.0 + # via fenics-smart +termplotlib==0.3.9 + # via fenics-smart +toml==0.10.2 + # via jupytext +traitlets==5.13.0 + # via + # jupyter-core + # nbformat +tzdata==2023.3 + # via pandas +zipp==3.17.0 + # via importlib-resources From 600421624f933bd235805352448a9195cec79156 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Mon, 30 Oct 2023 22:54:36 +0100 Subject: [PATCH 04/23] Add requirements and submit scripts --- ex3_scripts/README.md | 32 +++++++++++++++++++ ex3_scripts/dendritic_spine.sbatch | 27 ++++++++++++++++ .../requirements.in | 0 .../requirements.txt | 0 4 files changed, 59 insertions(+) create mode 100644 ex3_scripts/README.md create mode 100644 ex3_scripts/dendritic_spine.sbatch rename requirements.in => ex3_scripts/requirements.in (100%) rename requirements.txt => ex3_scripts/requirements.txt (100%) diff --git a/ex3_scripts/README.md b/ex3_scripts/README.md new file mode 100644 index 0000000..c5318aa --- /dev/null +++ b/ex3_scripts/README.md @@ -0,0 +1,32 @@ +# ex3 + + +## Setup environment +Before submitting any scripts we need to set up the environment on the cluster. First we load the installed modules +``` +module use /cm/shared/ex3-modules/202309a/defq/modulefiles +module load python-fenics-dolfin-2019.2.0.dev0 +``` +Next we create a python virtual environment in the root of the repo. +``` +python3 -m venv venv +``` +We activate the virtual environment +``` +. venv/bin/activate +``` +and install the rest of the dependencies that are not already installed +``` +python3 -m pip install -r requirements.txt +``` + +## Convert notebooks to python files +In order to run the scripts on the cluster we need to first convert the notebooks to python files. To do this we will use `jupytext` which is part of the requirements. For example you can use +``` +jupytext ca2+-examples/dendritic_spine.ipynb --to py +``` + +## Submit job +``` +sbatch dendritic_spine.sbatch +``` \ No newline at end of file diff --git a/ex3_scripts/dendritic_spine.sbatch b/ex3_scripts/dendritic_spine.sbatch new file mode 100644 index 0000000..d42213b --- /dev/null +++ b/ex3_scripts/dendritic_spine.sbatch @@ -0,0 +1,27 @@ +#!/bin/bash +#SBATCH --job-name="dendritic_spine" +#SBATCH --partition=defq +#SBATCH --time=3-00:00:00 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --output=%j-%x-stdout.txt +#SBATCH --error=%j-%x-stderr.txt + + +module use /cm/shared/ex3-modules/202309a/defq/modulefiles +module load python-fenics-dolfin-2019.2.0.dev0 + +LIBRARY_ROOT=$(realpath $(pwd)/..) +EXAMPLEDIR=$LIBRARY_ROOT/ca2+-examples +export OMPI_MCA_btl='^ofi' # James told me to set this to remove a warning + +SCRATCH_DIRECTORY=/global/D1/homes/${USER}/smart-comp-sci/dendritic_spine/${SLURM_JOBID} +mkdir -p ${SCRATCH_DIRECTORY} +echo "Scratch directory: ${SCRATCH_DIRECTORY}" +export RESULTSDIR=$SCRATCH_DIRECTORY + +echo "Run command: ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py" +# Assuming that you installed the software in a virutal environment in ${LIBRARY_ROOT}/venv +RESULTSDIR=$SCRATCH_DIRECTORY EXAMPLEDIR=$EXAMPLEDIR ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py +cp slurm-output/${SLURM_JOBID}-* ${SCRATCH_DIRECTORY} diff --git a/requirements.in b/ex3_scripts/requirements.in similarity index 100% rename from requirements.in rename to ex3_scripts/requirements.in diff --git a/requirements.txt b/ex3_scripts/requirements.txt similarity index 100% rename from requirements.txt rename to ex3_scripts/requirements.txt From 47e19fdbc03eb562e9a25d4004d8d12ab7081d6e Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Mon, 30 Oct 2023 22:55:23 +0100 Subject: [PATCH 05/23] Make it possible to set paths via environment variables --- ca2+-examples/dendritic_spine.ipynb | 79 ++++++++++++++++------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 883531a..8529607 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "78f7062b", + "id": "5bbbd516", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,13 +30,14 @@ { "cell_type": "code", "execution_count": null, - "id": "a4e5b17d", + "id": "523c4a6c", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", + "import os\n", "import pathlib\n", "import logging\n", "\n", @@ -60,7 +61,7 @@ }, { "cell_type": "markdown", - "id": "c2802b1c", + "id": "2b0060c9", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -69,7 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "223427f5", + "id": "da87c8ec", "metadata": {}, "outputs": [], "source": [ @@ -92,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "42cd87db", + "id": "6f783d43", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -103,7 +104,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4313d74e", + "id": "b8fcccfb", "metadata": {}, "outputs": [], "source": [ @@ -121,7 +122,10 @@ "# )\n", "# Load mesh\n", "# spine_mesh = d.Mesh('spine_mesh.xml')\n", - "cur_dir = pathlib.Path.cwd()\n", + "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " cur_dir = pathlib.Path(example_dir)\n", + "else:\n", + " cur_dir = pathlib.Path.cwd()\n", "parent_dir = cur_dir.parent\n", "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", @@ -132,7 +136,10 @@ " if facet_array[i] == 11: # this indicates PSD\n", " facet_array[i] = 10\n", "\n", - "mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", + "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " mesh_folder = pathlib.Path(folder)\n", + "else:\n", + " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", "mesh_folder.mkdir(exist_ok=True)\n", "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", "mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", @@ -145,7 +152,7 @@ "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", "# for f in d.facets(spine_mesh):\n", "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", - "# breakpoint()\n", + "\n", "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", @@ -155,7 +162,7 @@ }, { "cell_type": "markdown", - "id": "a14c9ccf", + "id": "93daec55", "metadata": {}, "source": [ "## Model generation\n", @@ -168,7 +175,7 @@ { "cell_type": "code", "execution_count": null, - "id": "aa879ee8", + "id": "5c29b10c", "metadata": {}, "outputs": [], "source": [ @@ -185,7 +192,7 @@ }, { "cell_type": "markdown", - "id": "f3265cbb", + "id": "558c62f4", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -194,7 +201,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f9c034f4", + "id": "8925c9f8", "metadata": {}, "outputs": [], "source": [ @@ -221,7 +228,7 @@ }, { "cell_type": "markdown", - "id": "1baa8cf6", + "id": "7a51ec00", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -242,7 +249,7 @@ { "cell_type": "code", "execution_count": null, - "id": "36b02f4a", + "id": "47f7cd5d", "metadata": {}, "outputs": [], "source": [ @@ -414,7 +421,7 @@ }, { "cell_type": "markdown", - "id": "95266776", + "id": "c2856179", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -423,7 +430,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3151827b", + "id": "b9b6210e", "metadata": {}, "outputs": [], "source": [ @@ -451,7 +458,7 @@ }, { "cell_type": "markdown", - "id": "0ea208f1", + "id": "4150cd47", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -460,7 +467,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4cb92100", + "id": "e3c65f96", "metadata": {}, "outputs": [], "source": [ @@ -479,7 +486,7 @@ }, { "cell_type": "markdown", - "id": "f89984db", + "id": "924a6d4c", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -490,7 +497,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e2f549c0", + "id": "6e1e1b75", "metadata": {}, "outputs": [], "source": [ @@ -536,7 +543,7 @@ }, { "cell_type": "markdown", - "id": "448f76df", + "id": "2e2a6abf", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -545,7 +552,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4d33b363", + "id": "ecf43a39", "metadata": {}, "outputs": [], "source": [ @@ -558,7 +565,7 @@ }, { "cell_type": "markdown", - "id": "b342d334", + "id": "3e1b823d", "metadata": {}, "source": [ "Initialize model and solver." @@ -567,7 +574,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48a946b3", + "id": "cd72ddba", "metadata": {}, "outputs": [], "source": [ @@ -576,7 +583,8 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " \"final_t\": 0.025,\n", + " # \"final_t\": 0.025,\n", + " \"final_t\": 0.0004,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", @@ -625,7 +633,7 @@ }, { "cell_type": "markdown", - "id": "fa275dec", + "id": "5bfa8aab", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -634,13 +642,16 @@ { "cell_type": "code", "execution_count": null, - "id": "590ce5e1", + "id": "59326232", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", + "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", + " result_folder = pathlib.Path(folder)\n", + "else:\n", + " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", @@ -648,7 +659,7 @@ " )\n", " results[species_name].parameters[\"flush_output\"] = True\n", " results[species_name].write(model_cur.sc[species_name].u[\"u\"], model_cur.t)\n", - "model_cur.to_pickle(\"model_cur.pkl\")\n", + "model_cur.to_pickle(result_folder / \"model_cur.pkl\")\n", "\n", "# Set loglevel to warning in order not to pollute notebook output\n", "# logger.setLevel(logging.WARNING)\n", @@ -681,7 +692,7 @@ }, { "cell_type": "markdown", - "id": "8ff63890", + "id": "26d20b8e", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -690,7 +701,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a0dc9d3a", + "id": "f3f72653", "metadata": { "lines_to_next_cell": 2 }, @@ -705,7 +716,7 @@ }, { "cell_type": "markdown", - "id": "cbfa8954", + "id": "37f4dc69", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -714,7 +725,7 @@ { "cell_type": "code", "execution_count": null, - "id": "acb51e12", + "id": "4b60e952", "metadata": {}, "outputs": [], "source": [ From 9d17affa921df21a5acd2a2a4fc0873a64d0c803 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Mon, 30 Oct 2023 23:06:12 +0100 Subject: [PATCH 06/23] Attempt to fix notebook formatting --- ca2+-examples/dendritic_spine.ipynb | 90 +++++++++++------------------ 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 7ce83f5..8630e61 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "5bbbd516", + "id": "b7cc0493", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,14 +30,13 @@ { "cell_type": "code", "execution_count": null, - "id": "523c4a6c", + "id": "107f3d6e", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", - "import os\n", "import pathlib\n", "import logging\n", "\n", @@ -61,7 +60,7 @@ }, { "cell_type": "markdown", - "id": "2b0060c9", + "id": "7147e820", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -70,7 +69,7 @@ { "cell_type": "code", "execution_count": null, - "id": "da87c8ec", + "id": "b02e2d0a", "metadata": {}, "outputs": [], "source": [ @@ -93,11 +92,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "id": "6f783d43", -======= - "id": "42cd87db", ->>>>>>> finsberg/fix-ca2+-example + "id": "45bdb41c", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -108,7 +103,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b8fcccfb", + "id": "d9ba942e", "metadata": {}, "outputs": [], "source": [ @@ -126,10 +121,7 @@ "# )\n", "# Load mesh\n", "# spine_mesh = d.Mesh('spine_mesh.xml')\n", - "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " cur_dir = pathlib.Path(example_dir)\n", - "else:\n", - " cur_dir = pathlib.Path.cwd()\n", + "cur_dir = pathlib.Path.cwd()\n", "parent_dir = cur_dir.parent\n", "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", @@ -140,10 +132,7 @@ " if facet_array[i] == 11: # this indicates PSD\n", " facet_array[i] = 10\n", "\n", - "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " mesh_folder = pathlib.Path(folder)\n", - "else:\n", - " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", + "mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", "mesh_folder.mkdir(exist_ok=True)\n", "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", "mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", @@ -156,7 +145,6 @@ "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", "# for f in d.facets(spine_mesh):\n", "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", - "\n", "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", @@ -166,7 +154,7 @@ }, { "cell_type": "markdown", - "id": "93daec55", + "id": "22bff93c", "metadata": {}, "source": [ "## Model generation\n", @@ -179,7 +167,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5c29b10c", + "id": "586c88f0", "metadata": {}, "outputs": [], "source": [ @@ -196,7 +184,7 @@ }, { "cell_type": "markdown", - "id": "558c62f4", + "id": "17043959", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -205,7 +193,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8925c9f8", + "id": "a2ffbf94", "metadata": {}, "outputs": [], "source": [ @@ -232,7 +220,7 @@ }, { "cell_type": "markdown", - "id": "7a51ec00", + "id": "a12f8ae6", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -253,7 +241,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47f7cd5d", + "id": "a5258615", "metadata": {}, "outputs": [], "source": [ @@ -425,7 +413,7 @@ }, { "cell_type": "markdown", - "id": "c2856179", + "id": "dd3abae6", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -434,7 +422,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b9b6210e", + "id": "01ba29af", "metadata": {}, "outputs": [], "source": [ @@ -462,7 +450,7 @@ }, { "cell_type": "markdown", - "id": "4150cd47", + "id": "abb7c63e", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -471,7 +459,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e3c65f96", + "id": "367e604f", "metadata": {}, "outputs": [], "source": [ @@ -490,7 +478,7 @@ }, { "cell_type": "markdown", - "id": "924a6d4c", + "id": "495ddd82", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -501,7 +489,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6e1e1b75", + "id": "f1b9f1e0", "metadata": {}, "outputs": [], "source": [ @@ -547,11 +535,7 @@ }, { "cell_type": "markdown", -<<<<<<< HEAD - "id": "2e2a6abf", -======= - "id": "448f76df", ->>>>>>> finsberg/fix-ca2+-example + "id": "dc0a8999", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -560,7 +544,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ecf43a39", + "id": "e38dff4e", "metadata": {}, "outputs": [], "source": [ @@ -573,7 +557,7 @@ }, { "cell_type": "markdown", - "id": "3e1b823d", + "id": "5f8fbfef", "metadata": {}, "source": [ "Initialize model and solver." @@ -582,7 +566,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cd72ddba", + "id": "6cfc4074", "metadata": {}, "outputs": [], "source": [ @@ -591,8 +575,7 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " # \"final_t\": 0.025,\n", - " \"final_t\": 0.0004,\n", + " \"final_t\": 0.025,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", @@ -641,7 +624,7 @@ }, { "cell_type": "markdown", - "id": "5bfa8aab", + "id": "5208cfb4", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -650,16 +633,13 @@ { "cell_type": "code", "execution_count": null, - "id": "59326232", + "id": "f8d10562", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", - " result_folder = pathlib.Path(folder)\n", - "else:\n", - " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", + "result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", @@ -667,7 +647,7 @@ " )\n", " results[species_name].parameters[\"flush_output\"] = True\n", " results[species_name].write(model_cur.sc[species_name].u[\"u\"], model_cur.t)\n", - "model_cur.to_pickle(result_folder / \"model_cur.pkl\")\n", + "model_cur.to_pickle(\"model_cur.pkl\")\n", "\n", "# Set loglevel to warning in order not to pollute notebook output\n", "# logger.setLevel(logging.WARNING)\n", @@ -700,7 +680,7 @@ }, { "cell_type": "markdown", - "id": "26d20b8e", + "id": "a36ae548", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -709,10 +689,8 @@ { "cell_type": "code", "execution_count": null, - "id": "f3f72653", - "metadata": { - "lines_to_next_cell": 2 - }, + "id": "2ba71ff6", + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -724,7 +702,7 @@ }, { "cell_type": "markdown", - "id": "37f4dc69", + "id": "d56af89f", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -733,7 +711,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4b60e952", + "id": "17caf929", "metadata": {}, "outputs": [], "source": [ From 841e9602ee629704933d91a07e71c0f962f93438 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Tue, 31 Oct 2023 09:46:23 +0100 Subject: [PATCH 07/23] Don't write mesh on HPC cluster --- ca2+-examples/dendritic_spine.ipynb | 86 +++++++++++++++++------------ 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 8630e61..22f0a23 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "b7cc0493", + "id": "cbd41fc0", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,13 +30,14 @@ { "cell_type": "code", "execution_count": null, - "id": "107f3d6e", + "id": "66911a87", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", + "import os\n", "import pathlib\n", "import logging\n", "\n", @@ -60,7 +61,7 @@ }, { "cell_type": "markdown", - "id": "7147e820", + "id": "2b4814cf", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -69,7 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b02e2d0a", + "id": "55ed6093", "metadata": {}, "outputs": [], "source": [ @@ -92,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "45bdb41c", + "id": "f9eaace8", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -103,7 +104,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d9ba942e", + "id": "6abd069f", "metadata": {}, "outputs": [], "source": [ @@ -121,7 +122,10 @@ "# )\n", "# Load mesh\n", "# spine_mesh = d.Mesh('spine_mesh.xml')\n", - "cur_dir = pathlib.Path.cwd()\n", + "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " cur_dir = pathlib.Path(example_dir)\n", + "else:\n", + " cur_dir = pathlib.Path.cwd()\n", "parent_dir = cur_dir.parent\n", "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", @@ -132,10 +136,15 @@ " if facet_array[i] == 11: # this indicates PSD\n", " facet_array[i] = 10\n", "\n", - "mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", + "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " mesh_folder = pathlib.Path(folder)\n", + "else:\n", + " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", "mesh_folder.mkdir(exist_ok=True)\n", "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", - "mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", + "if not bool(int(os.getenv(\"HPC\", 0))):\n", + " # Don't write mesh on HPC cluster\n", + " mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", "\n", "parent_mesh = mesh.ParentMesh(\n", " mesh_filename=str(mesh_file),\n", @@ -145,6 +154,7 @@ "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", "# for f in d.facets(spine_mesh):\n", "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", + "\n", "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", @@ -154,7 +164,7 @@ }, { "cell_type": "markdown", - "id": "22bff93c", + "id": "f02669bd", "metadata": {}, "source": [ "## Model generation\n", @@ -167,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "586c88f0", + "id": "c8680f80", "metadata": {}, "outputs": [], "source": [ @@ -184,7 +194,7 @@ }, { "cell_type": "markdown", - "id": "17043959", + "id": "e37c6dc3", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -193,7 +203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a2ffbf94", + "id": "87f3cfc3", "metadata": {}, "outputs": [], "source": [ @@ -220,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "a12f8ae6", + "id": "15d6b355", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -241,7 +251,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a5258615", + "id": "f8b640f5", "metadata": {}, "outputs": [], "source": [ @@ -413,7 +423,7 @@ }, { "cell_type": "markdown", - "id": "dd3abae6", + "id": "b8f7c0c5", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -422,7 +432,7 @@ { "cell_type": "code", "execution_count": null, - "id": "01ba29af", + "id": "82d7f8b0", "metadata": {}, "outputs": [], "source": [ @@ -450,7 +460,7 @@ }, { "cell_type": "markdown", - "id": "abb7c63e", + "id": "be051a74", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -459,7 +469,7 @@ { "cell_type": "code", "execution_count": null, - "id": "367e604f", + "id": "41ef9044", "metadata": {}, "outputs": [], "source": [ @@ -478,7 +488,7 @@ }, { "cell_type": "markdown", - "id": "495ddd82", + "id": "8038a9d1", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -489,7 +499,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f1b9f1e0", + "id": "7fce2c23", "metadata": {}, "outputs": [], "source": [ @@ -535,7 +545,7 @@ }, { "cell_type": "markdown", - "id": "dc0a8999", + "id": "70faced0", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -544,7 +554,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e38dff4e", + "id": "ae8b9dcf", "metadata": {}, "outputs": [], "source": [ @@ -557,7 +567,7 @@ }, { "cell_type": "markdown", - "id": "5f8fbfef", + "id": "f6f495df", "metadata": {}, "source": [ "Initialize model and solver." @@ -566,7 +576,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6cfc4074", + "id": "011da642", "metadata": {}, "outputs": [], "source": [ @@ -575,7 +585,8 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " \"final_t\": 0.025,\n", + " # \"final_t\": 0.025,\n", + " \"final_t\": 0.0004,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", @@ -624,7 +635,7 @@ }, { "cell_type": "markdown", - "id": "5208cfb4", + "id": "34c0e12e", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -633,13 +644,16 @@ { "cell_type": "code", "execution_count": null, - "id": "f8d10562", + "id": "0b0c28c0", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", + "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", + " result_folder = pathlib.Path(folder)\n", + "else:\n", + " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", @@ -647,7 +661,7 @@ " )\n", " results[species_name].parameters[\"flush_output\"] = True\n", " results[species_name].write(model_cur.sc[species_name].u[\"u\"], model_cur.t)\n", - "model_cur.to_pickle(\"model_cur.pkl\")\n", + "model_cur.to_pickle(result_folder / \"model_cur.pkl\")\n", "\n", "# Set loglevel to warning in order not to pollute notebook output\n", "# logger.setLevel(logging.WARNING)\n", @@ -680,7 +694,7 @@ }, { "cell_type": "markdown", - "id": "a36ae548", + "id": "b5912ed4", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -689,8 +703,10 @@ { "cell_type": "code", "execution_count": null, - "id": "2ba71ff6", - "metadata": {}, + "id": "732d52cc", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -702,7 +718,7 @@ }, { "cell_type": "markdown", - "id": "d56af89f", + "id": "6bc82d01", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -711,7 +727,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17caf929", + "id": "fcb2a968", "metadata": {}, "outputs": [], "source": [ From 7320d91ea485b597ceddc9439ac52e2dce434793 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Tue, 31 Oct 2023 09:59:10 +0100 Subject: [PATCH 08/23] Save both figures to result folder --- ca2+-examples/dendritic_spine.ipynb | 92 ++++++++++++++++++----------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 22f0a23..3c0d73a 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "cbd41fc0", + "id": "d8cd864d", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66911a87", + "id": "851b1c7c", "metadata": {}, "outputs": [], "source": [ @@ -61,7 +61,7 @@ }, { "cell_type": "markdown", - "id": "2b4814cf", + "id": "e67e364d", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -70,7 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55ed6093", + "id": "408b5953", "metadata": {}, "outputs": [], "source": [ @@ -93,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "f9eaace8", + "id": "fe94cae8", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -104,7 +104,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6abd069f", + "id": "575cb11d", "metadata": {}, "outputs": [], "source": [ @@ -164,7 +164,7 @@ }, { "cell_type": "markdown", - "id": "f02669bd", + "id": "c83b76de", "metadata": {}, "source": [ "## Model generation\n", @@ -177,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c8680f80", + "id": "fb3973fb", "metadata": {}, "outputs": [], "source": [ @@ -194,7 +194,7 @@ }, { "cell_type": "markdown", - "id": "e37c6dc3", + "id": "de8fe1e8", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -203,7 +203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "87f3cfc3", + "id": "85a49a76", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "15d6b355", + "id": "f8f8d98c", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -251,8 +251,10 @@ { "cell_type": "code", "execution_count": null, - "id": "f8b640f5", - "metadata": {}, + "id": "d72e5e92", + "metadata": { + "lines_to_next_cell": 0 + }, "outputs": [], "source": [ "# Both NMDAR and VSCC fluxes depend on the voltage over time\n", @@ -423,7 +425,29 @@ }, { "cell_type": "markdown", - "id": "b8f7c0c5", + "id": "369b5679", + "metadata": {}, + "source": [ + "Create a results folder" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f659ac3d", + "metadata": {}, + "outputs": [], + "source": [ + "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", + " result_folder = pathlib.Path(folder)\n", + "else:\n", + " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", + "result_folder.mkdir(exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "d40d9890", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -432,7 +456,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82d7f8b0", + "id": "ffd53302", "metadata": {}, "outputs": [], "source": [ @@ -455,12 +479,13 @@ "VSCC_func = lambdify(t, 0.8057 * VSCCNum * k_Ca * VSCC_biexp, \"numpy\")\n", "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", - "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")" + "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")\n", + "fig.savefig(results_folder / \"time_dependent.png\")" ] }, { "cell_type": "markdown", - "id": "be051a74", + "id": "6afcbe64", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -469,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41ef9044", + "id": "764017d8", "metadata": {}, "outputs": [], "source": [ @@ -488,7 +513,7 @@ }, { "cell_type": "markdown", - "id": "8038a9d1", + "id": "446e4b0d", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -499,7 +524,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7fce2c23", + "id": "443b03db", "metadata": {}, "outputs": [], "source": [ @@ -545,7 +570,7 @@ }, { "cell_type": "markdown", - "id": "70faced0", + "id": "6fd06bbd", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -554,7 +579,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ae8b9dcf", + "id": "93b28876", "metadata": {}, "outputs": [], "source": [ @@ -567,7 +592,7 @@ }, { "cell_type": "markdown", - "id": "f6f495df", + "id": "3eb6fdee", "metadata": {}, "source": [ "Initialize model and solver." @@ -576,7 +601,7 @@ { "cell_type": "code", "execution_count": null, - "id": "011da642", + "id": "78071aee", "metadata": {}, "outputs": [], "source": [ @@ -635,7 +660,7 @@ }, { "cell_type": "markdown", - "id": "34c0e12e", + "id": "9caca72d", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -644,17 +669,12 @@ { "cell_type": "code", "execution_count": null, - "id": "0b0c28c0", + "id": "bffb2e2d", "metadata": {}, "outputs": [], "source": [ "# Write initial condition(s) to file\n", "results = dict()\n", - "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", - " result_folder = pathlib.Path(folder)\n", - "else:\n", - " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", - "result_folder.mkdir(exist_ok=True)\n", "for species_name, species in model_cur.sc.items:\n", " results[species_name] = d.XDMFFile(\n", " model_cur.mpi_comm_world, str(result_folder / f\"{species_name}.xdmf\")\n", @@ -694,7 +714,7 @@ }, { "cell_type": "markdown", - "id": "b5912ed4", + "id": "960c0608", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -703,7 +723,7 @@ { "cell_type": "code", "execution_count": null, - "id": "732d52cc", + "id": "ba817505", "metadata": { "lines_to_next_cell": 2 }, @@ -713,12 +733,12 @@ "ax.plot(tvec, concVec)\n", "ax.set_xlabel(\"Time (s)\")\n", "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", - "fig.savefig(\"ca2+-example.png\")" + "fig.savefig(result_folder / \"ca2+-example.png\")" ] }, { "cell_type": "markdown", - "id": "6bc82d01", + "id": "bda270f0", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -727,7 +747,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fcb2a968", + "id": "b4ff9cb3", "metadata": {}, "outputs": [], "source": [ From 84b0fa84ab7688ce69889e90d7abb1390003b21f Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 31 Oct 2023 11:34:02 +0100 Subject: [PATCH 09/23] Fix typo --- ca2+-examples/dendritic_spine.ipynb | 62 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 3c0d73a..2941afd 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "d8cd864d", + "id": "6df8e49d", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "851b1c7c", + "id": "b926dcb7", "metadata": {}, "outputs": [], "source": [ @@ -61,7 +61,7 @@ }, { "cell_type": "markdown", - "id": "e67e364d", + "id": "86f2fbcc", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -70,7 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "408b5953", + "id": "2d9abff2", "metadata": {}, "outputs": [], "source": [ @@ -93,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "fe94cae8", + "id": "022739d3", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -104,7 +104,7 @@ { "cell_type": "code", "execution_count": null, - "id": "575cb11d", + "id": "9011a051", "metadata": {}, "outputs": [], "source": [ @@ -164,7 +164,7 @@ }, { "cell_type": "markdown", - "id": "c83b76de", + "id": "4feed263", "metadata": {}, "source": [ "## Model generation\n", @@ -177,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fb3973fb", + "id": "24e397ab", "metadata": {}, "outputs": [], "source": [ @@ -194,7 +194,7 @@ }, { "cell_type": "markdown", - "id": "de8fe1e8", + "id": "7d619a26", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -203,7 +203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85a49a76", + "id": "c27bb7ea", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "f8f8d98c", + "id": "619ef5bc", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -251,7 +251,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d72e5e92", + "id": "3028830c", "metadata": { "lines_to_next_cell": 0 }, @@ -425,7 +425,7 @@ }, { "cell_type": "markdown", - "id": "369b5679", + "id": "6d5d77e5", "metadata": {}, "source": [ "Create a results folder" @@ -434,7 +434,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f659ac3d", + "id": "f3430b1a", "metadata": {}, "outputs": [], "source": [ @@ -447,7 +447,7 @@ }, { "cell_type": "markdown", - "id": "d40d9890", + "id": "3dba6744", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -456,7 +456,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ffd53302", + "id": "4e7f935b", "metadata": {}, "outputs": [], "source": [ @@ -480,12 +480,12 @@ "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")\n", - "fig.savefig(results_folder / \"time_dependent.png\")" + "fig.savefig(result_folder / \"time_dependent.png\")" ] }, { "cell_type": "markdown", - "id": "6afcbe64", + "id": "a6d9b28e", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -494,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "764017d8", + "id": "108b0f11", "metadata": {}, "outputs": [], "source": [ @@ -513,7 +513,7 @@ }, { "cell_type": "markdown", - "id": "446e4b0d", + "id": "b26e147b", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -524,7 +524,7 @@ { "cell_type": "code", "execution_count": null, - "id": "443b03db", + "id": "77de9984", "metadata": {}, "outputs": [], "source": [ @@ -570,7 +570,7 @@ }, { "cell_type": "markdown", - "id": "6fd06bbd", + "id": "f8a73788", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -579,7 +579,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93b28876", + "id": "bb0772ac", "metadata": {}, "outputs": [], "source": [ @@ -592,7 +592,7 @@ }, { "cell_type": "markdown", - "id": "3eb6fdee", + "id": "dc98238a", "metadata": {}, "source": [ "Initialize model and solver." @@ -601,7 +601,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78071aee", + "id": "8d3c0369", "metadata": {}, "outputs": [], "source": [ @@ -660,7 +660,7 @@ }, { "cell_type": "markdown", - "id": "9caca72d", + "id": "e424e954", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -669,7 +669,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bffb2e2d", + "id": "786af961", "metadata": {}, "outputs": [], "source": [ @@ -714,7 +714,7 @@ }, { "cell_type": "markdown", - "id": "960c0608", + "id": "73d335ce", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -723,7 +723,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ba817505", + "id": "7d4076dd", "metadata": { "lines_to_next_cell": 2 }, @@ -738,7 +738,7 @@ }, { "cell_type": "markdown", - "id": "bda270f0", + "id": "64e0bb33", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -747,7 +747,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b4ff9cb3", + "id": "29872774", "metadata": {}, "outputs": [], "source": [ From 44d07e1e0264e90b390277f29380b22b7a113485 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 31 Oct 2023 11:34:31 +0100 Subject: [PATCH 10/23] Add HPC flag to submit script --- ex3_scripts/dendritic_spine.sbatch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ex3_scripts/dendritic_spine.sbatch b/ex3_scripts/dendritic_spine.sbatch index d42213b..0a40f02 100644 --- a/ex3_scripts/dendritic_spine.sbatch +++ b/ex3_scripts/dendritic_spine.sbatch @@ -23,5 +23,6 @@ export RESULTSDIR=$SCRATCH_DIRECTORY echo "Run command: ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py" # Assuming that you installed the software in a virutal environment in ${LIBRARY_ROOT}/venv -RESULTSDIR=$SCRATCH_DIRECTORY EXAMPLEDIR=$EXAMPLEDIR ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py -cp slurm-output/${SLURM_JOBID}-* ${SCRATCH_DIRECTORY} +RESULTSDIR=$SCRATCH_DIRECTORY EXAMPLEDIR=$EXAMPLEDIR HPC=1 ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py +# Move log file +mv ${SLURM_JOBID}-* ${SCRATCH_DIRECTORY} From 69ad36c37899f68e18e42ab1beffb9098f0458ca Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 31 Oct 2023 12:51:28 +0100 Subject: [PATCH 11/23] Dump config to results folder --- ca2+-examples/dendritic_spine.ipynb | 74 ++++++++++++++++------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 2941afd..a2f56d5 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "6df8e49d", + "id": "5cd0d151", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b926dcb7", + "id": "79eefb82", "metadata": {}, "outputs": [], "source": [ @@ -61,7 +61,7 @@ }, { "cell_type": "markdown", - "id": "86f2fbcc", + "id": "6771377d", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -70,7 +70,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2d9abff2", + "id": "339351eb", "metadata": {}, "outputs": [], "source": [ @@ -93,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "022739d3", + "id": "f3296c0c", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -104,7 +104,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9011a051", + "id": "44b17db3", "metadata": {}, "outputs": [], "source": [ @@ -164,7 +164,7 @@ }, { "cell_type": "markdown", - "id": "4feed263", + "id": "a2db85a6", "metadata": {}, "source": [ "## Model generation\n", @@ -177,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24e397ab", + "id": "f2cf4a74", "metadata": {}, "outputs": [], "source": [ @@ -194,7 +194,7 @@ }, { "cell_type": "markdown", - "id": "7d619a26", + "id": "60a20c91", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -203,7 +203,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c27bb7ea", + "id": "a3508657", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "619ef5bc", + "id": "c40442b2", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -251,7 +251,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3028830c", + "id": "c411c91c", "metadata": { "lines_to_next_cell": 0 }, @@ -425,7 +425,7 @@ }, { "cell_type": "markdown", - "id": "6d5d77e5", + "id": "a9d39ce7", "metadata": {}, "source": [ "Create a results folder" @@ -434,7 +434,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f3430b1a", + "id": "981d5ea2", "metadata": {}, "outputs": [], "source": [ @@ -447,7 +447,7 @@ }, { "cell_type": "markdown", - "id": "3dba6744", + "id": "b7bdb34d", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -456,7 +456,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4e7f935b", + "id": "c7bacfbe", "metadata": {}, "outputs": [], "source": [ @@ -485,7 +485,7 @@ }, { "cell_type": "markdown", - "id": "a6d9b28e", + "id": "17e44d95", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -494,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "108b0f11", + "id": "3987aad8", "metadata": {}, "outputs": [], "source": [ @@ -513,7 +513,7 @@ }, { "cell_type": "markdown", - "id": "b26e147b", + "id": "eeb2451e", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -524,7 +524,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77de9984", + "id": "c1156fe4", "metadata": {}, "outputs": [], "source": [ @@ -570,7 +570,7 @@ }, { "cell_type": "markdown", - "id": "f8a73788", + "id": "41ea5a82", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -579,7 +579,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bb0772ac", + "id": "bf02840a", "metadata": {}, "outputs": [], "source": [ @@ -592,7 +592,7 @@ }, { "cell_type": "markdown", - "id": "dc98238a", + "id": "ceb17b9c", "metadata": {}, "source": [ "Initialize model and solver." @@ -601,7 +601,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8d3c0369", + "id": "3c46f8be", "metadata": {}, "outputs": [], "source": [ @@ -610,14 +610,24 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " # \"final_t\": 0.025,\n", - " \"final_t\": 0.0004,\n", + " \"final_t\": 0.025,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", " # \"print_assembly\": False,\n", " }\n", ")\n", + "import json\n", + "# Dump config to results folder\n", + "(result_folder / \"config.json\").write_text(\n", + " json.dumps(\n", + " {\n", + " \"solver\": configCur.solver.__dict__,\n", + " \"reaction_database\": configCur.reaction_database,\n", + " }\n", + " )\n", + ")\n", + "\n", "\n", "model_cur.initialize(initialize_solver=False)\n", "\n", @@ -660,7 +670,7 @@ }, { "cell_type": "markdown", - "id": "e424e954", + "id": "6df287da", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -669,7 +679,7 @@ { "cell_type": "code", "execution_count": null, - "id": "786af961", + "id": "ab63f3e7", "metadata": {}, "outputs": [], "source": [ @@ -714,7 +724,7 @@ }, { "cell_type": "markdown", - "id": "73d335ce", + "id": "207c41b2", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -723,7 +733,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7d4076dd", + "id": "9cf59fdd", "metadata": { "lines_to_next_cell": 2 }, @@ -738,7 +748,7 @@ }, { "cell_type": "markdown", - "id": "64e0bb33", + "id": "385e2983", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -747,7 +757,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29872774", + "id": "5fce2daa", "metadata": {}, "outputs": [], "source": [ From 7ff1288f91f1e30aa997e0644f21bc5c8e1bbb56 Mon Sep 17 00:00:00 2001 From: emmetfrancis Date: Wed, 1 Nov 2023 16:53:16 -0700 Subject: [PATCH 12/23] clean up dendritic_spine model file and uncomment code for NMDAR localization --- ca2+-examples/dendritic_spine.ipynb | 175 ++++++++---------- .../results_ellipseinellipse/config.json | 1 + .../results_ellipseinellipse/model_cur.pkl | Bin 0 -> 7781 bytes .../time_dependent.png | Bin 0 -> 74724 bytes .../results_ellipseinellipse/tvec.txt | 62 +++++++ meshes/spine_mesh.xml | 3 - 6 files changed, 141 insertions(+), 100 deletions(-) create mode 100644 ca2+-examples/results_ellipseinellipse/config.json create mode 100644 ca2+-examples/results_ellipseinellipse/model_cur.pkl create mode 100644 ca2+-examples/results_ellipseinellipse/time_dependent.png create mode 100644 ca2+-examples/results_ellipseinellipse/tvec.txt delete mode 100644 meshes/spine_mesh.xml diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index a2f56d5..b49d54c 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -40,6 +40,7 @@ "import os\n", "import pathlib\n", "import logging\n", + "from sympy.utilities.lambdify import lambdify\n", "\n", "from smart import config, mesh, model, mesh_tools, visualization\n", "from smart.units import unit\n", @@ -108,20 +109,6 @@ "metadata": {}, "outputs": [], "source": [ - "spine_rad = 0.237\n", - "SA_rad = 0.08\n", - "ar_list = [27, 15, 7]\n", - "ar_1 = (1 / ((ar_list[1] / ar_list[0]) * (ar_list[2] / ar_list[0]))) ** (1 / 3)\n", - "ar_2 = ar_1 * ar_list[1] / ar_list[0]\n", - "ar_3 = ar_1 * ar_list[2] / ar_list[0]\n", - "z_PSD = 0.8 * spine_rad * ar_3\n", - "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids(\n", - "# (spine_rad * ar_1, spine_rad * ar_2, spine_rad * ar_3),\n", - "# (SA_rad * ar_1, SA_rad * ar_2, SA_rad * ar_3),\n", - "# hEdge=0.02,\n", - "# )\n", - "# Load mesh\n", - "# spine_mesh = d.Mesh('spine_mesh.xml')\n", "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", " cur_dir = pathlib.Path(example_dir)\n", "else:\n", @@ -133,7 +120,7 @@ "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", "facet_array = facet_markers.array()[:]\n", "for i in range(len(facet_array)):\n", - " if facet_array[i] == 11: # this indicates PSD\n", + " if facet_array[i] == 11: # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", " facet_array[i] = 10\n", "\n", "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", @@ -151,11 +138,7 @@ " mesh_filetype=\"hdf5\",\n", " name=\"parent_mesh\",\n", ")\n", - "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", - "# for f in d.facets(spine_mesh):\n", - "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", "\n", - "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", "integrateDomain = facet_markers_orig\n", "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", @@ -186,10 +169,7 @@ "SA = Compartment(\"SA\", 3, um, 2)\n", "SAm = Compartment(\"SAm\", 2, um, 12)\n", "PM.specify_nonadjacency([\"SAm\", \"SA\"])\n", - "SAm.specify_nonadjacency([\"PM\"])\n", - "\n", - "cc = CompartmentContainer()\n", - "cc.add([Cyto, PM, SA, SAm])" + "SAm.specify_nonadjacency([\"PM\"])" ] }, { @@ -209,10 +189,9 @@ "source": [ "Ca = Species(\"Ca\", 0.1, vol_unit, 220.0, D_unit, \"Cyto\")\n", "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", - "NMDAR_loc = f\"(1 + sign(z - {z_PSD}))/2\"\n", "NMDAR = Species(\n", - " \"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\"\n", - ") # specify species to localize NMDAR calcium influx to PSD\n", + " \"NMDAR\", 0.0, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # initial condition will be overwritten to localize NMDAR to PSD\n", "VSCC_zThresh = -10 # 0.3 #-0.25 for single spine, 0.3 for 2 spine\n", "VSCC_loc = f\"(1 + sign(z - {VSCC_zThresh}))/2\"\n", "VSCC = Species(\n", @@ -223,9 +202,7 @@ "Bm = Species(\"Bm\", 20.0, vol_unit, 20.0, D_unit, \"Cyto\")\n", "CaSA = Species(\n", " \"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\"\n", - ") # effective D due to buffering\n", - "sc = SpeciesContainer()\n", - "sc.add([Ca, NMDAR, Bf, Bm, CaSA])" + ") # effective D due to buffering" ] }, { @@ -441,7 +418,7 @@ "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", " result_folder = pathlib.Path(folder)\n", "else:\n", - " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", + " result_folder = pathlib.Path(\"results_2spines\")\n", "result_folder.mkdir(exist_ok=True)" ] }, @@ -460,9 +437,6 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "from sympy.utilities.lambdify import lambdify\n", - "\n", "Vm_func = lambdify(t, Vm_expr, \"numpy\") # returns a numpy-ready function\n", "tArray = np.linspace(-0.01, 0.05, 600)\n", "fig, ax = plt.subplots(3, 1)\n", @@ -474,12 +448,12 @@ " t, A_PSD * J0_NMDAR_expr * G_NMDARVal * (Vm_expr - Vrest_expr), \"numpy\"\n", ")\n", "ax[1].plot(tArray, NMDAR_func(tArray))\n", - "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/(um^2*s))\")\n", + "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/s)\")\n", "\n", "VSCC_func = lambdify(t, 0.8057 * VSCCNum * k_Ca * VSCC_biexp, \"numpy\")\n", "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", - "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")\n", + "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/s)\")\n", "fig.savefig(result_folder / \"time_dependent.png\")" ] }, @@ -583,10 +557,6 @@ "metadata": {}, "outputs": [], "source": [ - "# pc =ParameterContainer()\n", - "# pc.add([n_PMr])\n", - "# rc = ReactionContainer()\n", - "# rc.add([a1, a2, a3, a4, a5, b1, c1, c2])\n", "pc, sc, cc, rc = sbmodel_from_locals(locals().values())" ] }, @@ -614,7 +584,6 @@ " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", - " # \"print_assembly\": False,\n", " }\n", ")\n", "import json\n", @@ -626,44 +595,58 @@ " \"reaction_database\": configCur.reaction_database,\n", " }\n", " )\n", - ")\n", - "\n", - "\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "0ad1eda0", + "metadata": {}, + "source": [ + "Initialize model and set initial condition vector for NMDAR (set to 1.0 in the PSD). This is currently a very slow, inefficient method - we iterate through the mesh and save the coordinates originally tagged with marker=11 (these belong to the PSD). The associated entries of the dolfin vector for NMDAR are then set to 1.0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee9010f3", + "metadata": {}, + "outputs": [], + "source": [ "model_cur.initialize(initialize_solver=False)\n", + "sp = model_cur.sc[\"NMDAR\"]\n", + "u = model_cur.cc[sp.compartment_name].u[\"u\"]\n", + "indices = sp.dof_map\n", + "uvec = u.vector()\n", + "values = uvec.get_local()\n", + "# store coordinates for current compartment\n", + "dof_coord = model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1,3))\n", + "dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", + "vertex_coords = [] # save all vertex coordinates that need to be set to 1.0 (in PSD)\n", + "idx = 0\n", + "for facet in d.facets(spine_mesh):\n", + " cur_marker = facet_markers_orig.array()[idx]\n", + " if cur_marker == 11:\n", + " for vertex in d.vertices(facet):\n", + " cur_vertex = list(vertex.point().array())\n", + " if len(vertex_coords)==0: # first time in this loop\n", + " vertex_coords.append(cur_vertex)\n", + " else:\n", + " compare_vertices = np.sum((np.array(vertex_coords)-np.array(cur_vertex))**2,1)\n", + " if not any(np.isclose(compare_vertices,0)): # unique point not seen yet\n", + " vertex_coords.append(cur_vertex)\n", + " idx = idx + 1\n", + "for i in range(len(dof_coord)):\n", + " compare_vertices = np.sum((np.array(vertex_coords)-dof_coord[i,:])**2,1)\n", + " if np.any(np.isclose(compare_vertices, 0)):\n", + " values[indices[i]] = 1.0\n", + " print(f\"Completed value {i+1} of {len(dof_coord)}\")\n", "\n", - "# # set initial condition vector for NMDAR\n", - "# sp = model_cur.sc['NMDAR']\n", - "# u = model_cur.cc[sp.compartment_name].u[\"u\"]\n", - "# indices = sp.dof_map\n", - "# uvec = u.vector()\n", - "# values = uvec.get_local()\n", - "# dof_coord = model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1,3)) # coordinates for current compartment\n", - "# dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", - "# vertex_coords = [] # save all vertex coordinates that need to be corrected\n", - "# idx = 0\n", - "# for facet in d.facets(spine_mesh):\n", - "# cur_marker = facet_markers_orig.array()[idx]\n", - "# if cur_marker == 10:\n", - "# for vertex in d.vertices(facet):\n", - "# cur_vertex = list(vertex.point().array())\n", - "# if len(vertex_coords)==0:\n", - "# vertex_coords.append(cur_vertex)\n", - "# else:\n", - "# compare_vertices = np.sum((np.array(vertex_coords)-np.array(cur_vertex))**2,1)\n", - "# if not any(np.isclose(compare_vertices,0)): # unique point not seen yet\n", - "# vertex_coords.append(cur_vertex)\n", - "# idx = idx + 1\n", - "# for i in range(len(dof_coord)):\n", - "# compare_vertices = np.sum((np.array(vertex_coords)-dof_coord[i,:])**2,1)\n", - "# if np.any(np.isclose(compare_vertices, 0)):\n", - "# values[indices[i]] = 0\n", - "# print(f'Completed value {i} of {len(dof_coord)}')\n", - "\n", - "# uvec.set_local(values)\n", - "# uvec.apply(\"insert\")\n", - "# nvec = model_cur.cc[sp.compartment_name].u[\"n\"].vector()\n", - "# nvec.set_local(values)\n", - "# nvec.apply(\"insert\")\n", + "uvec.set_local(values)\n", + "uvec.apply(\"insert\")\n", + "nvec = model_cur.cc[sp.compartment_name].u[\"n\"].vector()\n", + "nvec.set_local(values)\n", + "nvec.apply(\"insert\")\n", "\n", "model_cur.initialize_discrete_variational_problem_and_solver()" ] @@ -694,7 +677,7 @@ "model_cur.to_pickle(result_folder / \"model_cur.pkl\")\n", "\n", "# Set loglevel to warning in order not to pollute notebook output\n", - "# logger.setLevel(logging.WARNING)\n", + "logger.setLevel(logging.WARNING)\n", "\n", "concVec = np.array([model_cur.sc[\"Ca\"].initial_condition])\n", "tvec = np.array([0.0])\n", @@ -727,7 +710,7 @@ "id": "207c41b2", "metadata": {}, "source": [ - "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." + "Plot calcium over time for this model." ] }, { @@ -745,25 +728,6 @@ "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", "fig.savefig(result_folder / \"ca2+-example.png\")" ] - }, - { - "cell_type": "markdown", - "id": "385e2983", - "metadata": {}, - "source": [ - "Calculate area under the curve (AUC)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5fce2daa", - "metadata": {}, - "outputs": [], - "source": [ - "auc_cur = np.trapz(concVec, tvec)\n", - "print(auc_cur)" - ] } ], "metadata": { @@ -771,6 +735,23 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "Python 3", + "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.10.6" } }, "nbformat": 4, diff --git a/ca2+-examples/results_ellipseinellipse/config.json b/ca2+-examples/results_ellipseinellipse/config.json new file mode 100644 index 0000000..a5e6122 --- /dev/null +++ b/ca2+-examples/results_ellipseinellipse/config.json @@ -0,0 +1 @@ +{"solver": {"final_t": 0.025, "use_snes": true, "snes_preassemble_linear_system": false, "initial_dt": 0.0002, "adjust_dt": null, "time_precision": 8, "attempt_timestep_restart_on_divergence": false, "reset_timestep_for_negative_solution": false}, "reaction_database": {"prescribed": "k", "prescribed_linear": "k*u"}} \ No newline at end of file diff --git a/ca2+-examples/results_ellipseinellipse/model_cur.pkl b/ca2+-examples/results_ellipseinellipse/model_cur.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9ab10795d677403ebb0cdfd8a8e4e1e26afdff37 GIT binary patch literal 7781 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%0A~;1Fok)P{!$GOktltw zvmcG5d=IAILp=CZt?x%7ep37fUDY2tU%TE<8^mmfUKNP8a29%# zb{G+@@17?u1vR#RzF{^ydpY9H3q0F4ywP%c!z3Am>K!oE2D|EL=C}bWmyGS+>&3*> zX#GYdweyjWg7rvSu&`y9x`tJghi{H;|JTE4%Eor1}h(}!?L{yJ0GVumyrE~!1)tDv9&y)83yAL(vycH%>I zAGZF7cm037$E$XA-M{^%RM~PuD?{)XJ`Yh!*_1jnfBH*XxS!tie%ZslrPhp?B6SJ#N zgadeNhiqR}Ny&ZDqDAB?X$W+!(woQtWWH?p#eZ*U*|$y$iJ|%%Bwm4<{gCVGNMLoU zryu=KxPgk{aTrlYB{p-Q7T+i-sfqel2~y%0DV7YELUUB)oWp(mP+F729~Jck6l2d&#|_l1 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 TbT;szH&NZD`BUPSpU?b1(o1cs literal 0 HcmV?d00001 diff --git a/ca2+-examples/results_ellipseinellipse/tvec.txt b/ca2+-examples/results_ellipseinellipse/tvec.txt new file mode 100644 index 0000000..22fb640 --- /dev/null +++ b/ca2+-examples/results_ellipseinellipse/tvec.txt @@ -0,0 +1,62 @@ +0.000000000000000000e+00 +1.999999949475750327e-04 +3.999999898951500654e-04 +6.000000284984707832e-04 +7.999999797903001308e-04 +1.000000047497451305e-03 +1.200000056996941566e-03 +1.399999950081110001e-03 +1.599999959580600262e-03 +1.799999969080090523e-03 +2.000000094994902611e-03 +2.199999988079071045e-03 +2.400000113993883133e-03 +2.600000007078051567e-03 +2.799999900162220001e-03 +3.000000026077032089e-03 +3.199999919161200523e-03 +3.400000045076012611e-03 +3.599999938160181046e-03 +3.800000064074993134e-03 +4.000000189989805222e-03 +4.199999850243330002e-03 +4.399999976158142090e-03 +4.600000102072954178e-03 +4.800000227987766266e-03 +4.999999888241291046e-03 +5.200000014156103134e-03 +5.400000140070915222e-03 +5.599999800324440002e-03 +5.799999926239252090e-03 +6.000000052154064178e-03 +6.200000178068876266e-03 +6.399999838322401047e-03 +6.599999964237213135e-03 +6.800000090152025223e-03 +7.000000216066837311e-03 +7.199999876320362091e-03 +7.400000002235174179e-03 +7.600000128149986267e-03 +7.799999788403511047e-03 +8.000000379979610443e-03 +8.200000040233135223e-03 +8.399999700486660004e-03 +8.600000292062759399e-03 +8.799999952316284180e-03 +8.999999612569808960e-03 +9.200000204145908356e-03 +9.399999864399433136e-03 +9.600000455975532532e-03 +9.800000116229057312e-03 +9.999999776482582092e-03 +1.020000036805868149e-02 +1.040000002831220627e-02 +1.059999968856573105e-02 +1.080000028014183044e-02 +1.099999994039535522e-02 +1.119999960064888000e-02 +1.140000019222497940e-02 +1.159999985247850418e-02 +1.180000044405460358e-02 +1.200000010430812836e-02 +1.219999976456165314e-02 diff --git a/meshes/spine_mesh.xml b/meshes/spine_mesh.xml deleted file mode 100644 index a98e29d..0000000 --- a/meshes/spine_mesh.xml +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79399dec9849dff4308800908a1f4178650899c5754041344b9c0653cdbd40c4 -size 8586734 From 9f08fe82a39050adc407c0ade8ef61bae2ff86d5 Mon Sep 17 00:00:00 2001 From: emmetfrancis Date: Wed, 1 Nov 2023 16:53:56 -0700 Subject: [PATCH 13/23] Remove results files --- .../results_ellipseinellipse/config.json | 1 - .../results_ellipseinellipse/model_cur.pkl | Bin 7781 -> 0 bytes .../time_dependent.png | Bin 74724 -> 0 bytes .../results_ellipseinellipse/tvec.txt | 62 ------------------ 4 files changed, 63 deletions(-) delete mode 100644 ca2+-examples/results_ellipseinellipse/config.json delete mode 100644 ca2+-examples/results_ellipseinellipse/model_cur.pkl delete mode 100644 ca2+-examples/results_ellipseinellipse/time_dependent.png delete mode 100644 ca2+-examples/results_ellipseinellipse/tvec.txt diff --git a/ca2+-examples/results_ellipseinellipse/config.json b/ca2+-examples/results_ellipseinellipse/config.json deleted file mode 100644 index a5e6122..0000000 --- a/ca2+-examples/results_ellipseinellipse/config.json +++ /dev/null @@ -1 +0,0 @@ -{"solver": {"final_t": 0.025, "use_snes": true, "snes_preassemble_linear_system": false, "initial_dt": 0.0002, "adjust_dt": null, "time_precision": 8, "attempt_timestep_restart_on_divergence": false, "reset_timestep_for_negative_solution": false}, "reaction_database": {"prescribed": "k", "prescribed_linear": "k*u"}} \ No newline at end of file diff --git a/ca2+-examples/results_ellipseinellipse/model_cur.pkl b/ca2+-examples/results_ellipseinellipse/model_cur.pkl deleted file mode 100644 index 9ab10795d677403ebb0cdfd8a8e4e1e26afdff37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7781 zcmcgxYiu0V751*5i4#LYc%-Evl#s5s#My_}&i*K|V~ESHvNgE1P*pmeow?&X<9Tf6 zk=O_%5TeG?6%sJeLRA$hQYBDT!JkT?ibvH}rD}`PwCazhDxs=qK@~;)fvT#HbMBp) zeRyLW1Kri$ow<*5&-u=|=bn4+eSMFAd0!9z+#6iF7iMZD03S z!pB1P32YtHWQF+NSi_Wcdn{>YJLjnHu5Ey!JBbxAG#42=#AbV z(#u$Xpz4Y*UfF<06vtM>%75YG0fnJ@pqsvKd%&}1Ix==#4eP7n9Z<38Izc0(Y1VH$ zKJoBrci<7J;Syk!EV{ClTqz(jc}2l= zMeGLH4uRaB4_Jo}qHH@fmk^w|?wDR!3nKkh=Oi~@4(Z_R(bt~D&r@`W4%3lpita)< z_dvn6Te{*pmSf6pSWN+nbZ-P1{S-~peek<(jqYEg*RRn}*XRv3dZ0#ctkHw?CVD8I zYG}cdm*dH%zB0v4Z2e>zS^)Z2aeW0_o2lQH=BHgQ)9L|#{^5U|SU*iH6Wa6EWg0kU zNxtLbR8!+((Iivl(4;N_pTQ*{=4cw{`|lmZ5?w^dY*7aTd=7i#DL?}p{ST*vfn!<% z&2{HiuiCcoY649njZpS)GZV_+vjh4@vEhy_a;iXRJONqFOiUY^TO=+_uPFUA=hC!AIeQ2 z-{wclcS7!me|vgUtoI8x#u69<9N1to#eoopEJMJ=`aFHn zMqqZe00WN@b%bnSUOYf^Dgs~_##(_7an&gjJ_HCn7~UU#9e$kIXW8Iw7>;D#PJE8GxS__Z~L%RDXgZey&H@|3adC3*T?1nJKKV9e!N`D z*X!kynwzYLm2-%1zJ0z}2>K_GU?W~)9!BzYC0{B{7V1Y7tq4@luD6LMG+;q$srd;j zzvR`l>0>|nS?7P*QNd*BTyF_zZV%@ z8&*-HzudJkLS-bM(~9L{esUrQpL|#WQk{!9h6Vc8bBd-O!LHc%?Jj*g1l`=2L{gO2 zM}4-$=c8;p9!-eHT@W4==%Kqp`kEY_4~8l^sdnO)X{d)2!ot;8rr$4oa=kx}Jn%)M zQsH`IL*7}=7bnZ*Tsb#+q+ALsKSfbHyw7*R1b&nZ0JE=;YlU1PKdFMv{2B+WTTYR8 z0|QWtd1X8omEGxgK)IoA2sl@4?UtL?f^*e8`S7L^K8&BAlZkqZj$MnUS`Ga=VjSqe4 z1q&_bm8l)izkd3CbN!^o3&8CLXUfQOo}uR|F9V$1kDmdL)c1MW)Wp_A6I~iVL($JB zdir2w`WL|V`T6aYxv93m#dsNLUE&cA4xV!-T)tFZxbWQC)1SE`5%}n)Nf9>aH6SQ} z-84zF^v%fg)3B%Nknw;WUBZ+GJVDejJ5>{561xH_71;4bjso&6kQ@o4qdbocS?U(( zz8Au2h^g6Ss;+4PF8lVb%@~VLSJPdz&o0O99&!*EYDgkIlm_nbs2h2W^f4wX5&qGM6isv;DPnctoTklZ7rr99V=UDw{VYXgpQM?m{j1^ z(_>t8()8Sk)+zgVK*|MTS7!42Kf51;oXp+(5A8pc&y9{AE`(iqXvtn|a%SV7h(i;| ze&KrsmI>cYERBE1U~xE;9v73WlYV+ej=($`oe*0EX;swHeVq73F*DwcIVWsKi-EKl zaqs-J1(CE0X)_z`PBt+MSWUBCE&bb#k*^vytPsJ80jcKL=!nV0Y77=%Ig{GAP#MvfAd5zx#8JYFE z;%yjsks;E@1RY4xfVe+H2jF`%y*XgR(PEEVSV{>E9Kw1PSXG=Be-{Ok=5+yN^vqyc z@)W?)UPPk2e)i}$ZawS$`M_U!|60Iy#@tFGfjFQ9p6`Gr_QB3?>Wc15=mTBInW8iB z9Z7X8$f$Yrh156wkBO_ioW=0lX5S}OC`zU-LhVLYN55pJXGHwzbj4}9cfdxqk zsE|%z`V#srj1&qD;3osqNILN?A>3K{3Iy)m<6JV z)Mr;2i$Y2~t*)?Mp|0H@Ry|bGDIMTqPQvj@6CxBS$ExNq%m?mVv6dsp> z=L=B=4Q1dt%I-3Z$1=3rX*UZ!Nl#LXTE=gYSuC28bg|P|5>B_~b?9K72w#iMQP^Uk z0JaEG2De3SVVE-fsI5>gZeIFTx=J5_?>+Ecrpq4>3XcE^LhvkKgC(&vF;R$%E}Jb0 zEU*GzM&Yex1!G=LbQ6n`=lyW;jvn6f>g~38WrzeK=N7p&Ty1jX)>x_%(x<${hy4r9 zg%`7Rj=#2vF8L6FG4vu>)!4C&SW}TxfX6#t)L>C4Az=S$x8xY0mv1j=_tq zvV22y7RkmZ%Pfy^vY7=7)+Z0g+t(w38!_3%SkodNl{DQXBIrB_QGwp@$UNK$65e=t zUCkIR!IfF(c(D=oQB{N10f?J5oo}FUTj-dlFz>fR+_FVYY{xvWpAkKILJW2(HmK9xXv-jg9bY#+EeU5IZRQFudXO8%Utk;2StZ zJ89{sIj&?!sT>Jpg^N_!N7jNm-i6AGUTE9{3TDH{@ugGRt7X%4PD6kOBC2o%u|$Ru nCEeX{;eJ$tnd=V7uiKh~weqqKsXw?ghTuZtH)M=noz1K3I~Ofyf32U{Nc}#o4exr ze#5d;G}mlJe&}%0;Q0xIm1A(r}d=nAkgb5K@>gY}%b9qubs<9mF;59^zip#!$ZdW7?eU zZ0$K14fbX7?z-2Nf8GdRzixf<@0;o~rv_ng*w+#+uG8NiQRUv%$F|XUN$_ zDr<>=_hDhlJv~ZZ5l5cbo9+fXyQ0NF;kpyasq4ouCaj-6eWEGxd?zj;v0FlVbfGON zPtQsPMMVLZ^P^12X9o2RJ|!le{_D-Ff9g4ts)$jXk1*5UajwJ6JxHDxVUeX6rQzj z6Pc{Q`m7PtHhsfL^3fNa37+vs2S(_)N;(ZWL|`g+Eo1H4cz5;}y>ej+Dt6{F510w6 zNp?J~as<_45*iLFABiPT9xxvTtZK$ngPpfS!^6$?ch(rS&nyESs(H876c_l z^4g_5dGh3L5ez^#6hEHuY~so;Q6Jl7BvnA6j-9}D;$Wl;N$>Zly<>~}`VVM#9k9v?U4iZQp(qvW$s6YndFL7OSt z+uIjh^G^AuUQnmq{6(#&%FByMvoNcF*|0g>wDBF4X9m8oN6EFp zS|%gOt|~4A`l_UsAmSra;UGh1Q-Z?fzE#C~_^`_~Ihc(Mk@MV$_9f=F{&P7#wDFXHV{F5o5I=pV{j5tuaWa}XY9AVr(RW)w>Eif4y9JOZ8ljJnZv4- z&U^~k)4t&CYOQo^UW6_rXR%Kufl6eDH&u9kb8@?c?dYpJ)R2R0eJBeNxb?vQG*Rb_ zog$6oVov{jLk9K(C{ka@Kt}D_7|1cf_S5g)1}D)9P2QSU`!X_zi~WSjDM(km+j(Pi zaxv8v5)boFIQWOun$ECfZf<}1ue)d&`ngShSr*mS#!zNA%iE;aQX2l9zf;fnQPtD3 zWc1XM5g$X+9BszF5=YF2tnD8#5Mb{TKa6Y(!D;)+1vcf=gRLTTL##k^}cs3qQ1erqB1-^{a>Ql$@q1rG!jCEgBrC>$k{!lLJj z@lfspk&lrf#*X?!TWPDcQ`ywuB?rrkRGKzX!@HJN)GA1^fI`W89m6b@ z&331n!v@7CB2onxWTqYR{(XXZPKo&W^XL78g8II{zNoCBF*k4z$2rg~Nt%4sG3teM z9P=i3)ljy3>5-!rzIcC>u!@n13C-fUd}e>-a9=ljnw-!tr*g-P`0{15$)+%k&ySy7 zX4XoRp!Dcz2*X_pXD6W_=gN@tFQvK9P?e;$m@_!Q^B*(5XW5@`>bf=Nhc0tk)M#hX zE>+SjvPA11?x?QM_sP}z5%mq0*sV{+STP6*jWnQfmrm4L2Zg5Z>bHo{2`F_=9`qKK zg{d~CY8}m1_r1}#bXMzg=s9#|go1*?-3IYDSEB;I9HiAA z;#Z%hk;nuOtQx-f^i3D`La4dGYH}GJS=qN2E?&$e4+snlt9$ZDR<=!&lG|=dy6*dT zXUp{T^gwL}Z$4WdZQXW*q&E}zGhXapV`ygg(Yv8hniC8EajC#3b3N&7QPQd9`%AS% z4xWOFObH+Kt`<-@{fCEE&)vkW-uBlwR!X*3|v}XUIug6Y_cPuxU1Hk zDyQn`SQs1;>aJuhQdHH8(bP_!3+TegB@yuQq|;8kb3}F9DzDUNH?lL_-1BwT@z&aU>qI zRP8B*+?4BmdHUida`J`gIh-Cpx1={EanH(V1j2h|sEk4?w@37{2W`G<{)dm*!~8P- zs9fc_zcy&sKD)I%KG&D~ac6ygI%?UHE2hAt^9G~@W-v>ovneqgMlDxcqu3kT!|>8_ zeOs)Z%=`GXiipfuBwfeyvKgEfzaNFs(VNFGI409=u{*y5Sd_H1Zl~L-*eb3MyDk2> z^Mvj$(`5!}L!``pi zf|FFnE(-+r$^@@?5%<+rjjsl;DH-v+ z=fIVn;N-B0;MjQXI2U(`7Tm%V^T6=v^j}xTg1d?OYCo$`vGqH^Y@pDhU>u$ZUFosA zr$mhEF>u!FR^qmMC#fj08A{_(%q6Osw{mUo!_oFw7a64x#y@i=)1k18oY(fH%QPzb zl3#>)Q!&9@kA~`|YJ6bS(6tvPZ9|I(?7$x9V$XqgU0og+g;D*M7 zgmNJLxW&z_I^wYzTh)w*A_8|RGTc0X=Hk*K()KLW(OSt%Vd>9kF1>k+x#oApUtocU zfNv1*K@f#NzG*j|w)MB;rN8|dmDJSI=5wobW@l&T221jeF4q`w-HHwqjG^d#G!*hF zR8=X3B%sJ;z2qor$odg(qC#NznRcgamz_oN(57~;qv1(t=)aRC&GxPL2IgjW3H2`a z5lk1LN87S1cK+tXH$`Cvxh(pX=50cLBz1Ji!~T8Ss))mJny~!=$>Ir2a8H#-mgTjd zvR>LD38{iZS?xCoLO6f^S?@c#HP88HZm8LTqFia>u`oBS7wHcpi7#CE6k}DXo?CTL zdQVhzUx6C+Kv}%WS;#7aMu6*6S>;b#`Ldu(W(N`coG$l^>RqU?tLe=UIYuEYxWA_8 zu+Xn;Rle-YWjUAzTXiOodsx26a%gSLk8*CIXrxzF*CRRgqi6p}GOo$khn(_?M|^{{ z^mMoHw+CV9C0!lrm@WFfvL5Li1*dohnB>N_+3xj~W)}1Bug=Hmd+e+|0#jRWVQ)7X zDs}LG^{OK=NHBxfZffg`0*WcNdzJn}y;~ro+f8$&j|Luv^qN5j7Kwr}&Qys<(Ox+5 zQ>}pa)8TUU1$cCua*y4O{&JT*n!cY=fBrnxjW~fq7en3>$f=l8v)Z?z;GQJLPe|Uk zcvzPBNcYUq<5mlJ+b<4l=NYvsn3|?ftEsC;8MQ_yV}{D4f(glZ;=g~t?-h0m6`av# zZ8caez+CeQ7V;={@oZ@{@98_*Kup)D4RISO54R)&DFieimsS2K=wZMXg_smC8KK9< zFXP~(p)#lW%3aK=T{238&o6ITqNzA=CMs!uCpKBGo5*BkgnKUUB#Tn9&*9$DvMy5F zoVqURd$x19!fm~;>OlDrl3jzVzLw?7;(_G+DvusL3Qn>auiq|th)Vk~HvE$uW7)3V zEt=^_-ifL8LHt;>iwEV$a%Swqny8-|#FI8KNWxTX>L5j1>O|ZaCbPOixmb*4s7JM^ zszL?w%%0CV7WT@E1|Q40j=shVU+d`2)(m_9{*gs^84WyQzZ@}+umgq!V?>FeVat~) z$5a&O>Fm2k#HkfaPNx)hsb|-?t75;7I^_%w)L;`cREjB`&v#ihijt<2i#N=ex$eM2 zc5s^rb$5^)Lr3XQTqa;!_e-N-K8?$WqC4sM?2bvz_2av2>qi!Z6BtV6eRr)xbnfSu zL_&(YX-iAi7QeoVfu9RJarSCF7<&K6B#yVATep&KV5rR*)|0bDMQh4QoN6PBt=*KB zgJa!&N0)jQ2+N+OQ=x&>4Ulvs#>Zbje*E}s%^8-SmMC^ko8R}nB2+V>HkivP!zeVo zC5MzIr7f0!zGONsla=r$HMMA;mR;bD8#gXdQs%zCc(YdxFMj*Z-I$b}gMxvgw;gNq zu9CqAOK4f9!|ivA0Y?*o^BQi;Lq;ejF8(kWYOJuZFj`5ok;;lfs}W3lm+aj1N3U@3 zgy44&S7tI??&7*o(6=@bBInu*-_!P3M>E{M-P07tSn&88E|XTVe30Ny8YSb&j&qg6 z9P9V{HUy-`PZUJZ$m}@3V6zL@Q#g9wQ@2~JiLO_;u6L!##Pp;q-XtL7k+0evS~U2g zwpM@5Dors}_JBE8g<9fm4-}dLoBR%QNwBBp(W<)W54?6Xl2Q@OXv6}dqS65YknDm) z%wam#$!uqB*8lC>4XFjxT>C7>dQ&36%_mhNHnH{O)BblZ(whek6_*c2&iVL8?yC?T zS<}G`j73gpKmUVlD$Q(B)qeKI!ayoi;HJyJ>s+@dBlfklhkiZfs;jGW!Q{ds!MImm zE(CKt`F`rxf@8LB)i5P3u1S0oq0=GyF#*@STv2Rl@u`49gUR_{Ui%Xy4o6#3p3)wI z>v?K%=gP&N^pHZ!p^U*nt-AX9ris)q1jc54Ioi=eUX@dAvGcs)At8z1zkfHIf13|!_qLV=vkm_)s_#y2^h0! zvt6HOdRJ5T#A?6@i7#FpFG)^Gah-0(F}VX7YRXbs^_jBz`PYo#n&J+hW>yZj(H+&C z@t&|l8$TZI853W*(#NBtrIl5+H_0NHJ^Ab;MMt5ADeJ_oK{ zykPp_!-pB2KjQwKDau5#iE+iW+BLVdtYKn~$y>#dU}UHuanMt{x&O&alb2xj8&P+` zs{FTE%A;J-8dtpOnD^eEJBcsV5k>?7teqDUyt;rZj8s(LQaS4y8W;ryD`m}zz@tjB zR4DE5ZuXTeR>B<3Rh=Zg>-;%I?qd%4Y}%VQrED#d%nCHnRbGc7g1Kn3Ftl0rVGOFn zWH7O-^?>ZRjt``+R=M-Z9ufihmR33SRhusk91i8{#MSn+kK(C`I828yR5o|@HQEGs z|Bz10(1n2IPx&>;vM@4AMQGW+?k_M;g-A2AG*%mDCn+fzoRpu>u|Cr&I$j^x4XH?2 zOibpFkelW;FHa)|t~!e%jsIFZ)b69C2PTIeXzz{`@<@F%cw=Zkf^zNK5FRnYdI z!^L&9*1p1V*9D)8{n=63`62Fyb!xQ92C}sr7y3C%P?_nC?d_`4(r=z}U5j1z{zwqJ z9!5yU*ekS1nNgXZy{9cpFuyS|w^7uWkw2KO$zqa9(zpKGm=Uq+W9Mvda9rjaj46?c z;ZiQEa#@`U$ytDkKtBQxz$=%Y45AAFA;M}I39&E zC9%=rmi2`JRj8&@cjKQ!&R09O^hgH5Yarld!6LBQ{&8(3g8$OR z%b%97IIcOVV|P~kX=-b$sf|Cs#uqQ3M&Bq~M)%v}IZ2Xor&YYAD4O3q97>oL8p!B4 zmsd~tbb9eC_DjMSk^91=50B8Is!3LlVOk%vbontaX8cz0(>XLMirbksQh)KnUi7dq z)$2O;J0><@J}o)gWornu1Q|*QSRC*4heSUL*9Wj*$TE+1l0kk8flR>0(CJrmtg97T zM_?l;5dSn`7a&6avcxCRB34v$a{(-s@<@VdImEX<+bw~3VlY5-p0OPG`4(){%6n}U z?Ro>!KVymY4yNhUCesHYmy_lx=QnrDj)0bq?;Vhu#X0{zEwHy#8_j915;$J9-D+iD zN36P)j%3ZVPbt_3Gx71+OzRyg4A~@+U_Mc06)rA*BpcnpwXL zuF8DAH`_Dq6w1t)BN0}zM0Ati&;A_^AfR%t#q1~~=n+v{R!B-_0Tm06gEm}~bnkGw?36IKN}Z|TT8B2@?L zxdm>UmINeh_rVw#Rdae^dax+LQTX^f~8Z)geUh{`42yjxtp_ z5zkxqn6DuV^bw5obUh=J96!65cwLtg?ZFcGiY(p? z?@gj;?{gn+Vk?$s{vS>S`m|S^dYEN zF{=LJk(AC{_tcMsKlijR9}S5Wsm;KJ#6lKH%3<CMI^kOoXD5RK6K?{Gnf&=5Fu#owb96(wRS849s>j!&L`qB~nFhp5L&eLg8L} z=H@;7D=*ckB{BgrTl4LeNhC9)lZj4BN}}^qpD;l~37c@$)JVt&3evHT}yR7Lq8 z1W$PtC^}V^$-4L?A26N8UXFtg9`f?}^9i3n-vKWPnbCF`8R{JkZ>D4MNexIU9^1W( zD@#fB^s>|S@rhhtcuP&n>6HwL;_5$6_dGmdq=4Io3FIVmor!)(PDtss_Xu(uWx#F# z*GEZ0DgP|0$nDLnQ@CJ-*DJn!!oFwvb){oy<(=+6eiBx!r3-uCfDGluNg|a9X6?1j zszdFn9dvHK)rcVEU#g|Mm@*_bFRq}3gD31&JSksM8_M0&!V2Axh zyHw50GOjS5=(wP~Xic-ta5(;Rb!~HfmvenYGvCN5C&ztTv$edkC}%GA4-5!- zc}F1o8y4l!;^&IuVl{T77J0Z(PWj41CXM_DkjsYrdfE%Q>^fj1>G}N=5uKyW z4`>VnFRwa;TtyWXl>$x@5|YxXX!A>82U}atG9P#zB_}8AWa4-)Dv|4260T8U3TCcS zCV1_QoE!2oHI9pZFcVOjxwrh7j`cp> z-0jurzHNt<)m2z{>X_D)nHiJZ9>UQUaz+N_k?9D@V8_26i&Qwx{uFoBqx<(?Y7|*! z1G2S0uY0)iq4~#;M?icPtbh6P<*VB5rXN3eXYJZP@G7#hvc_qenVBV`iQ+MXlv$r# z%1Z5m6W4!uh_0`pr*CzUW(7ty*zbQ-nSCF`eXBv`_upM3Ob%mw=X2(Y89)<&WhuJ4 zmcsMWq{qh#!w%wE7(WT)1`Af?<>f`DuE6WilYmlC3-W94~X4Fs5ux|d`Cz0H9X8nl`eqU$c{i(%J`#)%Hd95Rld!*czbkt`CZ6kXESpv zyOJYxG*UNfo-)UqHu9-tQRP(z)ABqx++7GeU`~}Bh;)(eKFAXjG9nA1p%wEp`#q_> z9)M#({A9{$3NZ?K-HH1qx4#`QqlWW5b{)0tJH+gntcL5nwVH0WXEk9GsLa713l?fH1!6t^}xqacee( zl$YU8dV&$GRu$-D<`%K1jrrXyd5t)D6R+tzFlB9 z-A3A=nxmC28_O5Fyqc}{<(|`^jbCtZ@Y>dR(EQGPZXe|5(}4;CP!gxA>bQtq<9gZw zU}?Vlwk?9oAVx9z?FFr*si`Rvis}QerLL{ndq8m;eb?nHopfZi)%9majr%1T*SVy# zFGAL5z8jaEOQ-v3I%hUv>p{QUAc-PqF7bB3in9+^i7^x$jDIJ^$r4Gj%vTe(Mi;zeGR zYuKr&MG9_DT-5g5o}BB=j;Rl#glgN>w9cRSt44M(P2R68v4G3;92`obeuQWl?x7@B z{dyDFWjfi%y{&izVp1*?e|!xdz>_mSQRS{oh4na+f7J0su5OQ8thbRWjwU)Yrn z3+Lb0ro(yyCQ^3DtQF-1lA(e~PfEHUY*3)V>9S%_u~c&=)#DGbs#d~eb3`WODLa!a zUg<~~4HQtI_Sg11F=tFj9VrEKnx}H)?M*&&$&imN+D0=W{MLbE(XeKL>Nf3UuonC9 zBA~NHIvuj-RZ{AajZ*#N9oFlfjQ{lMhQ&~+GUywCYLGWDFo>n#c3m?Wa_G}0zIq!c{;#Wv(T9#}g1{LIX&W24HLk+J2xWShtr(xsmyKIP23PmA@#UyDHR=3DQ3 zF7ViEAr(NL;Gw$e+JhdJ6ZvQW6%D4iDUqi`ITHX{`Tz>_e4u0q$ocX`+Am(bmUSk*D{NXi%$r(s~g8Ar={IuQ96d=Eb(fU(l1!N@8@eW zI7&#$UHqcZ;MdwF<;HyX8eUUj*o>U+V%NvY;bhC#k4T5O#S+_P^b(b^gPx;r964U? zrsF(zTmYyVaJFV0t1Yqosz?onWQQ!uCs1Kz#IO0O4fhg(OiOu-NiUECDzSc}n^@q2 zA?r!g-EX+92T#g;_bp3u_Q#!&q!S_By&|xsnQ-1cKQ~kqzf%sB?8$&UJIr*5KuAWn z^*Q8q>N1#qqTJ%S#gT?l=Qu(zTA7FEu`q1Vu?FRjVekhw3=Vn z7X#HbDvr?9`WF42q2PtN9=dy`0>PoZ?2kMK3Kh;#%3vRDGznSwNLKywmAgg5u9*n% zO~P(;iYa+r_i)kh{7acSpQmV#BW$X~+A2$R{~7aFDO@hXdapN9O-AlZkj zMIAE~{tk#mZ~bv5n7xoY)A6obh!=Ysrc*H~pL_wf%?4C&I?gSpN@|&bs}^)GiwoD;p80xONwu2WIQYdwl`rf{>JX`#b(h|z^@?d-M_4S~|o-~U{ zDui;X0e-KbyP2)cSSO*ezjwqPEHOjeS^0L7as~+?fD30sB{>U)j55Gi=u|n%Z1p_x zi#KJnx>s*Q>h_-Tct>yvp3}-k_4%iS3Wpmub0dPXL=PG4cnY??Wz3zX&4%I>6;>)>(wBviJBg=1wT3$bs;g1p0(8Cv74jMw z{(LHSxod1e^aA6NZ!d?Nf%mWwQj3Rl(3!$ZCG(ElmZz+3Xepg|x`$Tr^Hz}qS1Igt z^&q&27Rc0gfNSHl9u*b<)}$1w&UH}2F#>l13F8tyWtqhQS8F7zD4_mI4<5Wi>Se(8 zk(!K!MGjG3gfnS!Jg!Ey#tCRbl9L=XOzbVJ?`%}OTXJqo+1}(8^B>Y&K%}K?wXg7Q z)Ark5*VW8hE#PF-)%Mwfx zjyCc-7Izt?q^pf}zdo9!U8aJtx25*8pE?sI`es>aX=#x%vT}bzb781V71B?TB&Y(# zkI(@~dG|ik^6vkI&}1QTu142WexyfbXL?_BUz;$R2U3N?61=S}_XQrMc=D4$nZ;Qf z1m!Pa=Fd?ku zKLGfV#AXXDg%dMzn|<@;j@fHJJdR;_>||3DZfkyVB)f|_J?ccpmjn64d~wBIQcytt ziA;}qYll}s@_xNza~=KU^%A!PyY5{@*pd9}NLKtN^z=ET7F_F=lPlw8Ky7JGJ|L-) z>5-FvCuyNyi?G=dj&e(6+-F696<$DMEkEI9labCF3j!Kx0uuOP4q0jq3zPi{o%CLx zZkc~p7_WivWc@Cw?cw5*HdY=CI(=q&@(7pXP=uW|Q97T~Ep)gohq#vm=42hHd`=LO zr3GH&W)Kk223mFf10vl3`_9d++bO~lF)GKviaisdoVg1euWAAaf>>J&kGPp%{&^iX&`4iE4n(}zNuJ%nrr@%lD>pz{#jl7!VNSn)z^An z)vLNmErU-M+8MhJ%76RE%=RYk(^1{$S4K0NHmgAWT0c6hSQ;fVK+WwVnik@-V|?YyvjQQ7 zbi>cYKO59^xLBN6a;oT9ruh@tsl6195uv>HcdG7#sZIWT7;4C<@-}cI_lQ>;lj z=ya&PV-Nh{9H?l}-Z*&qkkXQx+uGWOMnrA6-QJ#m@}w+z3(F%}&(~kyDxX;SRrrLQ;q$zFrMBOh zKMW2$hK$T10AU^SJMV?&UNa?dJihWsh4#Lm%FE>(>q?nW<>mJ(5bG{ov(#(H!qrRX zA=<5iw=@S1w*QO1A5FS>UrscKX)q%E{n*P2g+3H13!&zfhX-!i8U+u*1x2$NHqrO> zZUo@f-jUrt;}gYh90!(CIX%pQmPWn0{QUx1QctmUay@#FCL%`fDk9i?3`4|!Xn$)2 zgFk-ep?z9)PAzc*(bRyl)>Ma$TM1-61-qB@pVIUl_WizbI}TO3qni0UQEPMHz6S9X zy+@yU8waegIkm=FBt9cj3WPOY`gW4jdFioaj?HZZ9N54NWsV$d6bWWx2|*^1i#2hQ z^VxS;dF^Tg%IN3(}1ePfw3m@!hwl4aAro|55K=h$<9 zYrzrXMoL~x=_GO3k1-DW;%c;r*A8jYPiv`lVZpA9UgBr&?xD&3tW&K>)+6jdS1aIl ztW^g}{;BqlxtdlL1^vrFc0YhRH~uXd@9)$mS!QbL#QwC1tgPFCS6S~JZZ!yTK&&gU z7*P4>bx?w22S7720=^i$26FFt1k6hi^_wd*U%7HN^=&h9Q>g4R z^(!4-7JSMZ{#1ObkLALa0g}r{;~z0*cu3X=25v^S2y(fm=H`9*&RJ0R z6RgQ7eh~1R@okCd&P>NSqjqGfO&pQj9|S!)Am~wM0Y^|Uj>vv2%NDqVl+o5kdFla| znYs|IG|V2z52OJzMnnO?ae2F@k3g>#R{A7tmpVApF5sYx9iMyq28S%Vdpdck}lBrpLqk9`+`8sv40IjXD8K zr-de&1a=5fha)_x$cqz_!M$1Pi7hSC`zsMuYpSn}lK_{WIKmbnZp^<+HFX_)3PS>6q6N(l9gMt=o_g4w%7?eS?rpkT>ZQY8R-U?Je`h4; z#kRwl8K!R{{z0E_a=ZvU= zyc02Dm-JN>q=#^Kg4z5sH4k25wyAwK{Jn>Vv=>R;6qV-WO-mF3;xhd8aBhus>UxOz z2@i{J?sZx_#nw*Vi#vip$$H)iVWU^;IYcd-8aYN-JpiTSLsR0EP!&A~4PC9dv`&GK zs!G2^K5Cxtm?kd4T=}U@Z8;cI->WRG_8^;l6&+N<6&QG?=vK>K-b`eve|fd=1mo#i{nbvr_`(>+dg% zsgodA_YOz&P5(d>YyIZA>5#)Atte)bStd3_Pcm>&J(?!3)PJz*J$#!!ApT{NF!e2Q z1o}^;5C(sGDCWBg!BTUqum3}G1_q|ygYweAlGCg)mo!=3hF3Xn+_xFac`R7?J9EA= zb3pL+cj*U^yLx*gDo~_`2oAmg$+6_v*ckrl)9H?a+}wR~VK~CHV8k`VsHIq7@_tii z`v>Jee4VOmb=4OH^Kw_M^o3>{V+Ly!h$DEFV3$B36m=l8qn7L)jlYQN8t}VDR7*FL zB6q8w;qJ>v+%SVCUy%jME*j1NHo>I+4GHwd(6r!o(3D6tMS=~WWWpgPNvpjAoUVte z66VTZN?YI3ma%lwZYZYaa+tDv_8<*cZkm#dk0FvHusAUcsC;^=DH%H-@HtZMLZhG3 zY<%w78L$d`y;?Q6pe78E@I{O z)l2)_Gj@8+F5sa_YPr=Ex5+B1RMuxE3Sy!zwk~gLAlhQgy`Hrt)Rv~p_Tcu<~ z?1BpHqN~EqiTFz2J|*;#5WV%Uj+Glj2{!}3e#w+T1>|h$He(L1(&$Ir1t(UNgRUr# z+$oGt(mGB%+3%E}_I6z|yGHiZguplVMo>&>iFhcPe13jv7WRz>2V9&D&_scqnZ#&8 zk5bo7jJCe78_?Px8SuOx0$L(+41@A!SF-2{oa36SALB5{{CTHvCoM(+sIE-M5%SUL zh|uOcN#&o|Ads6D;m@yWh|r}gJ5OGX`MGqiAY%pX0T>cHayMK8m@N`s+a#JK}6kQsywmKQgA>IzEr492lFob#?#H0 zYs$6TH43?o-7yTMvzp31q!%q8Wqv+afS=2wVk1=Var^FlR=vx0FMS#>kSPTTk-#KP z1t60IVnN_pyDf@_QV>=g(&}KOpATR@6VTmx=A1(@NHl#mWk8|U0kdMH>d$(~K9d49 zCZrR@A|9p%ZQD)Om262)PQR{%^BG*tn8^{!vvig8{~U>C82mKjUtMW-eev|4+IyES zQhZXu=#Nq1pJ81!7gLvvKnX2I#$gu^O4zU`MRRAmTNv`9w z&f0o2R}lFE?)-Z;EBAa?S-bLlVG;Pz$Yt;q)wzhP04bCMXwK0*6y)UDki%?OUPq<* z+_avitVN+XTzVhpUHH`U@KuHzcLfda2VeY$O?MOaUQ<=@rxg)LsPHb9>Q3EJnzNp> zeuh*P#%rZ?A(zNC>;?mOJ?+IFEFMh0*u6}CSz1*9g<8UgjTZdjLlzK083=pwHdm`e z0g&4jxko5#+?&*(Uq0nLbzQNpJ}aC(TF&Zf4)1E%&|30WK}L)qtOy;H z*Dg4}xFU-TqQ5A2N7v75HD<&F5~;Xdgls=N*aa?Am;T>b9VkNGwVAz7*zzG86eL05 zbu3S-6w|8FXVv#S4)j{LqDjvjsR#YsRP;Ltor>?ucyBE9?5?2(#Kbh5~{xO|BMwO+6D?)%Z z)X8r>M|0`4_GZ|KpKoOQ);d%l;{fvA0X(-4p{lYqi<)NbGmzWX%%>n6E64>4;xvKP zMKq<5kR~T3fv5#E12F%J2n-Akl||}!hwz(F@Pjh1Ar8IdR6F0xzL*h_1c}>>}L>c`oz0*4QAs^YF52)3a< zf(;M&L?6Q2&fE1bIR07)i@7~HJ!c$+ogna~8@8!8-P<>hEr*Fz zFn-V7sW1=ZFK1zJco2-uwH$7x#!)g9M!vW!;-19$lYOp!$YrroOoOq0^yLZ0(x&~i zj>lqZ&xtR6A7Q~k9Y~B)pb#lPliFutB%T0I=Kx9~MrP)=Tuy^=-5kCH(V?Yqt9FcBAz? z?bF;mCV5L2a8LwdFwBFsR3TG9CU!u}zShRSXwno)k8En7UY(%#tCht|{T1N4?(Xgt z6ftpe%#uJohSU5<-`Vs}K+_Ddx?r!xOJ}Q%q2xlL&RxCNBH|zT`BotXwGEAG1f&hM z7kkPi#)~V%0dHNYWchG~&6_o+*9D+UFF-0% zM9&PmlbwUz#dYXKGGCkN1Z{cjf3~!7xNSV0oSHIQnfMWXNAEebyYKH2qCU{e`SBl~ z0vBP@DNPr0BRl6YznI>-E8(Wehl_ZwPFEKRr-r6i7;APjrU$2K(_8eQ-d8Fim??}% zIu-D%TYyrJKn3WU^D22avZ?&@OWTwwh&J|kl>{hANV7hlV<+E4b+mwqUk~{~1Al7o zOxW}7Yq!xuH=qaj;AkL~0O{>7vO=8g3}7UiS*$Uw8u=#gk*9^CN9o^4?|4HKPR|u2 zaG^$-lqJUVUuPUXc(h5E-yncf?nWcq>MdB6l+{$qKvh+tl{}f_^#Za4CAGPZZ|-g{ zhIQXbqH(uRnNPOtGRK-dY zBPnE$&!0cv(;k)tsMH0tWv!-tfS$;H^w@|ZV!-YANk&|C(jW=Z5Jvm^THc#CVsTPB zGrlU-q-5eWQ(Ph4u#jWthyHH%AVWg z*2IXvdAxV?Fsh$v(Bb5H?oHzXo`@1*tNI!vKr=wlEev46Y+fvd&Z=qcZBdMINB8 z7v3a4yjpN0pGas-P3={gX4Q&2Bsk%Z4{VMdyMQ!x%emk>0a^i%e7V(tjh^Fs7U*0w zBwVL?o5^K~!+6aWDs%TL=RBMubt~9M$d|4t&(^Rw@F;6c^su_He2+*vm-mBRA=TA; z5r3mLGnSpPMM2;X(Wd>R>fv!z%mD6z2-5CNpPD)LvT6%%gIX$uxb~@4#GjC0xoMTe zH$lNqu`ZdA81|5s#;-uH->jQi?yB;-IyP+%Egm^5tb}2FT8BIgc2^nfoSo<@X`n-! zcxbD@X{?MNQ+wOU$FG`jjJJ*o#-2>XTDo&@s>S|RkSOTniK`y& zwpqlV9hwectYl%f>$$5khrbCYMCA73AG?8_dSFDcsV53GJiYLR&(gQd4ZoWHJkqo8 z|DFv7jOy6y;5gnx00Lo@d()%NOXt7q_9DMW343FYA_(2pe9P7kORpz&6(V6FOnv{(TZ<5`^i*{szT@J@p~9JFKYrz8^pK zcc<YmC4@ z?e_(y-QtL<9tc#226CsyJ7-%z3XKFon{!apdp4TCkNg>QsJQ@0uXl(NA&@h0fMs_Z z${gmWwmpH+?k{!7WMgL!3l6>^84ZdQO!=y;%tt}(&dyH3+-t)7Uyw=>UZ<4agg(>V z2VESZrUhr+ldCHPZ9MaEoJtQ6$al>Vump5vE;Wvlq^wU~f|Kvf)%EI{E*w~BYH!bV z#2fVrV;|htI$AF8i1RQm$HieL$!I=@j5yC7Q>1y)KQItocSSb~I=Cbi{&wOEFmrLK zTn-QeMdnQp46rQ>baeH0QZh0Y<=nXOZ6C1NHw+QSznc8Nd+u%dVuO=X<6Iwd4_d$2 z*<8#8IUmxP;syPc0`0~bw2|y*6MM%kf$;#@|LP!2M8%;v&Ii_1@Izoc$m^V1n}_! zf>ezu03zSTzgsN+@1C~NTpfD)|1h z=S{HQYdmg*8F4kpRjRR9Au$g7J|qp-LteON5zyTDgEHTs%)cB=BqHpO_e45hq45^Z zKB^{RRf;NRSHR-zsAHtKPVrHET8~wMDy?cp&C$|BVnt>2hH%EvIm?3|MscJ_yc z+I!k3d%ad7(`KMQWTPc=D{IJOgplN0^V|Hjkbx^+t)`pBx9g;A^|d!#3sUUtud zX@78@pttJ`U5wmOHd^7cC{z@xJA4@cQjxLAsGv|F$ z--PGSaiM^|b?a67z!C)?C?}5{Kb{T>n*-+BdB~LUri2_#T#7wA2W8_3HuH z>!`8Nth}gVJCK|w6W90VaeRqy-uT^Ez2G{*yvybc0 zkdpq-tT^LfD^2WMXB=4Xzb_r(zeWJ2aRrGNN3YT2usj!$vo?Ucqo<=2hgtyH7H`S< z9h(}i`X;0+rXpH?NTo|Bf~E4|a3nMHVdrrIZQCETFg<`$^DWrnIxMFO?MuRUluS(0 z5SIjc`Hj$dRY>{{_IDXUg$wXemw9f5ihe7&e>6B5NaK-)#gE`3^}~WLw+-|yg2T11 zCE!kjiVqZE$l+8Yuh5Cfs~hXPaBPa%&(C_WMU{bG_a&mtahUJroYE9NGqo_k2`ObK z(3FUhLG1p0qz?iCC9kbA#UD3+Q*~I1l$hWk_3Pg(+oFYOt#oUVqDY!fF;ElG5dBJm z_#dzhqd$MqZ!m-A2ISm{@Z0L2pzQtvWJB9vmJrY1eJ>m!UlZ7#2npQ3+;PtxsZM}wk8_=)PfkrWhjEueIe_$^&+j0ZK}Kdy*Jr(2 zL_T4fXWscdo!wTv!$8L+cec^aQ@v=o32F8W)E)`%LmOF!?t?nYv&`;OXn3r8_g5 z1Y)mEGgFasx>=B?Ih$BsKKRPtpY338T8O*+aXh}R zR+X>bRX7`q9pdn;FY%cV@n3RAR#t5F;S7~}Amt4QL1|N8(5EwxF2yOTFZPAHNt+tN z@O%MoD*-ud3=o@`!3jAgqt(yi^k1AHPyo-NlC7aUuMas#;)f4p(Bb3q7ED>&eYx(Y z$N!=2&BJnDyY}(h#zuy0vy?Wcr>wB)d{XFmU9>4ef9^Zd{9eW?!X7Bs{4A*t7wa#^(=bHCL9=`!fid|fX ztXol0k*swMvWKMX5eT_kZ*kkppE^~ssoX>$>nTUa$V}UJs^!KfPCP_ZU9x<6i%;gX z;pP$HdHc2OhhI|IbQVndX!GI42ZPTYgLuHI=-ZkBaj;4^@!zMdnU&+ptqQ9!>cG2j z2p>h4x0X45&tiB;?SR9Y=+PrbLN4!Kvt}7HGgU%o9R^mUY0#&TQpamk+di;0Q2!*3%h6h30;2N| zjT@=?Nq`=e@SUwYeNOC27xQs@L``czIAn6PQ?_A`V|J1gkew*Ps3s={(+6M)^Djm| z4j7IW7shd=SLt&ZYTh_z^kHQXtlo#+CF4e0)70rKNE%^_3(K3f?%D6tX0BQ_ve?yB zAWIc1ex~iqt>fci{ zr&$e-2yEQ=g$M&Ahk-{>t>N9e;0^aaBiA@!JiC#0YB7hhxKosOe))l@ zD*5}rWlOq{B8q{f;$nIWr%}&o9g)FoTYBuWvcEZ{U}hxEJ$;7~7$6|#yU5;F)UYJr zHHv+~0u`Vza*0j`D{D(mmJ8L-b+H>Sc(VO4ggRf4!f8Qbo0B~C)tkxB#K-71XY`d zw7sSgv9U`0DiOQdv>lwFRf200la@A4YKx){qhIy!gv-e33Q(i`ks}}fV`)Ff>#2yj z*x|Tb^NSm=3^6ggm%A=2^paxE&w5Z}WPJ52)<=Rpt&jYtA_h2ieXAg>BNH7ug8>H{ z#tz=_%u$uPF+YoZm%@Z+{2_2X+EQ<(yd~D^4y2FTaKWfNi4*8+vZb+hNyv%PGP91d z{2{<}y9dE+)`2U+3P0Q4JFnr}(FnS)Fa$C#5c?N;LrJtwLZaqsU%tqE>1?b8?ZDvR zRe;F{Ds1+jIR*2tzZNYzmoomlDjRUAi}a)QE5YH*hLy?utS$F8_+HIifiQV-9FNll zobX2Y%IpN7u228RA@EUqT$y88G0m_E*bep7R-lXU+qe4xrg0a}5%g;r8LmhHcE>+{yaHFnkpquMQ?gq5{CKPP zR&6JE(BaYYgME0ssj12O=j$9qzF_G2+lfv3TW!_?&M%W=3Yl;ONV;3p8-66t4NA_&*rw$=( zGstL1)4GlKxR03fi$g97NF&RlG{SAVQ&kb4qERICN<&4Sge-6A%9Wwy#R(Wf34TL| z?sJABmaA#41G8Ley#En+BK4x4K0cAiA(R_)T+4C&6oLo|Q7vUJ(1Z(DE;MX-g&c$I z1byh9qxy{DaABjMpezNIlE~8{E}#J6^i@O#Kr%sctfG?r!?~|0=r?>S4PCutNg$AX z8Qg6dIXFwnI$f-#Y*M2b{`XOHF z;7(t{c>Xv}(H1`@&DOtvLLHhr@dQF05Yh8Y>n`o2WkWJ89z7&Ym5HXo*!E!(a?;Xw z&}~ufFu<#laoe|lzeG-f#GpD$c~_Gh50&k6Y#Gv5Aw3_6m@{|oqaW>I^nGdsM}j-B zlEtN|JHM^e`g5DvQ8#$!P?11v$ZS&pK0#&W=%0A~;1Fok)P{!$GOktltw zvmcG5d=IAILp=CZt?x%7ep37fUDY2tU%TE<8^mmfUKNP8a29%# zb{G+@@17?u1vR#RzF{^ydpY9H3q0F4ywP%c!z3Am>K!oE2D|EL=C}bWmyGS+>&3*> zX#GYdweyjWg7rvSu&`y9x`tJghi{H;|JTE4%Eor1}h(}!?L{yJ0GVumyrE~!1)tDv9&y)83yAL(vycH%>I zAGZF7cm037$E$XA-M{^%RM~PuD?{)XJ`Yh!*_1jnfBH*XxS!tie%ZslrPhp?B6SJ#N zgadeNhiqR}Ny&ZDqDAB?X$W+!(woQtWWH?p#eZ*U*|$y$iJ|%%Bwm4<{gCVGNMLoU zryu=KxPgk{aTrlYB{p-Q7T+i-sfqel2~y%0DV7YELUUB)oWp(mP+F729~Jck6l2d&#|_l1 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 TbT;szH&NZD`BUPSpU?b1(o1cs diff --git a/ca2+-examples/results_ellipseinellipse/tvec.txt b/ca2+-examples/results_ellipseinellipse/tvec.txt deleted file mode 100644 index 22fb640..0000000 --- a/ca2+-examples/results_ellipseinellipse/tvec.txt +++ /dev/null @@ -1,62 +0,0 @@ -0.000000000000000000e+00 -1.999999949475750327e-04 -3.999999898951500654e-04 -6.000000284984707832e-04 -7.999999797903001308e-04 -1.000000047497451305e-03 -1.200000056996941566e-03 -1.399999950081110001e-03 -1.599999959580600262e-03 -1.799999969080090523e-03 -2.000000094994902611e-03 -2.199999988079071045e-03 -2.400000113993883133e-03 -2.600000007078051567e-03 -2.799999900162220001e-03 -3.000000026077032089e-03 -3.199999919161200523e-03 -3.400000045076012611e-03 -3.599999938160181046e-03 -3.800000064074993134e-03 -4.000000189989805222e-03 -4.199999850243330002e-03 -4.399999976158142090e-03 -4.600000102072954178e-03 -4.800000227987766266e-03 -4.999999888241291046e-03 -5.200000014156103134e-03 -5.400000140070915222e-03 -5.599999800324440002e-03 -5.799999926239252090e-03 -6.000000052154064178e-03 -6.200000178068876266e-03 -6.399999838322401047e-03 -6.599999964237213135e-03 -6.800000090152025223e-03 -7.000000216066837311e-03 -7.199999876320362091e-03 -7.400000002235174179e-03 -7.600000128149986267e-03 -7.799999788403511047e-03 -8.000000379979610443e-03 -8.200000040233135223e-03 -8.399999700486660004e-03 -8.600000292062759399e-03 -8.799999952316284180e-03 -8.999999612569808960e-03 -9.200000204145908356e-03 -9.399999864399433136e-03 -9.600000455975532532e-03 -9.800000116229057312e-03 -9.999999776482582092e-03 -1.020000036805868149e-02 -1.040000002831220627e-02 -1.059999968856573105e-02 -1.080000028014183044e-02 -1.099999994039535522e-02 -1.119999960064888000e-02 -1.140000019222497940e-02 -1.159999985247850418e-02 -1.180000044405460358e-02 -1.200000010430812836e-02 -1.219999976456165314e-02 From 07fba63d70d9d2cbfbcba232d7000db57f52f803 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Thu, 2 Nov 2023 18:20:07 +0100 Subject: [PATCH 14/23] Use argparse instead to pass arguments and add preprocessing script for mesh --- ca2+-examples/dendritic_spine.ipynb | 178 ++++++++++++---------------- ca2+-examples/preprocess_mesh.py | 20 ++++ 2 files changed, 99 insertions(+), 99 deletions(-) create mode 100644 ca2+-examples/preprocess_mesh.py diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index b49d54c..b21e165 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "5cd0d151", + "id": "b78cef70", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,14 +30,14 @@ { "cell_type": "code", "execution_count": null, - "id": "79eefb82", + "id": "6e3f515f", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", - "import os\n", + "import argparse\n", "import pathlib\n", "import logging\n", "from sympy.utilities.lambdify import lambdify\n", @@ -49,20 +49,29 @@ " Parameter,\n", " Reaction,\n", " Species,\n", - " SpeciesContainer,\n", - " CompartmentContainer,\n", " sbmodel_from_locals,\n", ")\n", "\n", "from matplotlib import pyplot as plt\n", "\n", "logger = logging.getLogger(\"smart\")\n", - "logger.setLevel(logging.DEBUG)" + "logger.setLevel(logging.DEBUG)\n", + "\n", + "parser = argparse.ArgumentParser()\n", + "parser.add_argument(\n", + " \"--mesh-file\",\n", + " type=pathlib.Path,\n", + " default=pathlib.Path.cwd() / \"ellipseSpine_mesh\" / \"ellipseSpine_mesh.h5\",\n", + ")\n", + "parser.add_argument(\n", + " \"-o\", \"--outdir\", type=pathlib.Path, default=pathlib.Path(\"results_2spines\")\n", + ")\n", + "args = vars(parser.parse_args())" ] }, { "cell_type": "markdown", - "id": "6771377d", + "id": "d9ddd435", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -71,7 +80,7 @@ { "cell_type": "code", "execution_count": null, - "id": "339351eb", + "id": "4867c946", "metadata": {}, "outputs": [], "source": [ @@ -94,60 +103,35 @@ }, { "cell_type": "markdown", - "id": "f3296c0c", + "id": "7bbaf836", "metadata": {}, "source": [ - "## Create and load in mesh\n", - "\n", - "Here, we consider an \"ellipsoid-in-an-ellipsoid\" geometry. The inner ellipsoid represents the SA and the volume between the SA boundary and the boundary of the outer ellipsoid represents the cytosol." + "## Load in mesh" ] }, { "cell_type": "code", "execution_count": null, - "id": "44b17db3", + "id": "cd9a578a", "metadata": {}, "outputs": [], "source": [ - "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " cur_dir = pathlib.Path(example_dir)\n", - "else:\n", - " cur_dir = pathlib.Path.cwd()\n", - "parent_dir = cur_dir.parent\n", - "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", - "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", - "facet_markers_orig = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", - "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", - "facet_array = facet_markers.array()[:]\n", - "for i in range(len(facet_array)):\n", - " if facet_array[i] == 11: # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", - " facet_array[i] = 10\n", - "\n", - "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " mesh_folder = pathlib.Path(folder)\n", - "else:\n", - " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", - "mesh_folder.mkdir(exist_ok=True)\n", - "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", - "if not bool(int(os.getenv(\"HPC\", 0))):\n", - " # Don't write mesh on HPC cluster\n", - " mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", - "\n", + "mesh_file = args[\"mesh_file\"]\n", "parent_mesh = mesh.ParentMesh(\n", " mesh_filename=str(mesh_file),\n", " mesh_filetype=\"hdf5\",\n", " name=\"parent_mesh\",\n", ")\n", - "\n", - "integrateDomain = facet_markers_orig\n", - "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", + "geo = mesh_tools.load_mesh(filename=args[\"mesh_file\"], mesh=parent_mesh.dolfin_mesh)\n", + "ds = d.Measure(\"ds\", domain=geo.mesh, subdomain_data=geo.mf_facet)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", - "visualization.plot_dolfin_mesh(spine_mesh, cell_markers)" + "visualization.plot_dolfin_mesh(geo.mesh, geo.mf_cell)\n", + "# -" ] }, { "cell_type": "markdown", - "id": "a2db85a6", + "id": "3dd28afb", "metadata": {}, "source": [ "## Model generation\n", @@ -160,7 +144,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f2cf4a74", + "id": "cda2c2e2", "metadata": {}, "outputs": [], "source": [ @@ -174,7 +158,7 @@ }, { "cell_type": "markdown", - "id": "60a20c91", + "id": "078b3827", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -183,7 +167,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a3508657", + "id": "6fca9ce9", "metadata": {}, "outputs": [], "source": [ @@ -207,7 +191,7 @@ }, { "cell_type": "markdown", - "id": "c40442b2", + "id": "28879afb", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -228,7 +212,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c411c91c", + "id": "df0d2034", "metadata": { "lines_to_next_cell": 0 }, @@ -402,7 +386,7 @@ }, { "cell_type": "markdown", - "id": "a9d39ce7", + "id": "d2bc3041", "metadata": {}, "source": [ "Create a results folder" @@ -411,20 +395,17 @@ { "cell_type": "code", "execution_count": null, - "id": "981d5ea2", + "id": "9ff8518c", "metadata": {}, "outputs": [], "source": [ - "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", - " result_folder = pathlib.Path(folder)\n", - "else:\n", - " result_folder = pathlib.Path(\"results_2spines\")\n", + "result_folder = args[\"outdir\"]\n", "result_folder.mkdir(exist_ok=True)" ] }, { "cell_type": "markdown", - "id": "b7bdb34d", + "id": "c6c6c160", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -433,7 +414,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c7bacfbe", + "id": "c52dc906", "metadata": {}, "outputs": [], "source": [ @@ -459,7 +440,7 @@ }, { "cell_type": "markdown", - "id": "17e44d95", + "id": "29bcb1a7", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -468,7 +449,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3987aad8", + "id": "8908d043", "metadata": {}, "outputs": [], "source": [ @@ -487,7 +468,7 @@ }, { "cell_type": "markdown", - "id": "eeb2451e", + "id": "9e927379", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -498,7 +479,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c1156fe4", + "id": "a95a79d5", "metadata": {}, "outputs": [], "source": [ @@ -544,7 +525,7 @@ }, { "cell_type": "markdown", - "id": "41ea5a82", + "id": "d8e976c9", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -553,7 +534,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bf02840a", + "id": "b4ae2f79", "metadata": {}, "outputs": [], "source": [ @@ -562,7 +543,7 @@ }, { "cell_type": "markdown", - "id": "ceb17b9c", + "id": "cdf7e37e", "metadata": {}, "source": [ "Initialize model and solver." @@ -571,7 +552,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3c46f8be", + "id": "044275ca", "metadata": {}, "outputs": [], "source": [ @@ -586,7 +567,16 @@ " \"use_snes\": True,\n", " }\n", ")\n", - "import json\n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4125fcf", + "metadata": {}, + "outputs": [], + "source": [ "# Dump config to results folder\n", "(result_folder / \"config.json\").write_text(\n", " json.dumps(\n", @@ -600,7 +590,7 @@ }, { "cell_type": "markdown", - "id": "0ad1eda0", + "id": "ce64365c", "metadata": {}, "source": [ "Initialize model and set initial condition vector for NMDAR (set to 1.0 in the PSD). This is currently a very slow, inefficient method - we iterate through the mesh and save the coordinates originally tagged with marker=11 (these belong to the PSD). The associated entries of the dolfin vector for NMDAR are then set to 1.0." @@ -609,7 +599,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ee9010f3", + "id": "bb5305ad", "metadata": {}, "outputs": [], "source": [ @@ -617,27 +607,33 @@ "sp = model_cur.sc[\"NMDAR\"]\n", "u = model_cur.cc[sp.compartment_name].u[\"u\"]\n", "indices = sp.dof_map\n", - "uvec = u.vector()\n", - "values = uvec.get_local()\n", + "uvec = u.vector()\n", + "values = uvec.get_local()\n", "# store coordinates for current compartment\n", - "dof_coord = model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1,3))\n", - "dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", - "vertex_coords = [] # save all vertex coordinates that need to be set to 1.0 (in PSD)\n", + "dof_coord = (\n", + " model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1, 3))\n", + ")\n", + "dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", + "vertex_coords = [] # save all vertex coordinates that need to be set to 1.0 (in PSD)\n", "idx = 0\n", - "for facet in d.facets(spine_mesh):\n", - " cur_marker = facet_markers_orig.array()[idx]\n", + "for facet in d.facets(geo.mesh):\n", + " cur_marker = geo.mf_facet.array()[idx]\n", " if cur_marker == 11:\n", " for vertex in d.vertices(facet):\n", " cur_vertex = list(vertex.point().array())\n", - " if len(vertex_coords)==0: # first time in this loop\n", + " if len(vertex_coords) == 0: # first time in this loop\n", " vertex_coords.append(cur_vertex)\n", " else:\n", - " compare_vertices = np.sum((np.array(vertex_coords)-np.array(cur_vertex))**2,1)\n", - " if not any(np.isclose(compare_vertices,0)): # unique point not seen yet\n", + " compare_vertices = np.sum(\n", + " (np.array(vertex_coords) - np.array(cur_vertex)) ** 2, 1\n", + " )\n", + " if not any(\n", + " np.isclose(compare_vertices, 0)\n", + " ): # unique point not seen yet\n", " vertex_coords.append(cur_vertex)\n", " idx = idx + 1\n", "for i in range(len(dof_coord)):\n", - " compare_vertices = np.sum((np.array(vertex_coords)-dof_coord[i,:])**2,1)\n", + " compare_vertices = np.sum((np.array(vertex_coords) - dof_coord[i, :]) ** 2, 1)\n", " if np.any(np.isclose(compare_vertices, 0)):\n", " values[indices[i]] = 1.0\n", " print(f\"Completed value {i+1} of {len(dof_coord)}\")\n", @@ -653,7 +649,7 @@ }, { "cell_type": "markdown", - "id": "6df287da", + "id": "fe484b12", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -662,7 +658,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ab63f3e7", + "id": "6a9e7f0d", "metadata": {}, "outputs": [], "source": [ @@ -707,7 +703,7 @@ }, { "cell_type": "markdown", - "id": "207c41b2", + "id": "3c6425fe", "metadata": {}, "source": [ "Plot calcium over time for this model." @@ -716,10 +712,8 @@ { "cell_type": "code", "execution_count": null, - "id": "9cf59fdd", - "metadata": { - "lines_to_next_cell": 2 - }, + "id": "4a021470", + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -736,22 +730,8 @@ "main_language": "python", "notebook_metadata_filter": "-all" }, - "kernelspec": { - "display_name": "Python 3", - "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.10.6" + "name": "python" } }, "nbformat": 4, diff --git a/ca2+-examples/preprocess_mesh.py b/ca2+-examples/preprocess_mesh.py new file mode 100644 index 0000000..8722cbc --- /dev/null +++ b/ca2+-examples/preprocess_mesh.py @@ -0,0 +1,20 @@ +from pathlib import Path + +import dolfin as d + +from smart import mesh_tools + + +mesh_file = Path.cwd().parent / "meshes" / "2spine_PM10_PSD11_ERM12_cyto1_ER2.xml" +print(f"Load mesh {mesh_file}") +spine_mesh = d.Mesh(mesh_file.as_posix()) +cell_markers = d.MeshFunction("size_t", spine_mesh, 3, spine_mesh.domains()) +facet_markers = d.MeshFunction("size_t", spine_mesh, 2, spine_mesh.domains()) + + +mesh_folder = Path("ellipseSpine_mesh") +mesh_folder.mkdir(exist_ok=True) +new_mesh_file = mesh_folder / "ellipseSpine_mesh.h5" + +mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, new_mesh_file) +print(f"Mesh saved to {new_mesh_file}") From d4078ee4edf7c02d48dba9e068198261ba5a66c2 Mon Sep 17 00:00:00 2001 From: Henrik Finsberg Date: Thu, 9 Nov 2023 10:19:26 +0100 Subject: [PATCH 15/23] Add timers --- ca2+-examples/dendritic_spine.ipynb | 128 +++++++++++++++++++--------- ca2+-examples/preprocess_mesh.py | 3 +- 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index b21e165..72dfdb1 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "b78cef70", + "id": "1c9692ae", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6e3f515f", + "id": "3b56cc19", "metadata": {}, "outputs": [], "source": [ @@ -71,19 +71,32 @@ }, { "cell_type": "markdown", - "id": "d9ddd435", + "id": "a3a9f250", "metadata": {}, "source": [ - "First, we define the various units for the inputs" + "Let add a timer to measure how long time it takes to set ut the model" ] }, { "cell_type": "code", "execution_count": null, - "id": "4867c946", + "id": "8073de0e", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "timer_setup = d.Timer(\"Setup model\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e9d3860", "metadata": {}, "outputs": [], "source": [ + "# First, we define the various units for the inputs\n", "# Aliases - base units\n", "uM = unit.uM\n", "um = unit.um\n", @@ -102,21 +115,23 @@ ] }, { - "cell_type": "markdown", - "id": "7bbaf836", + "cell_type": "code", + "execution_count": null, + "id": "32d6b79a", "metadata": {}, + "outputs": [], "source": [ - "## Load in mesh" + "# ## Load in mesh\n", + "mesh_file = args[\"mesh_file\"]" ] }, { "cell_type": "code", "execution_count": null, - "id": "cd9a578a", + "id": "867e31cf", "metadata": {}, "outputs": [], "source": [ - "mesh_file = args[\"mesh_file\"]\n", "parent_mesh = mesh.ParentMesh(\n", " mesh_filename=str(mesh_file),\n", " mesh_filetype=\"hdf5\",\n", @@ -131,7 +146,7 @@ }, { "cell_type": "markdown", - "id": "3dd28afb", + "id": "8bcf2422", "metadata": {}, "source": [ "## Model generation\n", @@ -144,7 +159,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cda2c2e2", + "id": "fbd4c027", "metadata": {}, "outputs": [], "source": [ @@ -158,7 +173,7 @@ }, { "cell_type": "markdown", - "id": "078b3827", + "id": "08f91934", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -167,7 +182,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6fca9ce9", + "id": "5039a406", "metadata": {}, "outputs": [], "source": [ @@ -191,7 +206,7 @@ }, { "cell_type": "markdown", - "id": "28879afb", + "id": "dff1c8ff", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -212,7 +227,7 @@ { "cell_type": "code", "execution_count": null, - "id": "df0d2034", + "id": "7343b969", "metadata": { "lines_to_next_cell": 0 }, @@ -386,7 +401,7 @@ }, { "cell_type": "markdown", - "id": "d2bc3041", + "id": "969ee9a8", "metadata": {}, "source": [ "Create a results folder" @@ -395,17 +410,21 @@ { "cell_type": "code", "execution_count": null, - "id": "9ff8518c", + "id": "48f45753", "metadata": {}, "outputs": [], "source": [ + "# if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", + "# result_folder = pathlib.Path(folder)\n", + "# else:\n", + "# result_folder = pathlib.Path(\"results_2spines\")\n", "result_folder = args[\"outdir\"]\n", "result_folder.mkdir(exist_ok=True)" ] }, { "cell_type": "markdown", - "id": "c6c6c160", + "id": "bb344685", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -414,7 +433,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c52dc906", + "id": "1c464008", "metadata": {}, "outputs": [], "source": [ @@ -440,7 +459,7 @@ }, { "cell_type": "markdown", - "id": "29bcb1a7", + "id": "5109ed7e", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -449,7 +468,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8908d043", + "id": "43e58e31", "metadata": {}, "outputs": [], "source": [ @@ -468,7 +487,7 @@ }, { "cell_type": "markdown", - "id": "9e927379", + "id": "c96723fa", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -479,7 +498,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a95a79d5", + "id": "61997de4", "metadata": {}, "outputs": [], "source": [ @@ -525,7 +544,7 @@ }, { "cell_type": "markdown", - "id": "d8e976c9", + "id": "069e90c3", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -534,7 +553,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b4ae2f79", + "id": "16f8a963", "metadata": {}, "outputs": [], "source": [ @@ -543,7 +562,7 @@ }, { "cell_type": "markdown", - "id": "cdf7e37e", + "id": "5318efb9", "metadata": {}, "source": [ "Initialize model and solver." @@ -552,7 +571,7 @@ { "cell_type": "code", "execution_count": null, - "id": "044275ca", + "id": "d7d3d83c", "metadata": {}, "outputs": [], "source": [ @@ -561,7 +580,8 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " \"final_t\": 0.025,\n", + " # \"final_t\": 0.025,\n", + " \"final_t\": 0.0004,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", @@ -573,7 +593,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f4125fcf", + "id": "c5730f69", "metadata": {}, "outputs": [], "source": [ @@ -590,7 +610,7 @@ }, { "cell_type": "markdown", - "id": "ce64365c", + "id": "c00e2661", "metadata": {}, "source": [ "Initialize model and set initial condition vector for NMDAR (set to 1.0 in the PSD). This is currently a very slow, inefficient method - we iterate through the mesh and save the coordinates originally tagged with marker=11 (these belong to the PSD). The associated entries of the dolfin vector for NMDAR are then set to 1.0." @@ -599,7 +619,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bb5305ad", + "id": "0fe80c58", "metadata": {}, "outputs": [], "source": [ @@ -636,7 +656,7 @@ " compare_vertices = np.sum((np.array(vertex_coords) - dof_coord[i, :]) ** 2, 1)\n", " if np.any(np.isclose(compare_vertices, 0)):\n", " values[indices[i]] = 1.0\n", - " print(f\"Completed value {i+1} of {len(dof_coord)}\")\n", + " # print(f\"Completed value {i+1} of {len(dof_coord)}\")\n", "\n", "uvec.set_local(values)\n", "uvec.apply(\"insert\")\n", @@ -649,7 +669,7 @@ }, { "cell_type": "markdown", - "id": "fe484b12", + "id": "25a19862", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -658,7 +678,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6a9e7f0d", + "id": "265b2db8", "metadata": {}, "outputs": [], "source": [ @@ -677,6 +697,12 @@ "\n", "concVec = np.array([model_cur.sc[\"Ca\"].initial_condition])\n", "tvec = np.array([0.0])\n", + "\n", + "# We are now done with setting up the model. Lets stop the timer and create a new timer to meausre the time spent solving\n", + "\n", + "timer_setup.stop()\n", + "timer_solve = d.Timer(\"Solve\")\n", + "\n", "# Solve\n", "displayed = False\n", "while True:\n", @@ -703,7 +729,32 @@ }, { "cell_type": "markdown", - "id": "3c6425fe", + "id": "6a4e605f", + "metadata": {}, + "source": [ + "Finally lets display the timings and save them for later" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41f41e5e", + "metadata": {}, + "outputs": [], + "source": [ + "timer_solve.stop()\n", + "res = d.timings(\n", + " d.TimingClear.keep,\n", + " [d.TimingType.wall, d.TimingType.user, d.TimingType.system],\n", + ")\n", + "logger.info(res.str(True))\n", + "# Save timings\n", + "(result_folder / \"timings.txt\").write_text(res.str(True))" + ] + }, + { + "cell_type": "markdown", + "id": "1a749c61", "metadata": {}, "source": [ "Plot calcium over time for this model." @@ -712,7 +763,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4a021470", + "id": "94708cae", "metadata": {}, "outputs": [], "source": [ @@ -729,9 +780,6 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" - }, - "language_info": { - "name": "python" } }, "nbformat": 4, diff --git a/ca2+-examples/preprocess_mesh.py b/ca2+-examples/preprocess_mesh.py index 8722cbc..27c869e 100644 --- a/ca2+-examples/preprocess_mesh.py +++ b/ca2+-examples/preprocess_mesh.py @@ -5,7 +5,8 @@ from smart import mesh_tools -mesh_file = Path.cwd().parent / "meshes" / "2spine_PM10_PSD11_ERM12_cyto1_ER2.xml" +mesh_file = Path.cwd().parent / "meshes" / "1spine_PM10_PSD11_ERM12_cyto1_ER2.xml" +# mesh_file = Path.cwd().parent / "meshes" / "2spine_PM10_PSD11_ERM12_cyto1_ER2.xml" print(f"Load mesh {mesh_file}") spine_mesh = d.Mesh(mesh_file.as_posix()) cell_markers = d.MeshFunction("size_t", spine_mesh, 3, spine_mesh.domains()) From c3064e4795d6a9302eea0e6ac6781ce76c5e4f89 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Sun, 17 Dec 2023 20:10:36 +0100 Subject: [PATCH 16/23] Add changes from emmet --- ca2+-examples/dendritic_spine.ipynb | 346 ++++++++++++++-------------- 1 file changed, 177 insertions(+), 169 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 72dfdb1..a724c07 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "1c9692ae", + "id": "5cd0d151", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,17 +30,16 @@ { "cell_type": "code", "execution_count": null, - "id": "3b56cc19", + "id": "79eefb82", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", - "import argparse\n", + "import os\n", "import pathlib\n", "import logging\n", - "from sympy.utilities.lambdify import lambdify\n", "\n", "from smart import config, mesh, model, mesh_tools, visualization\n", "from smart.units import unit\n", @@ -49,54 +48,32 @@ " Parameter,\n", " Reaction,\n", " Species,\n", + " SpeciesContainer,\n", + " CompartmentContainer,\n", " sbmodel_from_locals,\n", ")\n", "\n", "from matplotlib import pyplot as plt\n", "\n", "logger = logging.getLogger(\"smart\")\n", - "logger.setLevel(logging.DEBUG)\n", - "\n", - "parser = argparse.ArgumentParser()\n", - "parser.add_argument(\n", - " \"--mesh-file\",\n", - " type=pathlib.Path,\n", - " default=pathlib.Path.cwd() / \"ellipseSpine_mesh\" / \"ellipseSpine_mesh.h5\",\n", - ")\n", - "parser.add_argument(\n", - " \"-o\", \"--outdir\", type=pathlib.Path, default=pathlib.Path(\"results_2spines\")\n", - ")\n", - "args = vars(parser.parse_args())" + "logger.setLevel(logging.DEBUG)" ] }, { "cell_type": "markdown", - "id": "a3a9f250", + "id": "6771377d", "metadata": {}, "source": [ - "Let add a timer to measure how long time it takes to set ut the model" + "First, we define the various units for the inputs" ] }, { "cell_type": "code", "execution_count": null, - "id": "8073de0e", - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [], - "source": [ - "timer_setup = d.Timer(\"Setup model\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e9d3860", + "id": "339351eb", "metadata": {}, "outputs": [], "source": [ - "# First, we define the various units for the inputs\n", "# Aliases - base units\n", "uM = unit.uM\n", "um = unit.um\n", @@ -115,38 +92,79 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "32d6b79a", + "cell_type": "markdown", + "id": "f3296c0c", "metadata": {}, - "outputs": [], "source": [ - "# ## Load in mesh\n", - "mesh_file = args[\"mesh_file\"]" + "## Create and load in mesh\n", + "\n", + "Here, we consider an \"ellipsoid-in-an-ellipsoid\" geometry. The inner ellipsoid represents the SA and the volume between the SA boundary and the boundary of the outer ellipsoid represents the cytosol." ] }, { "cell_type": "code", "execution_count": null, - "id": "867e31cf", + "id": "44b17db3", "metadata": {}, "outputs": [], "source": [ + "spine_rad = 0.237\n", + "SA_rad = 0.08\n", + "ar_list = [27, 15, 7]\n", + "ar_1 = (1 / ((ar_list[1] / ar_list[0]) * (ar_list[2] / ar_list[0]))) ** (1 / 3)\n", + "ar_2 = ar_1 * ar_list[1] / ar_list[0]\n", + "ar_3 = ar_1 * ar_list[2] / ar_list[0]\n", + "z_PSD = 0.8 * spine_rad * ar_3\n", + "# spine_mesh, facet_markers, cell_markers = mesh_tools.create_ellipsoids(\n", + "# (spine_rad * ar_1, spine_rad * ar_2, spine_rad * ar_3),\n", + "# (SA_rad * ar_1, SA_rad * ar_2, SA_rad * ar_3),\n", + "# hEdge=0.02,\n", + "# )\n", + "# Load mesh\n", + "# spine_mesh = d.Mesh('spine_mesh.xml')\n", + "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " cur_dir = pathlib.Path(example_dir)\n", + "else:\n", + " cur_dir = pathlib.Path.cwd()\n", + "parent_dir = cur_dir.parent\n", + "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", + "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", + "facet_markers_orig = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", + "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", + "facet_array = facet_markers.array()[:]\n", + "for i in range(len(facet_array)):\n", + " if facet_array[i] == 11: # this indicates PSD\n", + " facet_array[i] = 10\n", + "\n", + "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", + " mesh_folder = pathlib.Path(folder)\n", + "else:\n", + " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", + "mesh_folder.mkdir(exist_ok=True)\n", + "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", + "if not bool(int(os.getenv(\"HPC\", 0))):\n", + " # Don't write mesh on HPC cluster\n", + " mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", + "\n", "parent_mesh = mesh.ParentMesh(\n", " mesh_filename=str(mesh_file),\n", " mesh_filetype=\"hdf5\",\n", " name=\"parent_mesh\",\n", ")\n", - "geo = mesh_tools.load_mesh(filename=args[\"mesh_file\"], mesh=parent_mesh.dolfin_mesh)\n", - "ds = d.Measure(\"ds\", domain=geo.mesh, subdomain_data=geo.mf_facet)\n", + "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", + "# for f in d.facets(spine_mesh):\n", + "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", + "\n", + "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", + "integrateDomain = facet_markers_orig\n", + "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", - "visualization.plot_dolfin_mesh(geo.mesh, geo.mf_cell)\n", - "# -" + "visualization.plot_dolfin_mesh(spine_mesh, cell_markers)" ] }, { "cell_type": "markdown", - "id": "8bcf2422", + "id": "a2db85a6", "metadata": {}, "source": [ "## Model generation\n", @@ -159,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fbd4c027", + "id": "f2cf4a74", "metadata": {}, "outputs": [], "source": [ @@ -168,12 +186,15 @@ "SA = Compartment(\"SA\", 3, um, 2)\n", "SAm = Compartment(\"SAm\", 2, um, 12)\n", "PM.specify_nonadjacency([\"SAm\", \"SA\"])\n", - "SAm.specify_nonadjacency([\"PM\"])" + "SAm.specify_nonadjacency([\"PM\"])\n", + "\n", + "cc = CompartmentContainer()\n", + "cc.add([Cyto, PM, SA, SAm])" ] }, { "cell_type": "markdown", - "id": "08f91934", + "id": "60a20c91", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -182,15 +203,16 @@ { "cell_type": "code", "execution_count": null, - "id": "5039a406", + "id": "a3508657", "metadata": {}, "outputs": [], "source": [ "Ca = Species(\"Ca\", 0.1, vol_unit, 220.0, D_unit, \"Cyto\")\n", "n_PMr = 0.1011 # vol to surf area ratio for a realistic dendritic spine\n", + "NMDAR_loc = f\"(1 + sign(z - {z_PSD}))/2\"\n", "NMDAR = Species(\n", - " \"NMDAR\", 0.0, dimensionless, 0.0, D_unit, \"PM\"\n", - ") # initial condition will be overwritten to localize NMDAR to PSD\n", + " \"NMDAR\", NMDAR_loc, dimensionless, 0.0, D_unit, \"PM\"\n", + ") # specify species to localize NMDAR calcium influx to PSD\n", "VSCC_zThresh = -10 # 0.3 #-0.25 for single spine, 0.3 for 2 spine\n", "VSCC_loc = f\"(1 + sign(z - {VSCC_zThresh}))/2\"\n", "VSCC = Species(\n", @@ -201,12 +223,14 @@ "Bm = Species(\"Bm\", 20.0, vol_unit, 20.0, D_unit, \"Cyto\")\n", "CaSA = Species(\n", " \"CaSA\", 60.0, vol_unit, 6.27, D_unit, \"SA\"\n", - ") # effective D due to buffering" + ") # effective D due to buffering\n", + "sc = SpeciesContainer()\n", + "sc.add([Ca, NMDAR, Bf, Bm, CaSA])" ] }, { "cell_type": "markdown", - "id": "dff1c8ff", + "id": "c40442b2", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -227,7 +251,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7343b969", + "id": "c411c91c", "metadata": { "lines_to_next_cell": 0 }, @@ -401,7 +425,7 @@ }, { "cell_type": "markdown", - "id": "969ee9a8", + "id": "a9d39ce7", "metadata": {}, "source": [ "Create a results folder" @@ -410,21 +434,20 @@ { "cell_type": "code", "execution_count": null, - "id": "48f45753", + "id": "981d5ea2", "metadata": {}, "outputs": [], "source": [ - "# if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", - "# result_folder = pathlib.Path(folder)\n", - "# else:\n", - "# result_folder = pathlib.Path(\"results_2spines\")\n", - "result_folder = args[\"outdir\"]\n", + "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", + " result_folder = pathlib.Path(folder)\n", + "else:\n", + " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", "result_folder.mkdir(exist_ok=True)" ] }, { "cell_type": "markdown", - "id": "bb344685", + "id": "b7bdb34d", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -433,10 +456,13 @@ { "cell_type": "code", "execution_count": null, - "id": "1c464008", + "id": "c7bacfbe", "metadata": {}, "outputs": [], "source": [ + "%matplotlib inline\n", + "from sympy.utilities.lambdify import lambdify\n", + "\n", "Vm_func = lambdify(t, Vm_expr, \"numpy\") # returns a numpy-ready function\n", "tArray = np.linspace(-0.01, 0.05, 600)\n", "fig, ax = plt.subplots(3, 1)\n", @@ -448,18 +474,18 @@ " t, A_PSD * J0_NMDAR_expr * G_NMDARVal * (Vm_expr - Vrest_expr), \"numpy\"\n", ")\n", "ax[1].plot(tArray, NMDAR_func(tArray))\n", - "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/s)\")\n", + "ax[1].set(xlabel=\"Time (s)\", ylabel=\"NMDAR flux\\n(molecules/(um^2*s))\")\n", "\n", "VSCC_func = lambdify(t, 0.8057 * VSCCNum * k_Ca * VSCC_biexp, \"numpy\")\n", "# VSCC_func = lambdify(t, VSCC_biexp, 'numpy')\n", "ax[2].plot(tArray, VSCC_func(tArray))\n", - "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/s)\")\n", + "ax[2].set(xlabel=\"Time (s)\", ylabel=\"VSCC flux\\n(molecules/(um^2*s))\")\n", "fig.savefig(result_folder / \"time_dependent.png\")" ] }, { "cell_type": "markdown", - "id": "5109ed7e", + "id": "17e44d95", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -468,7 +494,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43e58e31", + "id": "3987aad8", "metadata": {}, "outputs": [], "source": [ @@ -487,7 +513,7 @@ }, { "cell_type": "markdown", - "id": "c96723fa", + "id": "eeb2451e", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -498,7 +524,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61997de4", + "id": "c1156fe4", "metadata": {}, "outputs": [], "source": [ @@ -544,7 +570,7 @@ }, { "cell_type": "markdown", - "id": "069e90c3", + "id": "41ea5a82", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -553,16 +579,20 @@ { "cell_type": "code", "execution_count": null, - "id": "16f8a963", + "id": "bf02840a", "metadata": {}, "outputs": [], "source": [ + "# pc =ParameterContainer()\n", + "# pc.add([n_PMr])\n", + "# rc = ReactionContainer()\n", + "# rc.add([a1, a2, a3, a4, a5, b1, c1, c2])\n", "pc, sc, cc, rc = sbmodel_from_locals(locals().values())" ] }, { "cell_type": "markdown", - "id": "5318efb9", + "id": "ceb17b9c", "metadata": {}, "source": [ "Initialize model and solver." @@ -571,7 +601,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d7d3d83c", + "id": "3c46f8be", "metadata": {}, "outputs": [], "source": [ @@ -580,23 +610,14 @@ "model_cur = model.Model(pc, sc, cc, rc, configCur, parent_mesh)\n", "configCur.solver.update(\n", " {\n", - " # \"final_t\": 0.025,\n", - " \"final_t\": 0.0004,\n", + " \"final_t\": 0.025,\n", " \"initial_dt\": 0.0002,\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", + " # \"print_assembly\": False,\n", " }\n", ")\n", - "import json" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5730f69", - "metadata": {}, - "outputs": [], - "source": [ + "import json\n", "# Dump config to results folder\n", "(result_folder / \"config.json\").write_text(\n", " json.dumps(\n", @@ -605,71 +626,51 @@ " \"reaction_database\": configCur.reaction_database,\n", " }\n", " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "c00e2661", - "metadata": {}, - "source": [ - "Initialize model and set initial condition vector for NMDAR (set to 1.0 in the PSD). This is currently a very slow, inefficient method - we iterate through the mesh and save the coordinates originally tagged with marker=11 (these belong to the PSD). The associated entries of the dolfin vector for NMDAR are then set to 1.0." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0fe80c58", - "metadata": {}, - "outputs": [], - "source": [ - "model_cur.initialize(initialize_solver=False)\n", - "sp = model_cur.sc[\"NMDAR\"]\n", - "u = model_cur.cc[sp.compartment_name].u[\"u\"]\n", - "indices = sp.dof_map\n", - "uvec = u.vector()\n", - "values = uvec.get_local()\n", - "# store coordinates for current compartment\n", - "dof_coord = (\n", - " model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1, 3))\n", ")\n", - "dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", - "vertex_coords = [] # save all vertex coordinates that need to be set to 1.0 (in PSD)\n", - "idx = 0\n", - "for facet in d.facets(geo.mesh):\n", - " cur_marker = geo.mf_facet.array()[idx]\n", - " if cur_marker == 11:\n", - " for vertex in d.vertices(facet):\n", - " cur_vertex = list(vertex.point().array())\n", - " if len(vertex_coords) == 0: # first time in this loop\n", - " vertex_coords.append(cur_vertex)\n", - " else:\n", - " compare_vertices = np.sum(\n", - " (np.array(vertex_coords) - np.array(cur_vertex)) ** 2, 1\n", - " )\n", - " if not any(\n", - " np.isclose(compare_vertices, 0)\n", - " ): # unique point not seen yet\n", - " vertex_coords.append(cur_vertex)\n", - " idx = idx + 1\n", - "for i in range(len(dof_coord)):\n", - " compare_vertices = np.sum((np.array(vertex_coords) - dof_coord[i, :]) ** 2, 1)\n", - " if np.any(np.isclose(compare_vertices, 0)):\n", - " values[indices[i]] = 1.0\n", - " # print(f\"Completed value {i+1} of {len(dof_coord)}\")\n", "\n", - "uvec.set_local(values)\n", - "uvec.apply(\"insert\")\n", - "nvec = model_cur.cc[sp.compartment_name].u[\"n\"].vector()\n", - "nvec.set_local(values)\n", - "nvec.apply(\"insert\")\n", + "\n", + "model_cur.initialize(initialize_solver=False)\n", + "\n", + "# # set initial condition vector for NMDAR\n", + "# sp = model_cur.sc['NMDAR']\n", + "# u = model_cur.cc[sp.compartment_name].u[\"u\"]\n", + "# indices = sp.dof_map\n", + "# uvec = u.vector()\n", + "# values = uvec.get_local()\n", + "# dof_coord = model_cur.cc[sp.compartment_name].V.tabulate_dof_coordinates().reshape((-1,3)) # coordinates for current compartment\n", + "# dof_coord = dof_coord[indices, :] # pull out coordinates for the current dof\n", + "# vertex_coords = [] # save all vertex coordinates that need to be corrected\n", + "# idx = 0\n", + "# for facet in d.facets(spine_mesh):\n", + "# cur_marker = facet_markers_orig.array()[idx]\n", + "# if cur_marker == 10:\n", + "# for vertex in d.vertices(facet):\n", + "# cur_vertex = list(vertex.point().array())\n", + "# if len(vertex_coords)==0:\n", + "# vertex_coords.append(cur_vertex)\n", + "# else:\n", + "# compare_vertices = np.sum((np.array(vertex_coords)-np.array(cur_vertex))**2,1)\n", + "# if not any(np.isclose(compare_vertices,0)): # unique point not seen yet\n", + "# vertex_coords.append(cur_vertex)\n", + "# idx = idx + 1\n", + "# for i in range(len(dof_coord)):\n", + "# compare_vertices = np.sum((np.array(vertex_coords)-dof_coord[i,:])**2,1)\n", + "# if np.any(np.isclose(compare_vertices, 0)):\n", + "# values[indices[i]] = 0\n", + "# print(f'Completed value {i} of {len(dof_coord)}')\n", + "\n", + "# uvec.set_local(values)\n", + "# uvec.apply(\"insert\")\n", + "# nvec = model_cur.cc[sp.compartment_name].u[\"n\"].vector()\n", + "# nvec.set_local(values)\n", + "# nvec.apply(\"insert\")\n", "\n", "model_cur.initialize_discrete_variational_problem_and_solver()" ] }, { "cell_type": "markdown", - "id": "25a19862", + "id": "6df287da", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -678,7 +679,7 @@ { "cell_type": "code", "execution_count": null, - "id": "265b2db8", + "id": "ab63f3e7", "metadata": {}, "outputs": [], "source": [ @@ -693,16 +694,10 @@ "model_cur.to_pickle(result_folder / \"model_cur.pkl\")\n", "\n", "# Set loglevel to warning in order not to pollute notebook output\n", - "logger.setLevel(logging.WARNING)\n", + "# logger.setLevel(logging.WARNING)\n", "\n", "concVec = np.array([model_cur.sc[\"Ca\"].initial_condition])\n", "tvec = np.array([0.0])\n", - "\n", - "# We are now done with setting up the model. Lets stop the timer and create a new timer to meausre the time spent solving\n", - "\n", - "timer_setup.stop()\n", - "timer_solve = d.Timer(\"Solve\")\n", - "\n", "# Solve\n", "displayed = False\n", "while True:\n", @@ -729,49 +724,45 @@ }, { "cell_type": "markdown", - "id": "6a4e605f", + "id": "207c41b2", "metadata": {}, "source": [ - "Finally lets display the timings and save them for later" + "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." ] }, { "cell_type": "code", "execution_count": null, - "id": "41f41e5e", - "metadata": {}, + "id": "9cf59fdd", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [], "source": [ - "timer_solve.stop()\n", - "res = d.timings(\n", - " d.TimingClear.keep,\n", - " [d.TimingType.wall, d.TimingType.user, d.TimingType.system],\n", - ")\n", - "logger.info(res.str(True))\n", - "# Save timings\n", - "(result_folder / \"timings.txt\").write_text(res.str(True))" + "fig, ax = plt.subplots()\n", + "ax.plot(tvec, concVec)\n", + "ax.set_xlabel(\"Time (s)\")\n", + "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", + "fig.savefig(result_folder / \"ca2+-example.png\")" ] }, { "cell_type": "markdown", - "id": "1a749c61", + "id": "385e2983", "metadata": {}, "source": [ - "Plot calcium over time for this model." + "Calculate area under the curve (AUC)" ] }, { "cell_type": "code", "execution_count": null, - "id": "94708cae", + "id": "5fce2daa", "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "ax.plot(tvec, concVec)\n", - "ax.set_xlabel(\"Time (s)\")\n", - "ax.set_ylabel(\"Cytosolic calcium (μM)\")\n", - "fig.savefig(result_folder / \"ca2+-example.png\")" + "auc_cur = np.trapz(concVec, tvec)\n", + "print(auc_cur)" ] } ], @@ -780,6 +771,23 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "Python 3", + "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.10.6" } }, "nbformat": 4, From 6aaee579f0991fba88bf7cd7a70cbe78fa1ec45b Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Sun, 17 Dec 2023 20:20:33 +0100 Subject: [PATCH 17/23] Add back argument parser --- ca2+-examples/dendritic_spine.ipynb | 171 +++++++++++----------------- 1 file changed, 69 insertions(+), 102 deletions(-) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index a724c07..aef00b4 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "5cd0d151", + "id": "1d63cd5e", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "79eefb82", + "id": "e03193ac", "metadata": {}, "outputs": [], "source": [ @@ -40,6 +40,7 @@ "import os\n", "import pathlib\n", "import logging\n", + "import argparse\n", "\n", "from smart import config, mesh, model, mesh_tools, visualization\n", "from smart.units import unit\n", @@ -59,9 +60,31 @@ "logger.setLevel(logging.DEBUG)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dcf1cfc", + "metadata": {}, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "parser.add_argument(\n", + " \"--mesh-file\",\n", + " type=pathlib.Path,\n", + " default=pathlib.Path.cwd() / \"ellipseSpine_mesh\" / \"ellipseSpine_mesh.h5\"\n", + ")\n", + "parser.add_argument(\n", + " \"-o\",\n", + " \"--outdir\",\n", + " type=pathlib.Path,\n", + " default=pathlib.Path(\"results_2spines\")\n", + ")\n", + "args = vars(parser.parse_args())" + ] + }, { "cell_type": "markdown", - "id": "6771377d", + "id": "9b2c62b0", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -70,7 +93,7 @@ { "cell_type": "code", "execution_count": null, - "id": "339351eb", + "id": "6d0c1848", "metadata": {}, "outputs": [], "source": [ @@ -93,7 +116,7 @@ }, { "cell_type": "markdown", - "id": "f3296c0c", + "id": "68aca3a5", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -104,7 +127,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44b17db3", + "id": "069c1d1b", "metadata": {}, "outputs": [], "source": [ @@ -121,63 +144,35 @@ "# hEdge=0.02,\n", "# )\n", "# Load mesh\n", - "# spine_mesh = d.Mesh('spine_mesh.xml')\n", - "if (example_dir := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " cur_dir = pathlib.Path(example_dir)\n", - "else:\n", - " cur_dir = pathlib.Path.cwd()\n", - "parent_dir = cur_dir.parent\n", - "spine_mesh = d.Mesh(f\"{str(parent_dir)}/meshes/2spine_PM10_PSD11_ERM12_cyto1_ER2.xml\")\n", - "cell_markers = d.MeshFunction(\"size_t\", spine_mesh, 3, spine_mesh.domains())\n", - "facet_markers_orig = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", - "facet_markers = d.MeshFunction(\"size_t\", spine_mesh, 2, spine_mesh.domains())\n", - "facet_array = facet_markers.array()[:]\n", - "for i in range(len(facet_array)):\n", - " if facet_array[i] == 11: # this indicates PSD\n", - " facet_array[i] = 10\n", - "\n", - "if (folder := os.getenv(\"EXAMPLEDIR\")) is not None:\n", - " mesh_folder = pathlib.Path(folder)\n", - "else:\n", - " mesh_folder = pathlib.Path(\"ellipseSpine_mesh\")\n", - "mesh_folder.mkdir(exist_ok=True)\n", - "mesh_file = mesh_folder / \"ellipseSpine_mesh.h5\"\n", - "if not bool(int(os.getenv(\"HPC\", 0))):\n", - " # Don't write mesh on HPC cluster\n", - " mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, mesh_file)\n", - "\n", "parent_mesh = mesh.ParentMesh(\n", - " mesh_filename=str(mesh_file),\n", + " mesh_filename=str(args[\"mesh_file\"]),\n", " mesh_filetype=\"hdf5\",\n", " name=\"parent_mesh\",\n", ")\n", - "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, 0)\n", - "# for f in d.facets(spine_mesh):\n", - "# integrateDomain[f] = 11 if (f.midpoint().z() > z_PSD) else 0\n", + "geo = mesh_tools.load_mesh(filename=args[\"mesh_file\"], mesh=parent_mesh.dolfin_mesh)\n", + "facet_array = geo.mf_facet.array()[:]\n", + "for i in range(len(facet_array)):\n", + " if (\n", + " facet_array[i] == 11\n", + " ): # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", + " facet_array[i] = 10\n", "\n", - "# integrateDomain = d.MeshFunction(\"size_t\", spine_mesh, 2, facet_markers_orig)\n", - "integrateDomain = facet_markers_orig\n", - "ds = d.Measure(\"ds\", domain=spine_mesh, subdomain_data=integrateDomain)\n", + "integrateDomain = geo.mf_facet\n", + "ds = d.Measure(\"ds\", domain=geo.mesh, subdomain_data=integrateDomain)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", - "visualization.plot_dolfin_mesh(spine_mesh, cell_markers)" - ] - }, - { - "cell_type": "markdown", - "id": "a2db85a6", - "metadata": {}, - "source": [ - "## Model generation\n", + "visualization.plot_dolfin_mesh(geo.mesh, geo.mf_cell)\n", "\n", - "For each step of model generation, refer to Example 3 or API documentation for further details.\n", - "\n", - "We first define compartments and the compartment container. Note that we can specify nonadjacency for surfaces in the model, which is not required, but can speed up the solution process." + "# ## Model generation\n", + "#\n", + "# For each step of model generation, refer to Example 3 or API documentation for further details.\n", + "#\n", + "# We first define compartments and the compartment container. Note that we can specify nonadjacency for surfaces in the model, which is not required, but can speed up the solution process." ] }, { "cell_type": "code", "execution_count": null, - "id": "f2cf4a74", + "id": "65ecbfe3", "metadata": {}, "outputs": [], "source": [ @@ -194,7 +189,7 @@ }, { "cell_type": "markdown", - "id": "60a20c91", + "id": "20853a50", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -203,7 +198,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a3508657", + "id": "b7ee43e2", "metadata": {}, "outputs": [], "source": [ @@ -230,7 +225,7 @@ }, { "cell_type": "markdown", - "id": "c40442b2", + "id": "89011997", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -251,7 +246,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c411c91c", + "id": "8ffdded3", "metadata": { "lines_to_next_cell": 0 }, @@ -423,31 +418,20 @@ ")" ] }, - { - "cell_type": "markdown", - "id": "a9d39ce7", - "metadata": {}, - "source": [ - "Create a results folder" - ] - }, { "cell_type": "code", "execution_count": null, - "id": "981d5ea2", + "id": "d2832089", "metadata": {}, "outputs": [], "source": [ - "if (folder := os.getenv(\"RESULTSDIR\")) is not None:\n", - " result_folder = pathlib.Path(folder)\n", - "else:\n", - " result_folder = pathlib.Path(\"results_ellipseinellipse\")\n", - "result_folder.mkdir(exist_ok=True)" + "# Create a results folder\n", + "result_folder = args[\"outdir\"]" ] }, { "cell_type": "markdown", - "id": "b7bdb34d", + "id": "aac8a089", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -456,7 +440,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c7bacfbe", + "id": "0b443874", "metadata": {}, "outputs": [], "source": [ @@ -485,7 +469,7 @@ }, { "cell_type": "markdown", - "id": "17e44d95", + "id": "7519576b", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -494,7 +478,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3987aad8", + "id": "c3b7192e", "metadata": {}, "outputs": [], "source": [ @@ -513,7 +497,7 @@ }, { "cell_type": "markdown", - "id": "eeb2451e", + "id": "4e1eac97", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -524,7 +508,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c1156fe4", + "id": "bf9e191c", "metadata": {}, "outputs": [], "source": [ @@ -570,7 +554,7 @@ }, { "cell_type": "markdown", - "id": "41ea5a82", + "id": "1ce057ce", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -579,7 +563,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bf02840a", + "id": "bbd78db5", "metadata": {}, "outputs": [], "source": [ @@ -592,7 +576,7 @@ }, { "cell_type": "markdown", - "id": "ceb17b9c", + "id": "f42c933f", "metadata": {}, "source": [ "Initialize model and solver." @@ -601,7 +585,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3c46f8be", + "id": "e27f6147", "metadata": {}, "outputs": [], "source": [ @@ -670,7 +654,7 @@ }, { "cell_type": "markdown", - "id": "6df287da", + "id": "94a31e5b", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -679,7 +663,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ab63f3e7", + "id": "ba89cc8e", "metadata": {}, "outputs": [], "source": [ @@ -724,7 +708,7 @@ }, { "cell_type": "markdown", - "id": "207c41b2", + "id": "a55b2712", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -733,7 +717,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9cf59fdd", + "id": "c43a1a2b", "metadata": { "lines_to_next_cell": 2 }, @@ -748,7 +732,7 @@ }, { "cell_type": "markdown", - "id": "385e2983", + "id": "1b7b9e0b", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -757,7 +741,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5fce2daa", + "id": "2c7f4690", "metadata": {}, "outputs": [], "source": [ @@ -771,23 +755,6 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" - }, - "kernelspec": { - "display_name": "Python 3", - "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.10.6" } }, "nbformat": 4, From 89755d1fc7d33890e4d28e2997d818e65e84c5ea Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 19 Dec 2023 09:44:18 +0100 Subject: [PATCH 18/23] Add scripts for submitting to ex3 --- ca2+-examples/ca2_parser_args.py | 40 ++++++ ca2+-examples/dendritic_spine.ipynb | 183 +++++++++++++++++--------- ca2+-examples/pre_process_mesh.py | 31 +++++ ex3_scripts/dendritic_spine.sbatch | 28 ---- ex3_scripts/main.py | 191 ++++++++++++++++++++++++++++ ex3_scripts/run_all.sh | 9 ++ 6 files changed, 397 insertions(+), 85 deletions(-) create mode 100644 ca2+-examples/ca2_parser_args.py create mode 100644 ca2+-examples/pre_process_mesh.py delete mode 100644 ex3_scripts/dendritic_spine.sbatch create mode 100644 ex3_scripts/main.py create mode 100644 ex3_scripts/run_all.sh diff --git a/ca2+-examples/ca2_parser_args.py b/ca2+-examples/ca2_parser_args.py new file mode 100644 index 0000000..7a8b81c --- /dev/null +++ b/ca2+-examples/ca2_parser_args.py @@ -0,0 +1,40 @@ +import argparse +from pathlib import Path + +def add_run_dendritic_spine_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--mesh-file", + type=Path, + default=Path.cwd() / "ellipseSpine_mesh" / "ellipseSpine_mesh.h5" + ) + parser.add_argument( + "-o", + "--outdir", + type=Path, + default=Path("results_2spines") + ) + parser.add_argument( + "-n", + "--num-refinements", + type=int, + default=0, + ) + parser.add_argument( + "-dt", + "--time-step", + type=float, + default=0.0002, + ) + + +def add_preprocess_mesh_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--input-mesh-file", + type=Path, + default=Path.cwd().parent / "meshes_local" / "1spine_PM10_PSD11_ERM12_cyto1_ER2.xml" + ) + parser.add_argument( + "--output-mesh-file", + type=Path, + default=Path("ellipseSpine_mesh.h5") + ) \ No newline at end of file diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index aef00b4..b3edfdf 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "1d63cd5e", + "id": "d1604ec1", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,15 +30,13 @@ { "cell_type": "code", "execution_count": null, - "id": "e03193ac", + "id": "65c9e2b0", "metadata": {}, "outputs": [], "source": [ "import dolfin as d\n", "import sympy as sym\n", "import numpy as np\n", - "import os\n", - "import pathlib\n", "import logging\n", "import argparse\n", "\n", @@ -55,6 +53,7 @@ ")\n", "\n", "from matplotlib import pyplot as plt\n", + "from ca2_parser_args import add_run_dendritic_spine_arguments\n", "\n", "logger = logging.getLogger(\"smart\")\n", "logger.setLevel(logging.DEBUG)" @@ -63,28 +62,18 @@ { "cell_type": "code", "execution_count": null, - "id": "3dcf1cfc", + "id": "4ec8f896", "metadata": {}, "outputs": [], "source": [ "parser = argparse.ArgumentParser()\n", - "parser.add_argument(\n", - " \"--mesh-file\",\n", - " type=pathlib.Path,\n", - " default=pathlib.Path.cwd() / \"ellipseSpine_mesh\" / \"ellipseSpine_mesh.h5\"\n", - ")\n", - "parser.add_argument(\n", - " \"-o\",\n", - " \"--outdir\",\n", - " type=pathlib.Path,\n", - " default=pathlib.Path(\"results_2spines\")\n", - ")\n", + "add_run_dendritic_spine_arguments(parser)\n", "args = vars(parser.parse_args())" ] }, { "cell_type": "markdown", - "id": "9b2c62b0", + "id": "ac089e9c", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -93,7 +82,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6d0c1848", + "id": "a42ec47e", "metadata": {}, "outputs": [], "source": [ @@ -116,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "68aca3a5", + "id": "ca183e4c", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -127,7 +116,7 @@ { "cell_type": "code", "execution_count": null, - "id": "069c1d1b", + "id": "7c9b6757", "metadata": {}, "outputs": [], "source": [ @@ -150,17 +139,43 @@ " name=\"parent_mesh\",\n", ")\n", "geo = mesh_tools.load_mesh(filename=args[\"mesh_file\"], mesh=parent_mesh.dolfin_mesh)\n", - "facet_array = geo.mf_facet.array()[:]\n", - "for i in range(len(facet_array)):\n", - " if (\n", - " facet_array[i] == 11\n", - " ): # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", - " facet_array[i] = 10\n", - "\n", - "integrateDomain = geo.mf_facet\n", - "ds = d.Measure(\"ds\", domain=geo.mesh, subdomain_data=integrateDomain)\n", + "# facet_markers = dolfin.M\n", + "# facet_array = geo.mf_facet.array()[:]\n", + "# for i in range(len(facet_array)):\n", + "# if (\n", + "# facet_array[i] == 11\n", + "# ): # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", + "# facet_array[i] = 10\n", + "mesh = geo.mesh\n", + "\n", + "mf_cell = geo.mf_cell\n", + "mf_facet = geo.mf_facet\n", + "\n", + "if args[\"num_refinements\"] > 0:\n", + " print(\n", + " f\"Original mesh has {mesh.num_cells()} cells, \"\n", + " f\"{mesh.num_facets()} facets and \"\n", + " f\"{mesh.num_vertices()} vertices\"\n", + " )\n", + " d.parameters[\"refinement_algorithm\"] = \"plaza_with_parent_facets\"\n", + " for _ in range(args[\"num_refinements\"]):\n", + " mesh = d.adapt(mesh)\n", + " mf_cell = d.adapt(mf_cell, mesh)\n", + " mf_facet = d.adapt(mf_facet, mesh)\n", + " print(\n", + " f\"Original mesh has {mesh.num_cells()} cells, \"\n", + " f\"{mesh.num_facets()} facets and \"\n", + " f\"{mesh.num_vertices()} vertices\"\n", + " )\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "integrateDomain = mf_facet\n", + "ds = d.Measure(\"ds\", domain=mesh, subdomain_data=integrateDomain)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", - "visualization.plot_dolfin_mesh(geo.mesh, geo.mf_cell)\n", + "visualization.plot_dolfin_mesh(mesh, mf_cell)\n", "\n", "# ## Model generation\n", "#\n", @@ -172,7 +187,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65ecbfe3", + "id": "d2dbcc5f", "metadata": {}, "outputs": [], "source": [ @@ -189,7 +204,7 @@ }, { "cell_type": "markdown", - "id": "20853a50", + "id": "ae2eea6c", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -198,7 +213,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b7ee43e2", + "id": "9c817dd1", "metadata": {}, "outputs": [], "source": [ @@ -225,7 +240,7 @@ }, { "cell_type": "markdown", - "id": "89011997", + "id": "467e6f51", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -246,7 +261,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8ffdded3", + "id": "69f67941", "metadata": { "lines_to_next_cell": 0 }, @@ -421,17 +436,18 @@ { "cell_type": "code", "execution_count": null, - "id": "d2832089", + "id": "638b88a5", "metadata": {}, "outputs": [], "source": [ "# Create a results folder\n", - "result_folder = args[\"outdir\"]" + "result_folder = args[\"outdir\"]\n", + "result_folder.mkdir(exist_ok=True)" ] }, { "cell_type": "markdown", - "id": "aac8a089", + "id": "ec6d88cc", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -440,7 +456,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0b443874", + "id": "fcaf07f0", "metadata": {}, "outputs": [], "source": [ @@ -449,6 +465,7 @@ "\n", "Vm_func = lambdify(t, Vm_expr, \"numpy\") # returns a numpy-ready function\n", "tArray = np.linspace(-0.01, 0.05, 600)\n", + "\n", "fig, ax = plt.subplots(3, 1)\n", "fig.set_size_inches(15, 10)\n", "ax[0].plot(tArray, Vm_func(tArray))\n", @@ -469,7 +486,7 @@ }, { "cell_type": "markdown", - "id": "7519576b", + "id": "1fc3fe1a", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -478,7 +495,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c3b7192e", + "id": "38ef2f04", "metadata": {}, "outputs": [], "source": [ @@ -497,7 +514,7 @@ }, { "cell_type": "markdown", - "id": "4e1eac97", + "id": "934890c0", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -508,7 +525,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bf9e191c", + "id": "49396ad3", "metadata": {}, "outputs": [], "source": [ @@ -554,7 +571,7 @@ }, { "cell_type": "markdown", - "id": "1ce057ce", + "id": "0df8ed43", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -563,7 +580,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bbd78db5", + "id": "3fbda4ea", "metadata": {}, "outputs": [], "source": [ @@ -576,7 +593,7 @@ }, { "cell_type": "markdown", - "id": "f42c933f", + "id": "b11b966e", "metadata": {}, "source": [ "Initialize model and solver." @@ -585,7 +602,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e27f6147", + "id": "b595a8a8", "metadata": {}, "outputs": [], "source": [ @@ -595,7 +612,7 @@ "configCur.solver.update(\n", " {\n", " \"final_t\": 0.025,\n", - " \"initial_dt\": 0.0002,\n", + " \"initial_dt\": args[\"time_step\"],\n", " \"time_precision\": 8,\n", " \"use_snes\": True,\n", " # \"print_assembly\": False,\n", @@ -654,7 +671,7 @@ }, { "cell_type": "markdown", - "id": "94a31e5b", + "id": "bf5c8036", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -663,7 +680,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ba89cc8e", + "id": "1ad8e336", "metadata": {}, "outputs": [], "source": [ @@ -708,7 +725,7 @@ }, { "cell_type": "markdown", - "id": "a55b2712", + "id": "7b826b98", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -717,10 +734,8 @@ { "cell_type": "code", "execution_count": null, - "id": "c43a1a2b", - "metadata": { - "lines_to_next_cell": 2 - }, + "id": "b035ad7a", + "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", @@ -732,7 +747,28 @@ }, { "cell_type": "markdown", - "id": "1b7b9e0b", + "id": "f47f7a86", + "metadata": {}, + "source": [ + "Save results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3edab205", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "np.save(result_folder / \"tvec.npy\", tvec)\n", + "np.save(result_folder / \"concVec.npy\", concVec)" + ] + }, + { + "cell_type": "markdown", + "id": "59817fd7", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -741,13 +777,46 @@ { "cell_type": "code", "execution_count": null, - "id": "2c7f4690", + "id": "a15b21bf", "metadata": {}, "outputs": [], "source": [ "auc_cur = np.trapz(concVec, tvec)\n", "print(auc_cur)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0485922", + "metadata": {}, + "outputs": [], + "source": [ + "timings = d.timings(\n", + " d.TimingClear.keep,\n", + " [d.TimingType.wall, d.TimingType.user, d.TimingType.system],\n", + ").str(True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0babe405", + "metadata": {}, + "outputs": [], + "source": [ + "print(timings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa507e4f", + "metadata": {}, + "outputs": [], + "source": [ + "(result_folder / \"timings.txt\").write_text(timings)" + ] } ], "metadata": { diff --git a/ca2+-examples/pre_process_mesh.py b/ca2+-examples/pre_process_mesh.py new file mode 100644 index 0000000..94c6447 --- /dev/null +++ b/ca2+-examples/pre_process_mesh.py @@ -0,0 +1,31 @@ +from pathlib import Path +import argparse +import dolfin as d + +from smart import mesh_tools +import ca2_parser_args + +def main(input_mesh_file, output_mesh_file): + if not Path(input_mesh_file).is_file(): + raise FileNotFoundError(f"File {input_mesh_file} does not exists") + + + spine_mesh = d.Mesh(Path(input_mesh_file).as_posix()) + cell_markers = d.MeshFunction("size_t", spine_mesh, 3, spine_mesh.domains()) + facet_markers = d.MeshFunction("size_t", spine_mesh, 2, spine_mesh.domains()) + + # for i in range(len(facet_array)): + # if ( + # facet_array[i] == 11 + # ): # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM + # facet_array[i] = 10 + + Path(output_mesh_file).parent.mkdir(exist_ok=True, parents=True) + mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, output_mesh_file) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + ca2_parser_args.add_preprocess_mesh_arguments(parser) + args = vars(parser.parse_args()) + raise SystemExit(main(**args)) \ No newline at end of file diff --git a/ex3_scripts/dendritic_spine.sbatch b/ex3_scripts/dendritic_spine.sbatch deleted file mode 100644 index 0a40f02..0000000 --- a/ex3_scripts/dendritic_spine.sbatch +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -#SBATCH --job-name="dendritic_spine" -#SBATCH --partition=defq -#SBATCH --time=3-00:00:00 -#SBATCH --nodes=1 -#SBATCH --ntasks=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --output=%j-%x-stdout.txt -#SBATCH --error=%j-%x-stderr.txt - - -module use /cm/shared/ex3-modules/202309a/defq/modulefiles -module load python-fenics-dolfin-2019.2.0.dev0 - -LIBRARY_ROOT=$(realpath $(pwd)/..) -EXAMPLEDIR=$LIBRARY_ROOT/ca2+-examples -export OMPI_MCA_btl='^ofi' # James told me to set this to remove a warning - -SCRATCH_DIRECTORY=/global/D1/homes/${USER}/smart-comp-sci/dendritic_spine/${SLURM_JOBID} -mkdir -p ${SCRATCH_DIRECTORY} -echo "Scratch directory: ${SCRATCH_DIRECTORY}" -export RESULTSDIR=$SCRATCH_DIRECTORY - -echo "Run command: ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py" -# Assuming that you installed the software in a virutal environment in ${LIBRARY_ROOT}/venv -RESULTSDIR=$SCRATCH_DIRECTORY EXAMPLEDIR=$EXAMPLEDIR HPC=1 ${LIBRARY_ROOT}/venv/bin/python3 ${LIBRARY_ROOT}/ca2+-examples/dendritic_spine.py -# Move log file -mv ${SLURM_JOBID}-* ${SCRATCH_DIRECTORY} diff --git a/ex3_scripts/main.py b/ex3_scripts/main.py new file mode 100644 index 0000000..41c5b17 --- /dev/null +++ b/ex3_scripts/main.py @@ -0,0 +1,191 @@ +import sys +from textwrap import dedent +from pathlib import Path +import argparse +import shlex +import tempfile +import time +import subprocess as sp + +ex3_template = dedent( + """#!/bin/bash +#SBATCH --job-name="{job_name}" +#SBATCH --partition=defq +#SBATCH --time=3-00:00:00 +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --output=%j-%x-stdout.txt +#SBATCH --error=%j-%x-stderr.txt + + +module use /cm/shared/ex3-modules/202309a/defq/modulefiles +module load python-fenics-dolfin-2019.2.0.dev0 + +SCRATCH_DIRECTORY=/global/D1/homes/${{USER}}/smart-comp-sci/{job_name}/${{SLURM_JOBID}} +mkdir -p ${{SCRATCH_DIRECTORY}} +echo "Scratch directory: ${{SCRATCH_DIRECTORY}}" + +echo 'Run command: {python} {script} --outdir "${{SCRATCH_DIRECTORY}}" {args}' +{python} {script} --outdir "${{SCRATCH_DIRECTORY}}" {args} +# Move log file to results folder +mv ${{SLURM_JOBID}}-* ${{SCRATCH_DIRECTORY}} +""" +) + + +here = Path(__file__).parent.absolute() + + +def add_argument_dendritic_spine_example(parser: argparse.ArgumentParser): + sys.path.insert(0, (here / ".." / "ca2+-examples").as_posix()) + import ca2_parser_args + + ca2_parser_args.add_run_dendritic_spine_arguments(parser) + + +def add_argument_preprocess_spine_mesh(parser: argparse.ArgumentParser): + sys.path.insert(0, (here / ".." / "ca2+-examples").as_posix()) + import ca2_parser_args + + ca2_parser_args.add_preprocess_mesh_arguments(parser) + + +def run_preprocess_spine_mesh( + input_mesh_file: Path, output_mesh_file: Path, dry_run: bool, **kwargs +): + args = [ + "--input-mesh-file", + Path(input_mesh_file).as_posix(), + "--output-mesh-file", + Path(output_mesh_file).as_posix(), + ] + script = ( + (here / ".." / "ca2+-examples" / "pre_process_mesh.py") + .absolute() + .resolve() + .as_posix() + ) + args = list(map(str, args)) + if dry_run: + print(f"Run command: {sys.executable} {script} {' '.join(args)}") + return + + sp.run([sys.executable, script, *args]) + + +def run_dendritic_spine_example( + mesh_file: Path, + outdir: Path, + num_refinements: int, + time_step: float, + dry_run: bool = False, + submit_ex3: bool = False, + **kwargs, +): + args = [ + "--mesh-file", + Path(mesh_file).as_posix(), + "--num-refinements", + num_refinements, + "--time-step", + time_step, + ] + if not submit_ex3: + args.extend(["--outdir", Path(outdir).as_posix()]) + + script = ( + (here / ".." / "ca2+-examples" / "dendritic_spine.py") + .absolute() + .resolve() + .as_posix() + ) + # Turn all arguments into strings + args = list(map(str, args)) + args_str = shlex.join(args) + if dry_run: + print(f"Run command: {sys.executable} {script} {args_str}") + return + + if submit_ex3: + job_file = Path("tmp_job.sbatch") + job_file.write_text( + ex3_template.format( + job_name="dendritic-spine", + python=sys.executable, + script=script, + args=args_str, + ) + ) + + sp.run(["sbatch", job_file]) + job_file.unlink() + else: + sp.run([sys.executable, script, *args]) + + +def convert_notebooks(dry_run: bool = False, **kwargs): + import jupytext + + for example_folder in (here / "..").iterdir(): + # Only look in folders that contains the word example + if "example" not in example_folder.stem: + continue + + # Loop over all files in that folder + for f in example_folder.iterdir(): + if not f.suffix == ".ipynb": + continue + + print(f"Convert {f} to {f.with_suffix('.py')}") + if dry_run: + continue + + text = jupytext.reads(f.read_text()) + jupytext.write(text, f.with_suffix(".py")) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--dry-run", + action="store_true", + help="Just print the command and do not run it", + ) + parser.add_argument( + "--submit-ex3", + action="store_true", + help="Add this flag if you want to submit the job on the ex3 cluster", + ) + + subparsers = parser.add_subparsers(dest="command") + subparsers.add_parser("convert-notebooks", help="Convert notebooks to python files") + + preprocess_spine_mesh = subparsers.add_parser( + "preprocess-spine-mesh", help="Preprocess mesh for dendritic spine example" + ) + add_argument_preprocess_spine_mesh(preprocess_spine_mesh) + + dendritic_spine = subparsers.add_parser( + "dendritic-spine", help="Run dendritic spine example" + ) + add_argument_dendritic_spine_example(dendritic_spine) + + args = vars(parser.parse_args()) + # if args[""] + + if args["command"] == "convert-notebooks": + print("Convert notebook") + convert_notebooks(**args) + + elif args["command"] == "preprocess-spine-mesh": + print("Run preprocess dendritic spine mesh") + run_preprocess_spine_mesh(**args) + + elif args["command"] == "dendritic-spine": + print("Run dendritic spine example") + run_dendritic_spine_example(**args) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ex3_scripts/run_all.sh b/ex3_scripts/run_all.sh new file mode 100644 index 0000000..5389079 --- /dev/null +++ b/ex3_scripts/run_all.sh @@ -0,0 +1,9 @@ +python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh.h5 +# Base case +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0002 +# Run spatial convergence +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 1 --time-step 0.0002 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 2 --time-step 0.0002 +# Run temporal convergence +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0004 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0001 \ No newline at end of file From 2f9df82faf13ae599bb36f3a2a3c0f1d7352723e Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 19 Dec 2023 13:45:52 +0100 Subject: [PATCH 19/23] Dump info about run to config file --- ca2+-examples/dendritic_spine.ipynb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index b3edfdf..e5daeda 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -625,6 +625,10 @@ " {\n", " \"solver\": configCur.solver.__dict__,\n", " \"reaction_database\": configCur.reaction_database,\n", + " \"mesh_file\": str(args[\"mesh_file\"]),\n", + " \"outdir\": str(args[\"outdir\"]),\n", + " \"num_refinements\": args[\"num_refinements\"],\n", + " \"time_step\": args[\"time_step\"],\n", " }\n", " )\n", ")\n", @@ -824,6 +828,9 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" + }, + "language_info": { + "name": "python" } }, "nbformat": 4, From cfba4b5978e97193c2991de1385b6d07fac091a9 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Tue, 19 Dec 2023 18:32:44 +0100 Subject: [PATCH 20/23] Refine mesh before submitting to cluster --- ca2+-examples/ca2_parser_args.py | 12 ++-- ca2+-examples/dendritic_spine.ipynb | 101 +++++++++++----------------- ca2+-examples/pre_process_mesh.py | 22 +++++- ca2+-examples/preprocess_mesh.py | 21 ------ ex3_scripts/main.py | 11 +-- ex3_scripts/run_all.sh | 14 ++-- 6 files changed, 80 insertions(+), 101 deletions(-) delete mode 100644 ca2+-examples/preprocess_mesh.py diff --git a/ca2+-examples/ca2_parser_args.py b/ca2+-examples/ca2_parser_args.py index 7a8b81c..fbe17ab 100644 --- a/ca2+-examples/ca2_parser_args.py +++ b/ca2+-examples/ca2_parser_args.py @@ -13,12 +13,6 @@ def add_run_dendritic_spine_arguments(parser: argparse.ArgumentParser) -> None: type=Path, default=Path("results_2spines") ) - parser.add_argument( - "-n", - "--num-refinements", - type=int, - default=0, - ) parser.add_argument( "-dt", "--time-step", @@ -37,4 +31,10 @@ def add_preprocess_mesh_arguments(parser: argparse.ArgumentParser) -> None: "--output-mesh-file", type=Path, default=Path("ellipseSpine_mesh.h5") + ) + parser.add_argument( + "-n", + "--num-refinements", + type=int, + default=0, ) \ No newline at end of file diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index e5daeda..2691d40 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "d1604ec1", + "id": "ed86ef31", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65c9e2b0", + "id": "a5be77bc", "metadata": {}, "outputs": [], "source": [ @@ -62,7 +62,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4ec8f896", + "id": "1b269968", "metadata": {}, "outputs": [], "source": [ @@ -73,7 +73,7 @@ }, { "cell_type": "markdown", - "id": "ac089e9c", + "id": "c038983d", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -82,7 +82,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a42ec47e", + "id": "99979324", "metadata": {}, "outputs": [], "source": [ @@ -105,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "ca183e4c", + "id": "5f0e8c34", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -116,7 +116,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7c9b6757", + "id": "336809d3", "metadata": {}, "outputs": [], "source": [ @@ -146,36 +146,14 @@ "# facet_array[i] == 11\n", "# ): # this indicates PSD; in this case, set to 10 to indicate it is a part of the PM\n", "# facet_array[i] = 10\n", - "mesh = geo.mesh\n", "\n", - "mf_cell = geo.mf_cell\n", - "mf_facet = geo.mf_facet\n", - "\n", - "if args[\"num_refinements\"] > 0:\n", - " print(\n", - " f\"Original mesh has {mesh.num_cells()} cells, \"\n", - " f\"{mesh.num_facets()} facets and \"\n", - " f\"{mesh.num_vertices()} vertices\"\n", - " )\n", - " d.parameters[\"refinement_algorithm\"] = \"plaza_with_parent_facets\"\n", - " for _ in range(args[\"num_refinements\"]):\n", - " mesh = d.adapt(mesh)\n", - " mf_cell = d.adapt(mf_cell, mesh)\n", - " mf_facet = d.adapt(mf_facet, mesh)\n", - " print(\n", - " f\"Original mesh has {mesh.num_cells()} cells, \"\n", - " f\"{mesh.num_facets()} facets and \"\n", - " f\"{mesh.num_vertices()} vertices\"\n", - " )\n", "\n", "\n", "\n", - "\n", - "\n", - "integrateDomain = mf_facet\n", - "ds = d.Measure(\"ds\", domain=mesh, subdomain_data=integrateDomain)\n", + "integrateDomain = geo.mf_facet\n", + "ds = d.Measure(\"ds\", domain=geo.mesh, subdomain_data=integrateDomain)\n", "A_PSD = d.assemble(1.0 * ds(11))\n", - "visualization.plot_dolfin_mesh(mesh, mf_cell)\n", + "visualization.plot_dolfin_mesh(geo.mesh, geo.mf_cell)\n", "\n", "# ## Model generation\n", "#\n", @@ -187,7 +165,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d2dbcc5f", + "id": "17b9f287", "metadata": {}, "outputs": [], "source": [ @@ -204,7 +182,7 @@ }, { "cell_type": "markdown", - "id": "ae2eea6c", + "id": "8941cb6d", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -213,7 +191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9c817dd1", + "id": "c4db884e", "metadata": {}, "outputs": [], "source": [ @@ -240,7 +218,7 @@ }, { "cell_type": "markdown", - "id": "467e6f51", + "id": "95487d0b", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -261,7 +239,7 @@ { "cell_type": "code", "execution_count": null, - "id": "69f67941", + "id": "64c22d45", "metadata": { "lines_to_next_cell": 0 }, @@ -436,7 +414,7 @@ { "cell_type": "code", "execution_count": null, - "id": "638b88a5", + "id": "08b0aa41", "metadata": {}, "outputs": [], "source": [ @@ -447,7 +425,7 @@ }, { "cell_type": "markdown", - "id": "ec6d88cc", + "id": "fc64bc84", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -456,7 +434,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fcaf07f0", + "id": "3bfe204b", "metadata": {}, "outputs": [], "source": [ @@ -486,7 +464,7 @@ }, { "cell_type": "markdown", - "id": "1fc3fe1a", + "id": "0958fa14", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -495,7 +473,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38ef2f04", + "id": "634e1f5d", "metadata": {}, "outputs": [], "source": [ @@ -514,7 +492,7 @@ }, { "cell_type": "markdown", - "id": "934890c0", + "id": "4065dad4", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -525,7 +503,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49396ad3", + "id": "627b4e88", "metadata": {}, "outputs": [], "source": [ @@ -571,7 +549,7 @@ }, { "cell_type": "markdown", - "id": "0df8ed43", + "id": "c1f33609", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -580,7 +558,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3fbda4ea", + "id": "b053479c", "metadata": {}, "outputs": [], "source": [ @@ -593,7 +571,7 @@ }, { "cell_type": "markdown", - "id": "b11b966e", + "id": "c5d50daa", "metadata": {}, "source": [ "Initialize model and solver." @@ -602,7 +580,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b595a8a8", + "id": "2fe0212a", "metadata": {}, "outputs": [], "source": [ @@ -627,8 +605,8 @@ " \"reaction_database\": configCur.reaction_database,\n", " \"mesh_file\": str(args[\"mesh_file\"]),\n", " \"outdir\": str(args[\"outdir\"]),\n", - " \"num_refinements\": args[\"num_refinements\"],\n", " \"time_step\": args[\"time_step\"],\n", + " \n", " }\n", " )\n", ")\n", @@ -675,7 +653,7 @@ }, { "cell_type": "markdown", - "id": "bf5c8036", + "id": "3eee2d1c", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -684,7 +662,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1ad8e336", + "id": "85f83a86", "metadata": {}, "outputs": [], "source": [ @@ -729,7 +707,7 @@ }, { "cell_type": "markdown", - "id": "7b826b98", + "id": "9686197f", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -738,7 +716,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b035ad7a", + "id": "8a0f18e2", "metadata": {}, "outputs": [], "source": [ @@ -751,7 +729,7 @@ }, { "cell_type": "markdown", - "id": "f47f7a86", + "id": "58c9f878", "metadata": {}, "source": [ "Save results" @@ -760,7 +738,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3edab205", + "id": "04464f35", "metadata": { "lines_to_next_cell": 2 }, @@ -772,7 +750,7 @@ }, { "cell_type": "markdown", - "id": "59817fd7", + "id": "57982d9c", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -781,7 +759,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a15b21bf", + "id": "54c25342", "metadata": {}, "outputs": [], "source": [ @@ -792,7 +770,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f0485922", + "id": "1365aa87", "metadata": {}, "outputs": [], "source": [ @@ -805,7 +783,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0babe405", + "id": "d794ee9d", "metadata": {}, "outputs": [], "source": [ @@ -815,7 +793,7 @@ { "cell_type": "code", "execution_count": null, - "id": "aa507e4f", + "id": "2998acc5", "metadata": {}, "outputs": [], "source": [ @@ -828,9 +806,6 @@ "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" - }, - "language_info": { - "name": "python" } }, "nbformat": 4, diff --git a/ca2+-examples/pre_process_mesh.py b/ca2+-examples/pre_process_mesh.py index 94c6447..fc9244d 100644 --- a/ca2+-examples/pre_process_mesh.py +++ b/ca2+-examples/pre_process_mesh.py @@ -5,7 +5,7 @@ from smart import mesh_tools import ca2_parser_args -def main(input_mesh_file, output_mesh_file): +def main(input_mesh_file, output_mesh_file, num_refinements): if not Path(input_mesh_file).is_file(): raise FileNotFoundError(f"File {input_mesh_file} does not exists") @@ -14,6 +14,26 @@ def main(input_mesh_file, output_mesh_file): cell_markers = d.MeshFunction("size_t", spine_mesh, 3, spine_mesh.domains()) facet_markers = d.MeshFunction("size_t", spine_mesh, 2, spine_mesh.domains()) + + + if args["num_refinements"] > 0: + print( + f"Original mesh has {spine_mesh.num_cells()} cells, " + f"{spine_mesh.num_facets()} facets and " + f"{spine_mesh.num_vertices()} vertices" + ) + d.parameters["refinement_algorithm"] = "plaza_with_parent_facets" + for _ in range(args["num_refinements"]): + spine_mesh = d.adapt(spine_mesh) + cell_markers = d.adapt(cell_markers, spine_mesh) + facet_markers = d.adapt(facet_markers, spine_mesh) + print( + f"Refined mesh has {spine_mesh.num_cells()} cells, " + f"{spine_mesh.num_facets()} facets and " + f"{spine_mesh.num_vertices()} vertices" + ) + + # for i in range(len(facet_array)): # if ( # facet_array[i] == 11 diff --git a/ca2+-examples/preprocess_mesh.py b/ca2+-examples/preprocess_mesh.py deleted file mode 100644 index 27c869e..0000000 --- a/ca2+-examples/preprocess_mesh.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -import dolfin as d - -from smart import mesh_tools - - -mesh_file = Path.cwd().parent / "meshes" / "1spine_PM10_PSD11_ERM12_cyto1_ER2.xml" -# mesh_file = Path.cwd().parent / "meshes" / "2spine_PM10_PSD11_ERM12_cyto1_ER2.xml" -print(f"Load mesh {mesh_file}") -spine_mesh = d.Mesh(mesh_file.as_posix()) -cell_markers = d.MeshFunction("size_t", spine_mesh, 3, spine_mesh.domains()) -facet_markers = d.MeshFunction("size_t", spine_mesh, 2, spine_mesh.domains()) - - -mesh_folder = Path("ellipseSpine_mesh") -mesh_folder.mkdir(exist_ok=True) -new_mesh_file = mesh_folder / "ellipseSpine_mesh.h5" - -mesh_tools.write_mesh(spine_mesh, facet_markers, cell_markers, new_mesh_file) -print(f"Mesh saved to {new_mesh_file}") diff --git a/ex3_scripts/main.py b/ex3_scripts/main.py index 41c5b17..d83fb2f 100644 --- a/ex3_scripts/main.py +++ b/ex3_scripts/main.py @@ -52,13 +52,19 @@ def add_argument_preprocess_spine_mesh(parser: argparse.ArgumentParser): def run_preprocess_spine_mesh( - input_mesh_file: Path, output_mesh_file: Path, dry_run: bool, **kwargs + input_mesh_file: Path, + output_mesh_file: Path, + dry_run: bool, + num_refinements: int, + **kwargs, ): args = [ "--input-mesh-file", Path(input_mesh_file).as_posix(), "--output-mesh-file", Path(output_mesh_file).as_posix(), + "--num-refinements", + num_refinements, ] script = ( (here / ".." / "ca2+-examples" / "pre_process_mesh.py") @@ -77,7 +83,6 @@ def run_preprocess_spine_mesh( def run_dendritic_spine_example( mesh_file: Path, outdir: Path, - num_refinements: int, time_step: float, dry_run: bool = False, submit_ex3: bool = False, @@ -86,8 +91,6 @@ def run_dendritic_spine_example( args = [ "--mesh-file", Path(mesh_file).as_posix(), - "--num-refinements", - num_refinements, "--time-step", time_step, ] diff --git a/ex3_scripts/run_all.sh b/ex3_scripts/run_all.sh index 5389079..6f572e9 100644 --- a/ex3_scripts/run_all.sh +++ b/ex3_scripts/run_all.sh @@ -1,9 +1,11 @@ -python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh.h5 +python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_0.h5 --num-refinements 0 +python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_1.h5 --num-refinements 1 +python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_2.h5 --num-refinements 2 # Base case -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0002 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0002 # Run spatial convergence -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 1 --time-step 0.0002 -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 2 --time-step 0.0002 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_1.h5 --time-step 0.0002 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_2.h5 --time-step 0.0002 # Run temporal convergence -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0004 -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh.h5 --num-refinements 0 --time-step 0.0001 \ No newline at end of file +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0004 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0001 \ No newline at end of file From 622cb9f7917b9538dd351e2537de7a76db952bf6 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Wed, 20 Dec 2023 20:30:09 +0100 Subject: [PATCH 21/23] Make compatible with python3.7 and run on finer timestep --- ex3_scripts/main.py | 5 +---- ex3_scripts/run_all.sh | 8 +++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ex3_scripts/main.py b/ex3_scripts/main.py index d83fb2f..70e337d 100644 --- a/ex3_scripts/main.py +++ b/ex3_scripts/main.py @@ -2,9 +2,6 @@ from textwrap import dedent from pathlib import Path import argparse -import shlex -import tempfile -import time import subprocess as sp ex3_template = dedent( @@ -105,7 +102,7 @@ def run_dendritic_spine_example( ) # Turn all arguments into strings args = list(map(str, args)) - args_str = shlex.join(args) + args_str = " ".join(args) if dry_run: print(f"Run command: {sys.executable} {script} {args_str}") return diff --git a/ex3_scripts/run_all.sh b/ex3_scripts/run_all.sh index 6f572e9..14c674e 100644 --- a/ex3_scripts/run_all.sh +++ b/ex3_scripts/run_all.sh @@ -1,11 +1,13 @@ python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_0.h5 --num-refinements 0 python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_1.h5 --num-refinements 1 python3 main.py preprocess-spine-mesh --input-mesh-file /home/henriknf/local/src/smart-comp-sci/meshes_local/1spine_PM10_PSD11_ERM12_cyto1_ER2.xml --output-mesh-file meshes/ellipse1Spine_mesh_2.h5 --num-refinements 2 -# Base case +# # Base case python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0002 -# Run spatial convergence +# # Run spatial convergence python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_1.h5 --time-step 0.0002 python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_2.h5 --time-step 0.0002 # Run temporal convergence python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0004 -python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0001 \ No newline at end of file +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0001 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.00005 +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.00001 From a38c65fbdbb5c9384855b18b1a3331c4ec9808d3 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Sat, 23 Dec 2023 12:29:48 +0100 Subject: [PATCH 22/23] Add test for mass conservation --- ca2+-examples/ca2_parser_args.py | 4 ++ ca2+-examples/dendritic_spine.ipynb | 70 +++++++++++++++-------------- ex3_scripts/main.py | 5 +++ ex3_scripts/run_all.sh | 2 + 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/ca2+-examples/ca2_parser_args.py b/ca2+-examples/ca2_parser_args.py index fbe17ab..8113c91 100644 --- a/ca2+-examples/ca2_parser_args.py +++ b/ca2+-examples/ca2_parser_args.py @@ -19,6 +19,10 @@ def add_run_dendritic_spine_arguments(parser: argparse.ArgumentParser) -> None: type=float, default=0.0002, ) + parser.add_argument( + "--enforce-mass-conservation", + action='store_true' + ) def add_preprocess_mesh_arguments(parser: argparse.ArgumentParser) -> None: diff --git a/ca2+-examples/dendritic_spine.ipynb b/ca2+-examples/dendritic_spine.ipynb index 2691d40..c50e4a3 100644 --- a/ca2+-examples/dendritic_spine.ipynb +++ b/ca2+-examples/dendritic_spine.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "ed86ef31", + "id": "79bdc969", "metadata": {}, "source": [ "# Calcium dynamics in a dendritic spine\n", @@ -30,7 +30,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a5be77bc", + "id": "789f27b1", "metadata": {}, "outputs": [], "source": [ @@ -62,7 +62,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1b269968", + "id": "273911a1", "metadata": {}, "outputs": [], "source": [ @@ -73,7 +73,7 @@ }, { "cell_type": "markdown", - "id": "c038983d", + "id": "7381f072", "metadata": {}, "source": [ "First, we define the various units for the inputs" @@ -82,7 +82,7 @@ { "cell_type": "code", "execution_count": null, - "id": "99979324", + "id": "ab455f13", "metadata": {}, "outputs": [], "source": [ @@ -105,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "5f0e8c34", + "id": "7e72c081", "metadata": {}, "source": [ "## Create and load in mesh\n", @@ -116,7 +116,7 @@ { "cell_type": "code", "execution_count": null, - "id": "336809d3", + "id": "544f33af", "metadata": {}, "outputs": [], "source": [ @@ -165,7 +165,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17b9f287", + "id": "f2459cf9", "metadata": {}, "outputs": [], "source": [ @@ -182,7 +182,7 @@ }, { "cell_type": "markdown", - "id": "8941cb6d", + "id": "8d3a49b4", "metadata": {}, "source": [ "Define species and place them in a species container. Note that `NMDAR` and `VSCC` are stationary PM surface variables, effectively just serving the role restricting NMDAR calcium influx to the PSD and VSCC influx to the spine (not the dendritic shaft)" @@ -191,7 +191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c4db884e", + "id": "66fa90bd", "metadata": {}, "outputs": [], "source": [ @@ -218,7 +218,7 @@ }, { "cell_type": "markdown", - "id": "95487d0b", + "id": "1478589b", "metadata": {}, "source": [ "Define parameters and reactions at the plasma membrane:\n", @@ -239,7 +239,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64c22d45", + "id": "a2e99318", "metadata": { "lines_to_next_cell": 0 }, @@ -414,7 +414,7 @@ { "cell_type": "code", "execution_count": null, - "id": "08b0aa41", + "id": "e76feaeb", "metadata": {}, "outputs": [], "source": [ @@ -425,7 +425,7 @@ }, { "cell_type": "markdown", - "id": "fc64bc84", + "id": "f01fef01", "metadata": {}, "source": [ "We can plot the time-dependent stimulus from a1-a3 using lambdify." @@ -434,7 +434,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3bfe204b", + "id": "110567c8", "metadata": {}, "outputs": [], "source": [ @@ -464,7 +464,7 @@ }, { "cell_type": "markdown", - "id": "0958fa14", + "id": "b393a1b2", "metadata": {}, "source": [ "Now we define the cytosolic reactions. Here, there is only one reaction: mobile buffer binding calcium (b1). Note that because we assume that the buffering protein and the buffering protein bound to calcium have the same diffusion coefficient, we know that the total amount of buffering protein does not change over time or space, and we can write $[CaB_m] = B_{m,tot} - B_m$" @@ -473,7 +473,7 @@ { "cell_type": "code", "execution_count": null, - "id": "634e1f5d", + "id": "2c6c06c6", "metadata": {}, "outputs": [], "source": [ @@ -492,7 +492,7 @@ }, { "cell_type": "markdown", - "id": "4065dad4", + "id": "ba6eb279", "metadata": {}, "source": [ "Finally, we define reactions associated with the spine apparatus:\n", @@ -503,7 +503,7 @@ { "cell_type": "code", "execution_count": null, - "id": "627b4e88", + "id": "c45fd4d5", "metadata": {}, "outputs": [], "source": [ @@ -549,7 +549,7 @@ }, { "cell_type": "markdown", - "id": "c1f33609", + "id": "ec4010b0", "metadata": {}, "source": [ "Now we add all parameters and reactions to their SMART containers." @@ -558,7 +558,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b053479c", + "id": "bae6ce20", "metadata": {}, "outputs": [], "source": [ @@ -571,7 +571,7 @@ }, { "cell_type": "markdown", - "id": "c5d50daa", + "id": "996e8986", "metadata": {}, "source": [ "Initialize model and solver." @@ -580,7 +580,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2fe0212a", + "id": "f67699e5", "metadata": {}, "outputs": [], "source": [ @@ -596,12 +596,14 @@ " # \"print_assembly\": False,\n", " }\n", ")\n", + "configCur.flags.update({\"enforce_mass_conservation\": args[\"enforce_mass_conservation\"]})\n", "import json\n", "# Dump config to results folder\n", "(result_folder / \"config.json\").write_text(\n", " json.dumps(\n", " {\n", " \"solver\": configCur.solver.__dict__,\n", + " \"flags\": configCur.flags.__dict__,\n", " \"reaction_database\": configCur.reaction_database,\n", " \"mesh_file\": str(args[\"mesh_file\"]),\n", " \"outdir\": str(args[\"outdir\"]),\n", @@ -653,7 +655,7 @@ }, { "cell_type": "markdown", - "id": "3eee2d1c", + "id": "76501d44", "metadata": {}, "source": [ "Initialize XDMF files for saving results, save model information to .pkl file, then solve the system until `model_cur.t > model_cur.final_t`" @@ -662,7 +664,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85f83a86", + "id": "fc66c60e", "metadata": {}, "outputs": [], "source": [ @@ -707,7 +709,7 @@ }, { "cell_type": "markdown", - "id": "9686197f", + "id": "9b047af2", "metadata": {}, "source": [ "Plot results side-by-side with figure from original paper. This graph from the paper uses a spherical cell geometry, whereas we use an ellipsoidal case here, so we expect only qualitatively similar dynamics." @@ -716,7 +718,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8a0f18e2", + "id": "72426b50", "metadata": {}, "outputs": [], "source": [ @@ -729,7 +731,7 @@ }, { "cell_type": "markdown", - "id": "58c9f878", + "id": "d169abca", "metadata": {}, "source": [ "Save results" @@ -738,7 +740,7 @@ { "cell_type": "code", "execution_count": null, - "id": "04464f35", + "id": "80d413e1", "metadata": { "lines_to_next_cell": 2 }, @@ -750,7 +752,7 @@ }, { "cell_type": "markdown", - "id": "57982d9c", + "id": "f6eae5fc", "metadata": {}, "source": [ "Calculate area under the curve (AUC)" @@ -759,7 +761,7 @@ { "cell_type": "code", "execution_count": null, - "id": "54c25342", + "id": "ca70b03e", "metadata": {}, "outputs": [], "source": [ @@ -770,7 +772,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1365aa87", + "id": "f5f1283d", "metadata": {}, "outputs": [], "source": [ @@ -783,7 +785,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d794ee9d", + "id": "36739640", "metadata": {}, "outputs": [], "source": [ @@ -793,7 +795,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2998acc5", + "id": "1c62b95a", "metadata": {}, "outputs": [], "source": [ diff --git a/ex3_scripts/main.py b/ex3_scripts/main.py index 70e337d..914228f 100644 --- a/ex3_scripts/main.py +++ b/ex3_scripts/main.py @@ -63,6 +63,7 @@ def run_preprocess_spine_mesh( "--num-refinements", num_refinements, ] + script = ( (here / ".." / "ca2+-examples" / "pre_process_mesh.py") .absolute() @@ -81,6 +82,7 @@ def run_dendritic_spine_example( mesh_file: Path, outdir: Path, time_step: float, + enforce_mass_conservation: bool, dry_run: bool = False, submit_ex3: bool = False, **kwargs, @@ -91,6 +93,9 @@ def run_dendritic_spine_example( "--time-step", time_step, ] + if enforce_mass_conservation: + args.append("--enforce-mass-conservation") + if not submit_ex3: args.extend(["--outdir", Path(outdir).as_posix()]) diff --git a/ex3_scripts/run_all.sh b/ex3_scripts/run_all.sh index 14c674e..d9c791c 100644 --- a/ex3_scripts/run_all.sh +++ b/ex3_scripts/run_all.sh @@ -11,3 +11,5 @@ python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_me python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0001 python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.00005 python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.00001 +# Test mass conservation +python3 main.py --submit-ex3 dendritic-spine --mesh-file meshes/ellipse1Spine_mesh_0.h5 --time-step 0.0002 --enforce-mass-conservation \ No newline at end of file From 489a677aa723d5390407bfd6d393e3ed879e85a1 Mon Sep 17 00:00:00 2001 From: Henrik Nicolay Finsberg Date: Wed, 3 Jan 2024 12:24:38 +0100 Subject: [PATCH 23/23] Add script for analyzing the results from the dendritic spine example --- ex3_scripts/analyze_results.py | 166 +++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 ex3_scripts/analyze_results.py diff --git a/ex3_scripts/analyze_results.py b/ex3_scripts/analyze_results.py new file mode 100644 index 0000000..24f52dd --- /dev/null +++ b/ex3_scripts/analyze_results.py @@ -0,0 +1,166 @@ +from __future__ import annotations +# module use /cm/shared/ex3-modules/202309a/defq/modulefiles +# module load python-fenics-dolfin-2019.2.0.dev0 +# . ~/local/src/smart-comp-sci/venv/bin/activate +from pathlib import Path +import pandas as pd +import json +from typing import NamedTuple, Any +from itertools import cycle +import numpy as np +import datetime +import matplotlib.pyplot as plt + + +class Data(NamedTuple): + timings: pd.DataFrame + config: dict[str, Any] + t: np.ndarray + c: np.ndarray + + @property + def enforce_mass_conservation(self) -> bool: + return self.config.get("flags", {}).get("enforce_mass_conservation", False) + + @property + def num_refinements(self) -> int: + return int(Path(self.config["mesh_file"]).stem.split("_")[-1]) + + @property + def dt(self) -> float: + return self.config["solver"]["initial_dt"] + + @property + def total_run_time(self) -> float: + return self.timings[self.timings["name"] == "Total run time"]["wall tot"].values[0] + + +def load_all_data(main_path: str): + all_data = [] + for folder in (f for f in Path(main_path).iterdir() if f.is_dir()): + + try: + data = load_data(folder=folder) + except FileNotFoundError as e: + print(f"Skipping folder {folder}, due to {e}") + continue + all_data.append(data) + return all_data + +def is_default(d: Data) -> bool: + return np.isclose(d.dt, 0.0002) and not d.enforce_mass_conservation and d.num_refinements == 0 + +def plot_data(data: list[Data]): + fig, ax = plt.subplots(3, 1, sharex=True, sharey=True, figsize=(8, 8)) + fig_t, ax_t = plt.subplots(1, 3, sharey=True, figsize=(12, 4)) + linestyles = [cycle(["-", "--", ":", "-."]) for _ in range(3)] + + # Get the default data + default_data = next(d for d in data if is_default(d)) + + # Plot the temporal convergence + temporal_convergence_data = sorted([default_data] + [di for di in data if not is_default(di) and not np.isclose(di.dt, 0.0002)], key=lambda x: x.dt) + for d in temporal_convergence_data: + ax[0].plot(d.t, d.c, linestyle=next(linestyles[0]), label=f'dt={d.dt}') + + x = np.arange(len(temporal_convergence_data)) + ax_t[0].bar(x, [d.total_run_time for d in temporal_convergence_data]) + ax_t[0].set_xticks(x) + ax_t[0].set_xticklabels([d.dt for d in temporal_convergence_data]) + ax_t[0].set_xlabel("dt") + ax_t[0].set_title("Temporal convergence") + ax_t[0].set_ylabel("Time [s]") + + # Plot the spatial convergence + spatial_convergence_data = sorted([default_data] + [di for di in data if not is_default(di) and di.num_refinements > 0], key=lambda x: x.num_refinements) + for d in spatial_convergence_data: + ax[1].plot(d.t, d.c, linestyle=next(linestyles[1]), label=f'# refinements = {d.num_refinements}') + + x = np.arange(len(spatial_convergence_data)) + ax_t[1].bar(x, [d.total_run_time for d in spatial_convergence_data]) + ax_t[1].set_xticks(x) + ax_t[1].set_xticklabels([d.num_refinements for d in spatial_convergence_data]) + ax_t[1].set_xlabel("# refinements") + ax_t[1].set_title("Spatial convergence") + + + # Plot mass conservation + mass_conservation_data = next(d for d in data if d.enforce_mass_conservation) + ax[2].plot(default_data.t, default_data.c, linestyle=next(linestyles[2]), label='no mass conservation') + ax[2].plot(mass_conservation_data.t, mass_conservation_data.c, linestyle=next(linestyles[2]), label='mass conservation') + x = np.arange(2) + ax_t[2].bar(x, [default_data.total_run_time, mass_conservation_data.total_run_time]) + ax_t[2].set_xticks(x) + ax_t[2].set_xticklabels(["No", "Yes"]) + ax_t[2].set_xlabel("Mass conservation") + ax_t[2].set_title("Mass conservation") + + for axi in ax: + axi.legend() + axi.set_ylabel("[Ca$^{2+}$]") + + for axi in ax_t: + axi.grid() + axi.set_yscale("log") + axi.set_ylim(1e3, 3e5) + + ax[0].set_title("Temporal convergence") + ax[1].set_title("Spatial convergence (dt = 0.0002)") + ax[2].set_title("Mass conservation") + ax[2].set_xlabel("Time [s]") + + fig.savefig("ca2+results.png") + fig_t.savefig("timings.png") + + + +def load_timings(folder: Path): + timings = (folder / "timings.txt").read_text() + + # Read total run time from the start and end timestamp from the logs + logs = next(folder.glob(f"{folder.name}*stderr.txt")).read_text() + start_time = datetime.datetime.fromisoformat(logs.splitlines()[0].split(",")[0].strip()) + end_time = datetime.datetime.fromisoformat(logs.splitlines()[-1].split(",")[0].strip()) + total_run_time = (end_time - start_time).total_seconds() + + f = lambda x: len(x) > 0 and "|" not in x + + header = list(map(str.strip, filter(f, timings.splitlines()[0].split(" ")))) + header[0] = "name" + + data = [] + for item_str in timings.splitlines()[2:]: + item = list(map(str.strip, filter(f, item_str.split(" ")))) + data.append(dict(zip(header, item))) + + item = ["Total run time", 1] + [total_run_time] * (len(header) - 2) + data.append(dict(zip(header, item))) + return pd.DataFrame(data) + + + +def load_data(folder: Path = Path("82094")) -> Data: + + config_file = folder / "config.json" + if not config_file.is_file(): + raise FileNotFoundError(config_file) + + t_file = folder / "tvec.npy" + if not t_file.is_file(): + raise FileNotFoundError(t_file) + + t = np.load(t_file) + c = np.load(folder / "concVec.npy") + + config = json.loads(config_file.read_text()) + timings = load_timings(folder=folder) + + return Data(timings=timings, config=config, t=t, c=c) + +def main(): + data = load_all_data("/global/D1/homes/henriknf/smart-comp-sci/dendritic-spine") + plot_data(data) + + +if __name__ == "__main__": + main() \ No newline at end of file