diff --git a/runS1S2.py b/runS1S2.py new file mode 100755 index 0000000000000000000000000000000000000000..7504a5691938f10361cd6f60e74a69ec485f3ec6 --- /dev/null +++ b/runS1S2.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python + +""" +Overview +======== + +.. code-block:: bash + + cd ${TUTORIALS}/02_EP_tissue/01_basic_usage + +This example introduces to the basics of using the openCARP executable +for simulating EP at the tissue and organ scale. +Details on how to inquire input parameters are provided in the :ref:`openCARP <openCARP>` section +of the manual. + +In absence of pacemaker currents electrical stimulus currents must be supplied in order to +depolarize tissue above the threshold to elicit a propagated action potential. +The simplest way of initiating a propagating action potential is the use of a transmembrane stimulus, :math:`I_{\mathrm{tr}}`. +According to the definition in openCARP supplying a positive :math:`I_{\mathrm{tr}}` depolarizes the tissue, +a negative :math:`I_{\mathrm{tr}}` hyperpolarizes the tissue. + + +Experimental setup +================== + +To introduce definition and usage of transmembrane stimulus currents in openCARP a simple experiment +using a thin monolayer preparation was defined. The following input parameters are available to steer the experiment: + +.. code-block:: bash + + --duration + Duration of simulation (ms) + + --S1-strength + pick transmembrane current stimulus strength in + [uA/cm^2] = [pA/pF] considering fixed Cm (default: 20.) + + --S1-dur + pick transmembrane current stimulus duration in [ms + + +.. _tutorial-subthreshold-stimuation: + +Modeling subthreshold conditions +-------------------------------- + +.. _exp01-ep-ti-basic: + +**Experiment exp01 (subthreshold depolarizing stimulus)** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We start with a longer stimulus lasting for 15 ms and gradually increase the stimulus strength +until we achieve capture. This is equivalent to measuring one data point of a `strength-duration curve <https://en.wikipedia.org/wiki/Rheobase>`_ +to determine chronaxie and rheobase of our preparation. + +.. code-block:: bash + + ./run.py --duration 20 --S1-strength 20. --S1-dur 15 --visualize + +This weak current slowly depolarizes the membrane, but does not reach threshold. +After the end of stimulation, the membrane returns to its resting state, as shown in :numref:`fig-tutorial-subthreshold-depol-stim`. + +.. _fig-tutorial-subthreshold-depol-stim: + +.. figure:: /images/02_01_subthreshold_stim_response.png + :width: 50% + :align: center + + Membrane response to a subthresold *depolarizing* transmembrane stimulus. + +Inspect the variation in membrane depolarization as a function of distance to the stimulus location +by varying the vertex index in meshalyzer. + +To understand how the stimulus definition is passed on to the simulator we inspect the command line generated by the script. +For this sake run + +.. code-block:: bash + + ./run.py --duration 20 --S1-strength 20. --S1-dur 15 --dry + +The stimulus section of the command line is then + +.. code-block:: bash + + -num_stim 1 \ # use one stimulus electrode + -stimulus[0].name S1 \ + \ # pulse and type definition + -stimulus[0].stimtype 0 \ + -stimulus[0].strength 20.0 \ + -stimulus[0].duration 15.0 \ + \ # electrode definition (block) + -stimulus[0].x0 -5050.0 \ + -stimulus[0].xd 100.0 \ + -stimulus[0].y0 -550.0 \ + -stimulus[0].yd 1100.0 \ + -stimulus[0].z0 -150.0 \ + -stimulus[0].zd 300.0 + + + +**Experiment exp02 (subthreshold hyperpolarizing stimulus)** +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We repeat exp01 with a negative transmembrane stimulus current. + +.. code-block:: bash + + ./run.py --duration 20 --S1-strength -20 --S1-dur 15 --visualize + + +As the membrane behaves essentially only passively in the subthreshold regime +we observe the same response symmetric to the resting potential, see :numref:`fig-tutorial-subthreshold-hyper-stim` + +.. _fig-tutorial-subthreshold-hyper-stim: + +.. figure:: /images/02_01_subthreshold_stim_response_hyper.png + :width: 50% + :align: center + + Membrane response to a subthresold *hyperpolarizing* transmembrane stimulus. + + +.. _tutorial-suprathreshold-stimuation: + +Initiating action potential propagation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We repeat exp01 with a stronger transmembrane stimulus current of shorter duration. +Peak sodium current is known to be around :math:`250 \mu A/cm^2` and last for less than a millisecond. +We chose a strength of :math:`250 \mu A/cm^2` of 1 millisecond duration to achieve suprathreshold depolarization. +Keep in mind that *suprathreshold depolarization* at the stimulus site does not automatically lead to a propagated response. +The local membrane response at the stimulus location is shown in :numref:`fig-tutorial-suprathreshold-stim`. + +.. code-block:: bash + + ./run.py --duration 20 --S1-strength 250. --S1-dur 2 --visualize + + +.. _fig-tutorial-suprathreshold-stim: + +.. figure:: /images/02_01_suprathreshold_stim_response.png + :width: 50% + :align: center + + Membrane response to a suprathresold *depolarizing* transmembrane stimulus. + +""" +import os +EXAMPLE_DESCRIPTIVE_NAME = 'Basics of usage ' +EXAMPLE_AUTHOR = 'Gernot Plank <gernot.plank@medunigraz.at>' +EXAMPLE_DIR = os.path.dirname(__file__) +GUIinclude = True + +from datetime import date +from carputils import settings +from carputils import tools +from carputils import mesh +from carputils.carpio import txt +import numpy as np +from numpy import array as nplist + +#Joyce: Can add additional options for arguments here +def parser(): + parser = tools.standard_parser() + group = parser.add_argument_group('experiment specific options') + group.add_argument('--duration', + type = float, + default = 300., + help = 'Duration of simulation in [ms] (default: 20.)') + group.add_argument('--S1-strength', + type = float, + default = 172., + help = 'pick S1 transmembrane current stimulus strength in [uA/cm^2] = [pA/pF] considering fixed Cm (default: 20.)') + group.add_argument('--S1-dur', + type = float, + default = 2., + help = 'pick transmembrane current stimulus duration in [ms] (default: 2.)') + group.add_argument('--S1-start', + type = float, + default = 0.0, + help = 'pick S1 start time') + group.add_argument('--S2-strength', + type = float, + default = 200., + help = 'pick S2 transmembrane current stimulus strength in [uA/cm^2] = [pA/pF] considering fixed Cm (default: 20.)') + group.add_argument('--S2-dur', + type = float, + default = 2., + help = 'pick transmembrane current stimulus duration in [ms] (default: 2.)') + group.add_argument('--S2-start', + type = float, + default = 150.0, + help = 'pick S2 start time') + return parser + +def jobID(args): + """ + Generate name of top level output directory. + """ + today = date.today() + return '{}_basic_{}'.format(today.isoformat(), args.S1_strength) + +@tools.carpexample(parser, jobID) +def run(args, job): + + #=========================================== + # 1: Define the mesh: Units are mm + #=========================================== + + # Create a block with a thin z dimension + x = 10.0 #(mm) + y = 1.0 #(mm) + z = 0.2 #(mm) + geom = mesh.Block(size=(x,y,z)) + + # Set fibre angle to 0, sheet angle to 0 + geom.set_fibres(0, 0, 90, 90) #endo, epi, sheet endo, sheet epicard + + # Finilaize the creation of the mesh + meshname = mesh.generate(geom) + + + #=========================================== + # 2: Define the ionic models & conductivities + #=========================================== + + # Query for element labels + _, etags, _ = txt.read(meshname + '.elem') + etags = np.unique(etags) + + IntraTags = etags[etags != 0].tolist() + ExtraTags = etags.tolist().copy() + + #=========================================== + # 3: Configure the simulation + #=========================================== + + # Define the geometry of the stimulus at one end of the block + # Units are um + stim = ['-num_stim', 2, + '-stimulus[0].name', 'S1', #index 0 because only one type of electrode + '-stimulus[0].stimtype', 0, #0 means intracellular stim + '-stimulus[0].strength', args.S1_strength, #uA/Cm^2 + '-stimulus[0].duration', args.S1_dur, + '-stimulus[0].start', args.S1_start, + '-stimulus[1].name', 'S2', + '-stimulus[1].stimtype', 0, + '-stimulus[1].strength', args.S2_strength, + '-stimulus[1].duration', args.S2_dur, + '-stimulus[1].start', args.S2_start] + + #electrode = mesh.block_boundary_condition(geom, 'stimulus', 0, 'x', True) #True refers to a BC between tissue and bath + E1_lower_bound = nplist([ 0.0, 0.0, 0.0]) + E1_upper_bound = nplist([ 2.0, 0.3, 0.2]) + electrode = mesh.block_region(geom, 'stimulus', 0, E1_lower_bound, E1_upper_bound, False) # introudce a stimulus in the region defined by the lower and upper bounds + + #=========================================== + # 4: Defining simulator options: + #=========================================== + + # Get basic command line, including solver options + cmd = tools.carp_cmd(os.path.join(EXAMPLE_DIR, 'basic.par')) #cmd is a carp command + + cmd += ['-simID', job.ID, + '-meshname', meshname, + '-dt', 25, + '-tend', args.duration] + cmd += tools.gen_physics_opts(ExtraTags=ExtraTags, IntraTags=IntraTags) + cmd += stim + electrode + + # Run simulation + job.carp(cmd) #cmd includes the params + + + +if __name__ == '__main__': + run() diff --git a/run_with_actors.py b/run_with_actors.py new file mode 100644 index 0000000000000000000000000000000000000000..74e7245ebb182c2b8d66676c90acab1b8feddc9c --- /dev/null +++ b/run_with_actors.py @@ -0,0 +1,94 @@ +from thespian.actors import * +import runS1S2 +import datetime +import os +import re +from subprocess import Popen, PIPE +import subprocess +import numpy as np + + +class output_actor(Actor): + def receiveMessage(self, msg, sender): + S2_interval = msg[1] + # subprocess.run(msg) + file = msg[0][-1] + + lines = np.loadtxt(file) + desired_interval = lines[S2_interval:] + self.send(sender, bool(np.any(desired_interval > 0))) + +class Simulation_Manager(Actor): + def __init__(self): + # =========================================== + # Define the initial parameters for the S1S2 protocol + # =========================================== + self.S1_strength = 155.0 # Usually the S1 threshold (for this problem, we will pretend 155 is the S1 threshold) + self.t_final = 300 # duration of the experiment + self.S2_min = 150000 # Should be set to at least the S1 threshold. (150,000 is closer to the currently set S1-S2 intervals) + self.S2_absolute_max = 151000 # If unsure, set to maximum possible strength before solution blows up (for this problem, =225,000) + self.S2_max = self.S2_absolute_max # We want to retain the value of S2_absolute_max, so copy to S2_max to start with + self.tol = 1 # Find threshold up to a certain tolerance (typically 1 for these experiments). + self.S1S2_interval_min = 200 # User sets the range of desired S1-S2 time intervals + self.S1S2_interval_max = 220 + self.S1S2_interval_increment = 5 + self.S1S2_interval = self.S1S2_interval_max # S1S2 interval is set to max to start with + + self.nodes = 0 # Number of nodes in the mesh + self.actor_responses = [] + + def receiveMessage(self, message, sender): + if isinstance(message, str): + args = ["--S1-strength", str(self.S1_strength), "--S2-strength", str(self.S2_min - 1), \ + "--S2-start", str(self.S1S2_interval), "--duration", str(self.t_final)] + # runS1S2.run(args) + + # check if output directory exists + + output_folder = str(datetime.date.today().isoformat()) + "_basic_" + str(self.S1_strength) + if not os.path.exists(output_folder): + Exception("Output folder does not exist") + else: + # Find out how many nodes are in the mesh + # TODO: This is a hacky way to do this. Should be a better way to do this. + output_file = output_folder + "/vm.igb" + process = Popen(["/opt/openCARP/_build/bin/igbhead", output_file], stdout=PIPE) + (output, err) = process.communicate() + exit_code = process.wait() + string_output = output.decode("utf-8").split("\n") + nodes = re.findall(r"\d+", string_output[0]) + nodes = int(nodes[0]) + self.nodes = nodes + + # Inspect the mesh + + # Make a temporary directory to store the output values + temp_dir = output_folder + "/temp" + if not os.path.exists(temp_dir): + os.makedirs(temp_dir) + else: + Exception("Temporary directory already exists") + + + for i in range(0, self.nodes): + data_file = temp_dir + "/out_" + str(i) + ".txt" + cmd = (["/opt/openCARP/_build/bin/igbextract", "-o", "ascii", "-l", str(i), output_file, "-O", data_file], self.S1S2_interval) + actor = self.createActor(output_actor) + self.send(actor, cmd) + + elif isinstance(message, bool): + self.actor_responses.append(message) + if len(self.actor_responses) == self.nodes: + if not all(self.actor_responses): + print("APs not generated") + + + + +def actor_system_start(): + print("Starting actor system") + first = ActorSystem().createActor(Simulation_Manager) + ActorSystem().tell(first, "start") + +if __name__ == '__main__': + actor_system_start()