From 8c284a007cb49eb216e23a812d7c98b96f101325 Mon Sep 17 00:00:00 2001 From: Kyle <kyle.c.klenk@gmail.com> Date: Mon, 26 Jun 2023 23:11:01 +0000 Subject: [PATCH] Added C++ actor model to docker file Created basic actor code that can run s1 python file --- .gitignore | 6 + CMakeLists.txt | 15 +++ environment_setup/Dockerfile | 19 +++ find_s1.py | 111 ++++++++++++++++ find_s2.py | 131 +++++++++++++++++++ main.cpp | 75 +++++++++++ runS1S2.py | 158 +---------------------- run_with_actors.py | 15 ++- basic.par => simulations/input/basic.par | 0 test.py | 13 ++ 10 files changed, 383 insertions(+), 160 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 find_s1.py create mode 100644 find_s2.py create mode 100644 main.cpp rename basic.par => simulations/input/basic.par (100%) create mode 100644 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..276ff8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +my_program +output7.txt +simulations/output/* +build/ +meshes/ +output8.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..562f1f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.0) +project(my_project) + +find_package(CAF COMPONENTS core io REQUIRED) +find_package (PythonLibs REQUIRED) +include_directories (${PYTHON_INCLUDE_DIRS}) +add_executable(my_program main.cpp) +# Add this line to set the C++ 17 standard +set_property(TARGET my_program PROPERTY CXX_STANDARD 17) + +target_link_libraries(my_program CAF::core CAF::io) +target_link_libraries(my_program ${PYTHON_LIBRARIES}) + +# Move the executable up one directory +set_target_properties(my_program PROPERTIES RUNTIME_OUTPUT_DIRECTORY ..) diff --git a/environment_setup/Dockerfile b/environment_setup/Dockerfile index fc3b33c..70c80fe 100755 --- a/environment_setup/Dockerfile +++ b/environment_setup/Dockerfile @@ -18,6 +18,15 @@ RUN apt-get update -y && apt-get upgrade -y && \ python3-testresources python-is-python3 \ bc +# Install network tools - (ping, traceroute, etc.) +RUN apt-get -y install iputils-ping libssl-dev + + +# Install performance tools - (top, htop, etc.) +RUN apt-get -y install htop + +# Install development tools - (git, gcc, etc.) +RUN apt-get -y install gcc build-essential # Install PETSc WORKDIR /opt @@ -41,6 +50,16 @@ ENV PETSC_DIR=/usr/local/petsc ENV PATH=$PATH:$PETSC_DIR/bin ENV PATH=$PATH:/root/.local/bin +# Install common applications - (C++ Actor Framework) +WORKDIR /opt +WORKDIR /opt/c++_actor_framework + +RUN git clone https://github.com/actor-framework/actor-framework.git +WORKDIR /opt/c++_actor_framework/actor-framework/build +RUN cmake .. +RUN make +RUN make install + # Install OpenCARP WORKDIR /opt RUN git clone https://git.opencarp.org/openCARP/openCARP.git diff --git a/find_s1.py b/find_s1.py new file mode 100644 index 0000000..95b5eff --- /dev/null +++ b/find_s1.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +import os +import sys +NAME = 'Find_S1' +AUTHORS = 'Joyce Reimer, Kyle Klenk, Raymond Spiteri' +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 + +def parser_for_s1(): + parser = tools.standard_parser() + group = parser.add_argument_group('Experiment for finding S1 threshold') + group.add_argument('--duration', + type = float, + default = 20., + help = 'Duration of simulation in [ms] (default: 20.)') + group.add_argument('--S1-strength', + type = float, + default = 20., + help = 'pick 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.)') + return parser + +def jobID(args): + """ + Generate name of top level output directory. + """ + today = date.today() + return 'simulations/output/s1_{}_basic_{}'.format(today.isoformat(), args.S1_strength) + +@tools.carpexample(parser_for_s1, 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', 1, + '-stimulus[0].name', 'S1', + '-stimulus[0].stimtype', 0, + '-stimulus[0].strength', args.S1_strength, + '-stimulus[0].duration', args.S1_dur ] + + #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 + + #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('simulations/input/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 + + + +run() diff --git a/find_s2.py b/find_s2.py new file mode 100644 index 0000000..c522555 --- /dev/null +++ b/find_s2.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +import os +NAME = 'Find_S2' +AUTHORS = 'Joyce Reimer, Kyle Klenk, Raymond Spiteri' +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 + +def parser_for_s2(s1_strength): + 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 = s1_strength, + 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 'output/s2_{}_basic_{}'.format(today.isoformat(), args.S2_strength) + +@tools.carpexample(parser_for_s2, 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 + + #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('simulations/input/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 + +run() \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..dc5d3fe --- /dev/null +++ b/main.cpp @@ -0,0 +1,75 @@ +#include <iostream> +#include <fstream> +#include <chrono> +#include <thread> +#include <caf/all.hpp> +#include "caf/io/all.hpp" +#include <cstdio> +#include <sstream> + + + + +using namespace caf; + + +behavior mirror(event_based_actor* self) { + + + return { + [=](float duration, float s1_strength, float s1_duration) { + /* + Run the S1 Script + */ + std::stringstream ss; + ss << "python3 /code/find_s1.py" + << " --duration " << duration + << " --S1-strength " << s1_strength + << " --S1-dur " << s1_duration; + + FILE* pipe = popen(ss.str().c_str(), "r"); // create a pipe and run the + + if (!pipe) std::cerr << "popen() failed!" << std::endl; + char buffer[128]; + std::string result = ""; + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) + result += buffer; + } + pclose(pipe); // close the pipe + + // Log output to a txt file + std::stringstream ss_out; + ss_out << "/code/output" << self->id() << ".txt"; + + std::ofstream out(ss_out.str().c_str()); + out << result; + out.close(); + } + }; +} + + + + +void caf_main(actor_system& system) { + scoped_actor self{system}; + + + + auto actor_1 = self->spawn(mirror); + auto actor_2 = self->spawn(mirror); + + float duration = 20.0; + float s1_strength_min = 100.0; + float s1_duration = 2.0; + float s1_strength_max = 200.0; + + + self->send(actor_1, duration, s1_strength_min, s1_duration); + self->send(actor_2, duration, s1_strength_max, s1_duration); + +} + + +CAF_MAIN(io::middleman) diff --git a/runS1S2.py b/runS1S2.py index 01c5e89..d4d3874 100755 --- a/runS1S2.py +++ b/runS1S2.py @@ -1,154 +1,8 @@ #!/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__) +NAME = 'S1_S2_with_actors' +AUTHORS = 'Joyce Reimer, Kyle Klenk, Raymond Spiteri' GUIinclude = True from datetime import date @@ -198,11 +52,10 @@ def jobID(args): Generate name of top level output directory. """ today = date.today() - return '{}_basic_{}'.format(today.isoformat(), args.S2_strength) + return '{}_basic_{}/test'.format(today.isoformat(), args.S2_strength) @tools.carpexample(parser, jobID) def run(args, job): - #=========================================== # 1: Define the mesh: Units are mm #=========================================== @@ -211,7 +64,7 @@ def run(args, job): x = 10.0 #(mm) y = 1.0 #(mm) z = 0.2 #(mm) - geom = mesh.Block(size=(x,y,z)) + 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 @@ -219,7 +72,6 @@ def run(args, job): # Finilaize the creation of the mesh meshname = mesh.generate(geom) - #=========================================== # 2: Define the ionic models & conductivities #=========================================== @@ -259,7 +111,7 @@ def run(args, job): #=========================================== # Get basic command line, including solver options - cmd = tools.carp_cmd(os.path.join(EXAMPLE_DIR, 'basic.par')) #cmd is a carp command + cmd = tools.carp_cmd(os.path.join('simulations/input/basic.par')) #cmd is a carp command cmd += ['-simID', job.ID, '-meshname', meshname, diff --git a/run_with_actors.py b/run_with_actors.py index 06d2f07..f83560a 100644 --- a/run_with_actors.py +++ b/run_with_actors.py @@ -1,11 +1,14 @@ from thespian.actors import * -import runS1S2 +# import runS1S2 import datetime import os import re from subprocess import Popen, PIPE import subprocess import numpy as np +from carputils import tools + + class output_actor(Actor): @@ -27,7 +30,7 @@ class simulation_actor(Actor): class Simulation_Manager(Actor): - def __init__(self): + def __init__(self) # =========================================== # Define the initial parameters for the S1S2 protocol # =========================================== @@ -105,14 +108,12 @@ class Simulation_Manager(Actor): + def actor_system_start(): print("Starting actor system") - - asys = ActorSystem('multiprocTCPBase') - - + asys = ActorSystem() first = asys.createActor(Simulation_Manager) - asys.tell(first, "start") + # asys.tell(first, "start") if __name__ == '__main__': actor_system_start() diff --git a/basic.par b/simulations/input/basic.par similarity index 100% rename from basic.par rename to simulations/input/basic.par diff --git a/test.py b/test.py new file mode 100644 index 0000000..61224c8 --- /dev/null +++ b/test.py @@ -0,0 +1,13 @@ +from thespian.actors import * + +class MyActor(Actor): + def __init__(self, name): + self.name = name + + def receiveMessage(self, message, sender): + print(f"My name is {self.name} and I received {message} from {sender}") + +system = ActorSystem() + +actor = system.createActor(MyActor.__init__("test", 5)) +system.tell(actor, "Hello") \ No newline at end of file -- GitLab