diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 558b930330cbe7fa42c5c33d987004c6099535e7..7bf0902c54be06d4bde8180bbb3f31ceb134c949 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -75,7 +75,7 @@ jobs: mkdir build cd build cmake .. -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_WITH_PYTHON=ON -DPYTHON_EXECUTABLE=`which python` -DARB_WITH_MPI=${{ matrix.mpi }} - make -j4 tests examples html pyarb + make -j4 tests examples pyarb html cd - - name: Run unit tests run: build/bin/unit @@ -93,3 +93,5 @@ jobs: python python/example/single_cell_model.py python python/example/single_cell_recipe.py python python/example/single_cell_swc.py test/unit/swc/pyramidal.swc + python python/example/single_cell_detailed.py python/example/morph.swc + python python/example/single_cell_detailed_recipe.py python/example/morph.swc diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1b3f23f5d305268b8060193a8c7d3a9c5b9a89f9..5889d51a7252c576f888986ded087a7679efa678 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -20,7 +20,9 @@ string(REPLACE ";" "," theme_path "${theme_path}") add_custom_target(html COMMAND - ${SPHINX_EXECUTABLE} + PYTHONPATH=${CMAKE_BINARY_DIR}/python + ${PYTHON_EXECUTABLE} + -m sphinx -b html -d ${doctree_dir} -D "html_theme_path=${theme_path}" diff --git a/doc/concepts/recipe.rst b/doc/concepts/recipe.rst index 8223f321e3ee2987a23f8d9cab79855a9252222d..35ee8333a999f60c7d494babe0e86b44009749e4 100644 --- a/doc/concepts/recipe.rst +++ b/doc/concepts/recipe.rst @@ -4,18 +4,18 @@ Recipes ======= An Arbor *recipe* is a description of a model. The recipe is queried during the model -building phase to provide information about cells in the model, such as: +building phase to provide information about individual cells in the model, such as: * The **number of cells** in the model. * The **kind** of each cell. * The **description** of each cell, e.g. with morphology, dynamics, synapses, detectors, stimuli etc. - * The number of **spike targets**. - * The number of **spike sources**. - * The number of **gap junction sites**. + * The number of **spike targets** on each cell. + * The number of **spike sources** on each cell. + * The number of **gap junction sites** on each cell. * Incoming **network connections** from other cells terminating on a cell. - * **Gap junction connections** on a cell. - * **Probes** on a cell. + * **Gap junction connections** on each cell. + * **Probes** on each cell. To better illustrate the content of a recipe, let's consider the following network of three cells: diff --git a/doc/contrib/coding-style.rst b/doc/contrib/coding-style.rst index cd5b79a8905651450a524fc6be47be0bd5803c2f..21dd9c6d20c64dbcec97b058b94dc29d03696444 100644 --- a/doc/contrib/coding-style.rst +++ b/doc/contrib/coding-style.rst @@ -1,4 +1,4 @@ -.. _contribcodingstyle +.. _contribcodingstyle: Coding Guidelines ================= diff --git a/doc/index.rst b/doc/index.rst index 746b51dfab58a4466143d7e6d6cb2e42f6ed923c..0424bb7b259f6b34db56e1313e81fabd9e0d03c7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,7 +6,7 @@ Arbor Welcome to the documentation for Arbor, the multi-compartment neural network simulation library. -You can find out how to :ref:`get Arbor<in_install>`; get started quickly with our :ref:`tutorials<gs_other_examples>`; or continue reading to learn more about Arbor. +You can find out how to :ref:`get Arbor<in_install>`; get started quickly with our :ref:`tutorials<tutorial>`; or continue reading to learn more about Arbor. What is Arbor? -------------- @@ -23,7 +23,7 @@ Arbor is open source and openly developed, and we use development practices such Documentation organisation -------------------------- -* :ref:`gs_other_examples` contains a few ready-made examples you can use to quickly get started using Arbor. In the tutorial descriptions we link to the relevant Arbor concepts. +* :ref:`tutorial` contains a few ready-made examples you can use to quickly get started using Arbor. In the tutorial descriptions we link to the relevant Arbor concepts. * :ref:`modelintro` describes the design and concepts used in Arbor. The breakdown of concepts is mirrored (as much as possible) in the :ref:`pyoverview` and :ref:`cppoverview`, so you can easily switch between languages and concepts. * :ref:`hpc-overview` details Arbor-features for distribution of computation over supercomputer nodes. * :ref:`internals-overview` describes Arbor code that is not user-facing; convenience classes, architecture abstractions, etc. diff --git a/doc/install/python.rst b/doc/install/python.rst index 2ed52291b61d80e1c8c70338ec6c8f9a23f4081d..d515bf12df85843d097b9b9f47b95fcb61a9f6a8 100644 --- a/doc/install/python.rst +++ b/doc/install/python.rst @@ -41,7 +41,7 @@ to see information about the version and enabled features: >>> print(arbor.__config__) You are now ready to use Arbor! You can continue reading these documentation pages, have a look at the -:ref:`Python API reference<pyoverview>`, or visit the :ref:`gs_other_examples`. +:ref:`Python API reference<pyoverview>` , or visit the :ref:`Quick Start page<tutorialsimplecell>`. .. Note:: To get help in case of problems installing with pip, run pip with the ``--verbose`` flag, and attach the output diff --git a/doc/python/cable_cell.rst b/doc/python/cable_cell.rst index 03affce555da07ec6f7c1228d9fd1e04b76f0128..c0e2a7f7902b61fc5c8823cb2ada7784286c2916 100644 --- a/doc/python/cable_cell.rst +++ b/doc/python/cable_cell.rst @@ -207,7 +207,7 @@ Cable cells import arbor # Construct the morphology from an SWC file. - tree = arbor.load_swc('granule.swc') + tree = arbor.load_swc_arbor('granule.swc') morph = arbor.morphology(tree) # Define regions using standard SWC tags diff --git a/doc/python/morphology.rst b/doc/python/morphology.rst index c4e20bc1d381443f25ac6b203f25bf1e54a9e9b5..1dc0d9136c10bb27b3d04924b0c700f4bbf2f4b6 100644 --- a/doc/python/morphology.rst +++ b/doc/python/morphology.rst @@ -317,7 +317,7 @@ Cell morphology :param int i: branch index :rtype: list -.. py:function:: load_swc(filename) +.. py:function:: load_swc_arbor(filename) Loads the :class:`morphology` from an SWC file according to arbor's SWC specifications. (See the morphology concepts :ref:`page <morph-formats>` for more details). diff --git a/doc/scripts/gen-labels.py b/doc/scripts/gen-labels.py index 1f5b5c6df914dc66ad53ddeb96f377cc4d850888..22a5dd0aa660a17b66769c12fe0f1ba127286b49 100644 --- a/doc/scripts/gen-labels.py +++ b/doc/scripts/gen-labels.py @@ -159,7 +159,7 @@ tree.append(8, mpoint(-14.5,-0.1, 0.0, 0.5), tag=2) ysoma_morph3 = arbor.morphology(tree) fn = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__), "../concepts/example.swc")) -swc_morph = arbor.load_swc(fn) +swc_morph = arbor.load_swc_arbor(fn) regions = { 'empty': '(nil)', @@ -214,8 +214,49 @@ d = arbor.label_dict(labels) # Create a cell to concretise the region and locset definitions cell = arbor.cable_cell(label_morph, d, arbor.decor()) +############################################################################### +# Tutorial Example +############################################################################### + +tree = arbor.segment_tree() +tree.append(mnpos, mpoint(0, 0.0, 0, 2.0), mpoint( 4, 0.0, 0, 2.0), tag=1) +tree.append(0, mpoint(4, 0.0, 0, 0.8), mpoint( 8, 0.0, 0, 0.8), tag=3) +tree.append(1, mpoint(8, 0.0, 0, 0.8), mpoint(12, -0.5, 0, 0.8), tag=3) +tree.append(2, mpoint(12, -0.5, 0, 0.8), mpoint(20, 4.0, 0, 0.4), tag=3) +tree.append(3, mpoint(20, 4.0, 0, 0.4), mpoint(26, 6.0, 0, 0.2), tag=3) +tree.append(2, mpoint(12, -0.5, 0, 0.5), mpoint(19, -3.0, 0, 0.5), tag=3) +tree.append(5, mpoint(19, -3.0, 0, 0.5), mpoint(24, -7.0, 0, 0.2), tag=4) +tree.append(5, mpoint(19, -3.0, 0, 0.5), mpoint(23, -1.0, 0, 0.2), tag=4) +tree.append(7, mpoint(23, -1.0, 0, 0.2), mpoint(36, -2.0, 0, 0.2), tag=4) +tree.append(mnpos, mpoint(0, 0.0, 0, 2.0), mpoint(-7, 0.0, 0, 0.4), tag=2) +tree.append(9, mpoint(-7, 0.0, 0, 0.4), mpoint(-10, 0.0, 0, 0.4), tag=2) +tutorial_morph = arbor.morphology(tree) + +tutorial_regions = { + 'all': '(all)', + 'soma': '(tag 1)', + 'axon': '(tag 2)', + 'dend': '(tag 3)', + 'last': '(tag 4)', + 'rad_gt': '(radius-ge (region "all") 1.5)', + 'custom': '(join (region "last") (region "rad_gt"))' +} +tutorial_locsets = { + 'root': '(root)', + 'terminal': '(terminal)', + 'custom_terminal': '(restrict (locset "terminal") (region "custom"))', + 'axon_terminal': '(restrict (locset "terminal") (region "axon"))' + +} + +tutorial_labels = {**tutorial_regions, **tutorial_locsets} +tutorial_dict = arbor.label_dict(tutorial_labels) + +# Create a cell to concretise the region and locset definitions +tutorial_cell = arbor.cable_cell(tutorial_morph, tutorial_dict, arbor.decor()) + ################################################################################ -# Output all of the morphologies and reion/locset definitions to a Python script +# Output all of the morphologies and region/locset definitions to a Python script # that can be run during the documentation build to generate images. ################################################################################ f = open(sys.argv[1] + '/inputs.py', 'w') @@ -235,16 +276,27 @@ f.write(write_morphology('yshaped_morph', yshaped_morph)) f.write(write_morphology('ysoma_morph1', ysoma_morph1)) f.write(write_morphology('ysoma_morph2', ysoma_morph2)) f.write(write_morphology('ysoma_morph3', ysoma_morph3)) +f.write(write_morphology('tutorial_morph', tutorial_morph)) f.write(write_morphology('swc_morph', swc_morph)) -f.write('\n############# locsets\n\n') +f.write('\n############# locsets (label_morph)\n\n') for label in locsets: locs = [(l.branch, l.pos) for l in cell.locations('"{}"'.format(label))] f.write('ls_{} = {{\'type\': \'locset\', \'value\': {}}}\n'.format(label, locs)) -f.write('\n############# regions\n\n') +f.write('\n############# regions (label_morph)\n\n') for label in regions: comps = [(c.branch, c.prox, c.dist) for c in cell.cables('"{}"'.format(label))] f.write('reg_{} = {{\'type\': \'region\', \'value\': {}}}\n'.format(label, comps)) +f.write('\n############# locsets (tutorial_morph)\n\n') +for label in tutorial_locsets: + locs = [(l.branch, l.pos) for l in tutorial_cell.locations('"{}"'.format(label))] + f.write('tut_ls_{} = {{\'type\': \'locset\', \'value\': {}}}\n'.format(label, locs)) + +f.write('\n############# regions (tutorial_morph)\n\n') +for label in tutorial_regions: + comps = [(c.branch, c.prox, c.dist) for c in tutorial_cell.cables('"{}"'.format(label))] + f.write('tut_reg_{} = {{\'type\': \'region\', \'value\': {}}}\n'.format(label, comps)) + f.close() diff --git a/doc/scripts/make_images.py b/doc/scripts/make_images.py index da9f9d8936cf42994e2b98b5998e5bea2fc77e9e..69edbde370e2e8c4ade68bae64541505e50fea5a 100644 --- a/doc/scripts/make_images.py +++ b/doc/scripts/make_images.py @@ -3,7 +3,7 @@ import svgwrite import math import inputs -tag_colors = ['white', '#ffc2c2', 'gray', '#c2caff'] +tag_colors = ['white', '#ffc2c2', 'gray', '#c2caff', '#81c8aa'] # # ############################################ @@ -282,6 +282,20 @@ def generate(path=''): label_image(inputs.label_morph, [inputs.reg_radgt5], path+'/radiusgt_label.svg') label_image(inputs.label_morph, [inputs.reg_radge5], path+'/radiusge_label.svg') + ####################### Tutorial examples + + morph_image([inputs.tutorial_morph], ['segments'], path+'/tutorial_morph.svg') + + ####################### locsets + + label_image(inputs.tutorial_morph, [inputs.tut_ls_root, inputs.tut_ls_terminal], path+'/tutorial_root_term.svg') + label_image(inputs.tutorial_morph, [inputs.tut_ls_custom_terminal, inputs.tut_ls_axon_terminal], path+'/tutorial_custom_axon_term.svg') + + + ####################### regions + label_image(inputs.tutorial_morph, [inputs.tut_reg_soma, inputs.tut_reg_axon, inputs.tut_reg_dend, inputs.tut_reg_last], path+'/tutorial_tag.svg') + label_image(inputs.tutorial_morph, [inputs.tut_reg_all, inputs.tut_reg_rad_gt], path+'/tutorial_all_gt.svg') + label_image(inputs.tutorial_morph, [inputs.tut_reg_custom], path+'/tutorial_custom.svg') if __name__ == '__main__': generate('.') diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst index 77d30e81944219605f8a3fc5122fdb18b75e12dd..f52907ddf6a2241b91be1c2543b942854ac92bff 100644 --- a/doc/tutorial/index.rst +++ b/doc/tutorial/index.rst @@ -1,14 +1,25 @@ -.. _gs_other_examples: +.. _tutorial: Tutorials ========= .. Note:: - You can find some examples of full Arbor simulations in the ``python/examples`` directory of the `Arbor repository <https://github.com/arbor-sim/arbor>`_. - Note that some examples use ``pandas`` and ``seaborn`` for analysis and plotting which are expected to be installed independently from Arbor. + You can find some examples of full Arbor simulations in the ``python/examples`` directory of the + `Arbor repository <https://github.com/arbor-sim/arbor>`_. + + The examples use ``pandas`` and ``seaborn`` for analysis and plotting which are expected to be + installed independently from Arbor. + + In an interactive Python interpreter, you can use ``help()`` on any class or function to get its + documentation. (Try, ``help(arbor.simulation``, for example). + +.. Todo:: + Add more in-depth tutorial-like pages building up examples here. .. toctree:: :maxdepth: 1 :caption: Tutorials single_cell_model + single_cell_detailed + single_cell_detailed_recipe diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst new file mode 100644 index 0000000000000000000000000000000000000000..6558a5b0e6f9807bbac1d414c85b7bbda8fd1cd6 --- /dev/null +++ b/doc/tutorial/single_cell_detailed.rst @@ -0,0 +1,505 @@ +.. _tutorialsinglecellswc: + +A detailed single cell model +============================ + +We can expand on the :ref:`single segment cell example <tutorialsimplecell>` to create a more +complex single cell model, and go through the process in more detail. + +.. Note:: + + **Concepts covered in this example:** + + 1. Building a morphology from a :class:`arbor.segment_tree`. + 2. Building a morphology from an SWC file. + 3. Writing and visualizing region and locset expressions. + 4. Building a decor. + 5. Discretising the morphology. + 6. Setting and overriding model and cell parameters. + 7. Running a simulation and visualising the results using a :class:`arbor.single_cell_model`. + +We start by building the cell. This will be a :ref:`cable cell <cablecell>` with complex +geometry and dynamics which can be constructed from 3 components: + +1. A **morphology** defining the geometry of the cell. +2. A **label dictionary** storing labeled expressions which define regions and locations of + interest on the cell. +3. A **decor** defining various properties and dynamics on these labeled regions and locations. + The decor also includes hints about how the cell is to be modeled under the hood, by + splitting it into discrete control volumes (CV). + +Next, we construct a :class:`arbor.single_cell_model`. This model takes care of a lot of details +behind the scenes: it sets up a recipe (more on recipes :ref:`here <modelrecipe>`), creates +a simulation object, manages the hardware etc. These details become more important when modeling +a network of cells, but can be abstracted away when working with single cell networks. + +The single cell model has 4 main functions: + +1. It holds the **global properties** of the model +2. It registers **probes** on specific locations on the cell to measure the voltage. +3. It **runs** the simulation. +4. It collects **spikes** from spike detectors and voltage **traces** from registered probes. + +.. _tutorialsinglecellswc-cell: + +The cell +******** + +Before creating the actual cell object, we have to create its components. + +The morpholohgy +^^^^^^^^^^^^^^^ +We begin by constructing the following morphology: + +.. figure:: ../gen-images/tutorial_morph.svg + :width: 400 + :align: center + +This can be done by manually building a segment tree: + +.. code-block:: python + + import arbor + from arbor import mpoint + from arbor import mnpos + + # Define the morphology by manually building a segment tree + + tree = arbor.segment_tree() + + # Start with segment 0: a cylindrical soma with tag 1 + tree.append(mnpos, mpoint(0.0, 0.0, 0.0, 2.0), mpoint( 40.0, 0.0, 0.0, 2.0), tag=1) + # Construct the first section of the dendritic tree with tag 3, + # comprised of segments 1 and 2, attached to soma segment 0. + tree.append(0, mpoint(40.0, 0.0, 0.0, 0.8), mpoint( 80.0, 0.0, 0.0, 0.8), tag=3) + tree.append(1, mpoint(80.0, 0.0, 0.0, 0.8), mpoint(120.0, -5.0, 0.0, 0.8), tag=3) + # Construct the rest of the dendritic tree: segments 3, 4 and 5. + tree.append(2, mpoint(120.0, -5.0, 0.0, 0.8), mpoint(200.0, 40.0, 0.0, 0.4), tag=3) + tree.append(3, mpoint(200.0, 40.0, 0.0, 0.4), mpoint(260.0, 60.0, 0.0, 0.2), tag=3) + tree.append(2, mpoint(120.0, -5.0, 0.0, 0.5), mpoint(190.0, -30.0, 0.0, 0.5), tag=3) + # Construct a special region of the tree made of segments 6, 7, and 8 + # differentiated from the rest of the tree using tag 4. + tree.append(5, mpoint(190.0, -30.0, 0.0, 0.5), mpoint(240.0, -70.0, 0.0, 0.2), tag=4) + tree.append(5, mpoint(190.0, -30.0, 0.0, 0.5), mpoint(230.0, -10.0, 0.0, 0.2), tag=4) + tree.append(7, mpoint(230.0, -10.0, 0.0, 0.2), mpoint(360.0, -20.0, 0.0, 0.2), tag=4) + # Construct segments 9 and 10 that make up the axon with tag 2. + # Segment 9 is at the root, where its proximal end will be connected to the + # proximal end of the soma segment. + tree.append(mnpos, mpoint( 0.0, 0.0, 0.0, 2.0), mpoint( -70.0, 0.0, 0.0, 0.4), tag=2) + tree.append(9, mpoint(-70.0, 0.0, 0.0, 0.4), mpoint(-100.0, 0.0, 0.0, 0.4), tag=2) + + morph = arbor.morphology(tree); + +The same morphology can be represented using an SWC file (interpreted according +to :ref:`Arbor's specifications <morph-formats>`). We can save the following in +``morph.swc``. + +.. code-block:: python + + # id, tag, x, y, z, r, parent + 1 1 0.0 0.0 0.0 2.0 -1 # seg0 prox / seg9 prox + 2 1 40.0 0.0 0.0 2.0 1 # seg0 dist + 3 3 40.0 0.0 0.0 0.8 2 # seg1 prox + 4 3 80.0 0.0 0.0 0.8 3 # seg1 dist / seg2 prox + 5 3 120.0 -5.0 0.0 0.8 4 # seg2 dist / seg3 prox + 6 3 200.0 40.0 0.0 0.4 5 # seg3 dist / seg4 prox + 7 3 260.0 60.0 0.0 0.2 6 # seg4 dist + 8 3 120.0 -5.0 0.0 0.5 5 # seg5 prox + 9 3 190.0 -30.0 0.0 0.5 8 # seg5 dist / seg6 prox / seg7 prox + 10 4 240.0 -70.0 0.0 0.2 9 # seg6 dist + 11 4 230.0 -10.0 0.0 0.2 9 # seg7 dist / seg8 prox + 12 4 360.0 -20.0 0.0 0.2 11 # seg8 dist + 13 2 -70.0 0.0 0.0 0.4 1 # seg9 dist / seg10 prox + 14 2 -100.0 0.0 0.0 0.4 13 # seg10 dist + +.. Note:: + + SWC samples always form a segment with their parent sample. For example, + sample 3 and sample 2 form a segment which has length = 0. + We use these zero-length segments to represent an abrupt radius change + in the morphology, like we see between segment 0 and segment 1 in the above + morphology diagram. + + More information on SWC loaders can be found :ref:`here <morph-formats>`. + +The morphology can then be loaded from ``morph.swc`` in the following way: + +.. code-block:: python + + import arbor + + # Read the morphology from an SWC file + + morph = arbor.load_swc_arbor("morph.swc") + +The label dictionary +^^^^^^^^^^^^^^^^^^^^ + +Next, we can define **region** and **location** expressions and give them labels. +The regions and locations are defined using an Arbor-specific DSL, and the labels +can be stored in a :class:`arbor.lable_dict`. + +.. Note:: + + The expressions in the label dictionary don't actually refer to any concrete regions + or locations of the morphology at this point. They are merely descriptions that can be + applied to any morphology, and depending on its geometry, they will generate different + regions and locations. However, we will show some figures illustrating the effect of + applying these expressions to the above morphology, in order to better visualize the + final cell. + + More information on region and location expressions is available :ref:`here <labels>`. + +First, we can define some **regions**, These are continuous parts of the morphology, +They can correspond to full segments or parts of segments. Our morphology already has some +pre-established regions determined by the ``tag`` parameter of the segments. They are +defined as follows: + +.. code-block:: python + + #Create a label dictionary + + labels = arbor.label_dict() + + # Add labels for tag 1, 2, 3, 4 + labels['soma'] = '(tag 1)' + labels['axon'] = '(tag 2)' + labels['dend'] = '(tag 3)' + labels['last'] = '(tag 4)' + +This will generate the following regions when applied to the previously defined morphology: + +.. figure:: ../gen-images/tutorial_tag.svg + :width: 800 + :align: center + + From left to right: regions "soma", "axon", "dend" and "last" + +We can also define a region that represents the whole cell; and to make things a bit more interesting, +a region that includes the parts of the morphology that have a radius greater than 1.5 μm. This is done +in the following way: + +.. code-block:: python + + # Add a label for a region that includes the whole morphology + labels['all'] = '(all)' + + # Add a label for the parts of the morphology with radius greater than 1.5 μm. + labels['gt_1.5'] = '(radius-gt (region "all") 1.5)' + +This will generate the following regions when applied to the previously defined morphology: + +.. figure:: ../gen-images/tutorial_all_gt.svg + :width: 400 + :align: center + + Left: region "all"; right: region "gt_1.5" + +By comparing to the original morphology, we can see region "gt_1.5" includes all of segment 0 and part of +segment 9. + +Finally, let's define a region that includes two already defined regions: "last" and "gt_1.5". This can +be done as follows: + +.. code-block:: python + + # Join regions "last" and "gt_1.5" + labels['custom'] = '(join (region "last") (region "gt_1.5"))' + +This will generate the following region when applied to the previously defined morphology: + +.. figure:: ../gen-images/tutorial_custom.svg + :width: 200 + :align: center + +Our label dictionary so far only contains regions. We can also add some **locations**. Let's start +with a location that is the root of the morphology, and the set of locations that represent all the +terminal points of the morphology. + +.. code-block:: python + + # Add a labels for the root of the morphology and all the terminal points + labels['root'] = '(root)' + labels['terminal'] = '(terminal)' + +This will generate the following **locsets** (sets of one or more locations) when applied to the +previously defined morphology: + +.. figure:: ../gen-images/tutorial_root_term.svg + :width: 400 + :align: center + + Left: locset "root"; right: locset "terminal" + +To make things more interesting, let's select only the terminal points which belong to the +previously defined "custom" region; and, separately, the terminal points which belong to the +"axon" region: + +.. code-block:: python + + # Add a label for the terminal locations in the "custom" region: + labels['custom_terminal'] = '(restrict (locset "terminal") (region "custom"))' + + # Add a label for the terminal locations in the "axon" region: + labels['axon_terminal'] = '(restrict (locset "terminal") (region "axon"))' + +This will generate the following 2 locsets when applied to the previously defined morphology: + +.. figure:: ../gen-images/tutorial_custom_axon_term.svg + :width: 400 + :align: center + + Left: locset "custom_terminal"; right: locset "axon_terminal" + +The decorations +^^^^^^^^^^^^^^^ + +With the key regions and location expressions identified and labeled, we can start to +define certain features, properties and dynamics on the cell. This is done through a +:class:`arbor.decor` object, which stores a mapping of these "decorations" to certain +region or location expressions. + +.. Note:: + + Similar to the label dictionary, the decor object is merely a description of how an abstract + cell should behave, which can then be applied to any morphology, and have a different effect + depending on the geometry and region/locset expressions. + + More information on decors can be found :ref:`here <cablecell-decoration>`. + +The decor object can have default values for properties, which can then be overridden on specific +regions. It is in general better to explicitly set all the default properties of your cell, +to avoid the confusion to having simulator-specific default values. This will therefore be our first +step: + +.. code-block:: python + + # Create a decor object + decor = arbor.decor() + + # Set the default properties + decor.set_property(Vm =-55, tempK=300, rL=35.4, cm=0.01) + decor.set_ion('na', int_con=10, ext_con=140, rev_pot=50, method='nernst/na') + decor.set_ion('k', int_con=54.4, ext_con=2.5, rev_pot=-77) + +We have set the default initial membrane voltage to -55 mV; the default initial +temperature to 300 K; the default axial resistivity to 35.4 Ω·cm; and the default membrane +capacitance to 0.01 F/m². + +We also set the initial properties of the *na* and *k* ions because they will be utilized +by the density mechanisms that we will be adding shortly. +For both ions we set the default initial concentration and external concentration measures in mM; +and we set the default initial reversal potential in mV. For the *na* ion, we additionally indicate +the the progression on the reversal potential during the simulation will be dictated by the +`nernst equation <https://en.wikipedia.org/wiki/Nernst_equation>`_. + +It happens, however, that we want the temperature of the "custom" region defined in the label +dictionary earlier to be colder, and the initial voltage of the "soma" region to be higher. +We can override the default properties by *painting* new values on the relevant regions using +:meth:`arbor.decor.paint`. + +.. code-block:: python + + # Override default parameters on certain regions + + decor.paint('"custom"', tempK=270) + decor.paint('"soma"', Vm=-50) + +With the default and initial values taken care of, we now add some density mechanisms. Let's *paint* +a *pas* mechanism everywhere on the cell using the previously defined "all" region; an *hh* mechanism +on the "custom" region; and an *Ih* mechanism on the "dend" region. The *Ih* mechanism is explicitly +constructed in order to change the default values of its 'gbar' parameter. + + +.. code-block:: python + + # Paint density mechanisms on certain regions + + from arbor import mechanism as mech + + decor.paint('"all"', 'pas') + decor.paint('"custom"', 'hh') + decor.paint('"dend"', mech('Ih', {'gbar': 0.001})) + +The decor object is also used to *place* stimuli and spike detectors on the cell using :meth:`arbor.decor.place`. +We place 3 current clamps of 2 nA on the "root" locset defined earlier, starting at time = 10, 30, 50 ms and +lasting 1ms each. As well as spike detectors on the "axon_terminal" locset for voltages above -10 mV: + +.. code-block:: python + + # Place stimuli and spike detectors on certain locsets + + decor.place('"root"', arbor.iclamp(10, 1, current=2)) + decor.place('"root"', arbor.iclamp(30, 1, current=2)) + decor.place('"root"', arbor.iclamp(50, 1, current=2)) + decor.place('"axon_terminal"', arbor.spike_detector(-10)) + +Finally, there's one last property that impacts the behavior of a model: the discretisation. +Cells in Arbor are simulated as discrete components called control volumes (CV). The size of +a CV has an impact on the accuracy of the results of the simulation. Usually, smaller CVs +are more accurate because they simulate the continuous nature of a neuron more closely. + +The user controls the discretisation using a :class:`arbor.cv_policy`. There are a few different policies to +choose from, and they can be composed with one another. In this example, we would like the "soma" region +to be a single CV, and the rest of the morphology to be comprised of CVs with a maximum length of 1 μm: + +.. code-block:: python + + # Single CV for the "soma" region + soma_policy = arbor.cv_policy_single('"soma"') + + # CVs with max length = 1 μm as default + dflt_policy = arbor.cv_policy_max_extent(1.0) + + # default policy everywhere except the soma + policy = dflt_policy | soma_policy + + decor.discretization(policy) + + +The model +********* + +We begin by constructing a :class:`arbor.single_cell_model` of the cell we just created. + +.. code-block:: python + + # Construct the model + + model = arbor.single_cell_model(cell) + +The global properties +^^^^^^^^^^^^^^^^^^^^^ + +The global properties of a single cell model include: + +1. The **mechanism catalogue**: A mechanism catalogue is a collection of density and point + mechanisms. Arbor has 3 built in mechanism catalogues: default, allen and bbp. The mechanism + catalogue in the global properties of the model must include the catalogues of all the + mechanisms painted on the cell decor. + +2. The default **parameters**: The initial membrane voltage; the initial temperature; the + axial resistivity; the membrane capacitance; the ion parameters; and the discretisation + policy. + +.. Note:: + + You may notice that the same parameters can be set both at the cell level and at + the model level. This is intentional. The model parameters apply to all the cells in a model, + whereas the cell parameters apply only to that specific cell. + + The idea is that the user is able to define a set of global properties for all cells in a model + which can then be overridden for individual cells, and overridden yet again on certain + regions of the cells. + + You may now be wondering why this is needed for the `single cell model` where there is only one + cell by design. You can use this feature to ease moving from simulating a set of single cell models + to simulating a network of these cells. + For example, a user may choose to individually test several single cell models before simulating + their interactions. By using the same global properties for each *model*, and customizing the *cell* + global properties, it becomes possible to use the cell descriptions of each cell, unchanged, in a + larger network model. + +Earlier in the example we mentioned that it is better to explicitly set all the default properties +of your cell, while that is true, it is better yet to set the default properties of the entire +model: + +.. _tutorialsinglecellswc-gprop: + +.. code-block:: python + + # Set the model default properties + + model.properties.set_property(Vm =-65, tempK=300, rL=35.4, cm=0.01) + model.properties.set_ion('na', int_con=10, ext_con=140, rev_pot=50, method='nernst/na') + model.properties.set_ion('k', int_con=54.4, ext_con=2.5, rev_pot=-77) + +We set the same properties as we did earlier when we were creating the *decor* of the cell, except +for the initial membrane voltage, which is -65 mV as opposed to -55 mV. + +During the decoration step, we also made use of 3 mechanisms: *pas*, *hh* and *Ih*. As it happens, +the *pas* and *hh* mechanisms are in the default Arbor catalogue, whereas the *Ih* mechanism is in +the "allen" catalogue. We can extend the default catalogue as follow: + +.. code-block:: python + + # Extend the default catalogue with the allen catalogue. + # The function takes a second string parameter that can prefix + # the name of the mechanisms to avoid collisions between catalogues + # in this case we have no collisions so we use an empty prefix string. + + model.catalogue.extend(arbor.allen_catalogue(), "") + +Now all three mechanisms in the *decor* object have been made available to the model. + +The probes +^^^^^^^^^^ + +The model is almost ready for simulation. Except that the only output we would be able to +measure at this point is the spikes from the spike detectors placed in the decor. + +The :class:`arbor.single_cell_model` can also measure the voltage on specific locations of the cell. +We can indicate the location we would like to probe using labels from the :class:`label_dict`: + +.. code-block:: python + + # Add voltage probes on the "custom_terminal" locset + # which sample the voltage at 50000 Hz + + model.probe('voltage', where='"custom_terminal"', frequency=50000) + +The simulation +^^^^^^^^^^^^^^ + +The cell and model descriptions are now complete and we can run the simulation: + +.. code-block:: python + + # Run the simulation for 100 ms, with a dt of 0.025 ms + model.run(tfinal=100, dt=0.025) + +The results +^^^^^^^^^^^ + +Finally we move on to the data collection segment of the example. We have added a spike detector +on the "axon_terminal" locset. The :class:`arbor.single_cell_model` automatically registers all +spikes on the cell from all spike detectors on the cell and saves the times at which they occurred. + +.. code-block:: python + + # Print the number of spikes. + print(len(model.spikes), 'spikes recorded:') + + # Print the spike times. + for s in model.spikes: + print(s) + + +A more interesting result of the simulation is perhaps the output of the voltage probe previously +placed on the "custom_terminal" locset. The model saves the output of the probes as [time, value] +pairs which can then be plotted. We use `pandas` and `seaborn` for the plotting, but the user can +choose the any other library: + +.. code-block:: python + + import pandas + import seaborn + + # Plot the output of the probes + df = pandas.DataFrame() + for t in model.traces: + df=df.append(pandas.DataFrame({'t/ms': t.time, 'U/mV': t.value, 'Location': str(t.location), 'Variable': t.variable})) + + seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV",hue="Location",col="Variable",ci=None).savefig('single_cell_detailed_result.svg') + +The following plot is generated. The orange line is slightly delayed from the blue line, which is +what we'd expect because branch 4 is longer than branch 3 of the morphology. We also see 3 spikes, +corresponding to each of the current clamps placed on the cell. + +.. figure:: single_cell_detailed_result.svg + :width: 400 + :align: center + +The full code +************* +You can find the full code of the example at ``python/examples/single_cell_detailed.py``. \ No newline at end of file diff --git a/doc/tutorial/single_cell_detailed_recipe.rst b/doc/tutorial/single_cell_detailed_recipe.rst new file mode 100644 index 0000000000000000000000000000000000000000..32df752764c9c91f4d3b8649a1718ee84a06e784 --- /dev/null +++ b/doc/tutorial/single_cell_detailed_recipe.rst @@ -0,0 +1,419 @@ +.. _tutorialsinglecellrecipe: + +A detailed single cell recipe +============================= + +This example aims to build the same single cell model as +:ref:`the previous tutorial <tutorialsinglecellswc>`, except using a :class:`arbor.recipe` +and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`. + +.. Note:: + + **Concepts covered in this example:** + + 1. Building a :class:`arbor.recipe`. + 2. Building an :class:`arbor.context` and a :class:`arbor.domain_decomposition` + 3. Using the recipe, context and domain decomposition to create a :class:`arbor.simulation` + 4. Running the simulation and visualizing the results, + + +Recipes are an important concept in Arbor. They represent the most versatile tool +for building a complex network of cells. We will go though this example of a model +of a single cell, before using the recipe to represent more complex networks in +subsequent examples. + +We outline the following steps of this example: + +1. Define the **cell**. This is the same cell we have seen before. +2. Define the **recipe** of the model. +3. Define the **execution context** of the model: a description of the underlying system + on which the simulation will run. +4. Define the **domain decomposition** of the network: how the cells are distributed on + the different ranks of the system. +5. Define the **simulation**. +6. **Run** the simulation. +7. Collect and visualize the **results**. + +The cell +******** + +We can immediately paste the cell description code from the +:ref:`previous example <tutorialsinglecellswc-cell>` where it is explained in detail. + +.. code-block:: python + + import arbor + from arbor import mechanism as mech + + #(1) Read the morphology from an SWC file. + + morph = arbor.load_swc_arbor("morph.swc") + + #(2) Create and populate the label dictionary. + + labels = arbor.label_dict() + + # Regions: + + labels['soma'] = '(tag 1)' + labels['axon'] = '(tag 2)' + labels['dend'] = '(tag 3)' + labels['last'] = '(tag 4)' + + labels['all'] = '(all)' + + labels['gt_1.5'] = '(radius-ge (region "all") 1.5)' + labels['custom'] = '(join (region "last") (region "gt_1.5"))' + + # Locsets: + + labels['root'] = '(root)' + labels['terminal'] = '(terminal)' + labels['custom_terminal'] = '(restrict (locset "terminal") (region "custom"))' + labels['axon_terminal'] = '(restrict (locset "terminal") (region "axon"))' + + # (3) Create and populate the decor. + + decor = arbor.decor() + + # Set the default properties of the cell (this overrides the model defaults) + + decor.set_property(Vm =-55) + + # Override the cell defaults. + + decor.paint('"custom"', tempK=270) + decor.paint('"soma"', Vm=-50) + + # Paint density mechanisms. + + decor.paint('"all"', 'pas') + decor.paint('"custom"', 'hh') + decor.paint('"dend"', mech('Ih', {'gbar': 0.001})) + + # Place stimuli and spike detectors. + + decor.place('"root"', arbor.iclamp(10, 1, current=2)) + decor.place('"root"', arbor.iclamp(30, 1, current=2)) + decor.place('"root"', arbor.iclamp(50, 1, current=2)) + decor.place('"axon_terminal"', arbor.spike_detector(-10)) + + # Set cv_policy + + soma_policy = arbor.cv_policy_single('"soma"') + dflt_policy = arbor.cv_policy_max_extent(1.0) + policy = dflt_policy | soma_policy + decor.discretization(policy) + + # (4) Create the cell. + + cell = arbor.cable_cell(morph, labels, decor) + +We will add one more thing to this section. We will create the voltage probe at the "custom_terminal" locset. +In the previous example, this probe was registered directly using the :class:`arbor.single_cell_model` object. +Now it has to be explicitly created and registered in the recipe. + +.. _tutorialsinglecellrecipe-probe: +.. code-block:: python + + probe = arbor.cable_probe_membrane_voltage('"custom_terminal"') + +The recipe +********** + +The :class:`arbor.single_cell_model` of the previous example created a :class:`arbor.recipe` under +the hood, and abstracted away the details so we were unaware of its existence. In this example, we will +examine the recipe in detail: how to create one, and why it is needed. + +.. code-block:: python + + # (1) Create a class that inherits from arbor.recipe + class single_recipe (arbor.recipe): + + # (2) Define the class constructor + def __init__(self, cell, probes): + # The base C++ class constructor must be called first, to ensure that + # all memory in the C++ class is initialized correctly. + arbor.recipe.__init__(self) + self.the_cell = cell + self.the_probes = probes + + self.the_cat = arbor.default_catalogue() + self.the_cat.extend(arbor.allen_catalogue(), "") + + self.the_props = arbor.cable_global_properties() + self.the_props.set_property(Vm=-65, tempK=300, rL=35.4, cm=0.01) + self.the_props.set_ion(ion='na', int_con=10, ext_con=140, rev_pot=50, method='nernst/na') + self.the_props.set_ion(ion='k', int_con=54.4, ext_con=2.5, rev_pot=-77) + self.the_props.set_ion(ion='ca', int_con=5e-5, ext_con=2, rev_pot=132.5) + + self.the_props.register(self.the_cat) + + # (3) Override the num_cells method + def num_cells(self): + return 1 + + # (4) Override the num_sources method + def num_sources(self, gid): + return 1 + + # (5) Override the num_targets method + def num_targets(self, gid): + return 0 + + # (6) Override the num_targets method + def cell_kind(self, gid): + return arbor.cell_kind.cable + + # (7) Override the cell_description method + def cell_description(self, gid): + return self.the_cell + + # (8) Override the probes method + def probes(self, gid): + return self.the_probes + + # (9) Override the connections_on method + def connections_on(self, gid): + return [] + + # (10) Override the gap_junction_on method + def gap_junction_on(self, gid): + return [] + + # (11) Override the event_generators method + def event_generators(self, gid): + return [] + + # (12) Overrode the global_properties method + def global_properties(self, gid): + return self.the_props + +Let's go through the recipe point by point. + +Step **(1)** creates a ``single_recipe`` class that inherits from :class:`arbor.recipe`. The base recipe +implements all the methods defined above with default values except :meth:`arbor.recipe.num_cells`, +:meth:`arbor.recipe.cell_kind` and :meth:`arbor.recipe.cell_description` which always have to be implemented +by the user. The :meth:`arbor.recipe.gloabl_properties` also needs to be implemented for +:class:`arbor.cell_kind.cable` cells. The inherited recipe can implement any number of additional methods and +have any number of instance or class variables. + +Step **(2)** defines the class constructor. In this case, we pass a ``cell`` and a set of ``probes`` as +arguments. These will be used to initialize the instance variables ``self.the_cell`` and ``self.the_probes``, +which will be used in the overloaded ``cell_description`` and ``get_probes`` methods. Before variable +initialization, we call the base C++ class constructor ``arbor.recipe.__init__(self)``. This ensures correct +initialization of memory in the C++ class. + +We also create the ``self.the_cat`` variable and set it to arbor's default mechanism catalogue. This will expose +the *hh* and *pas* mechanisms but not the *Ih* mechanism, which is present in the allen catalogue. To be able +to use *Ih*, we extend ``self.the_cat`` to include the allen catalogue. + +Finally we create the ``self.the_props`` variable. This will hold the global properties of the model, which apply +to all the cells in the network. Initially it is empty. We set all the properties of the system similar to +what we did in the :ref:`previous example <tutorialsinglecellswc-gprop>`. One last important step is to register +``self.the_cat`` with ``self.the_props``. + +.. Note:: + + The mechanism catalogue needs to live in the recipe as an instance variable. Its lifetime needs to extend + to the entire duration of the simulation. + +Step **(3)** overrides the :meth:`arbor.recipe.num_cells` method. It takes 0 arguments. We simply return 1, +as we are only simulating one cell in this example. + +Step **(4)** overrides the :meth:`arbor.recipe.num_sources` method. It takes one argument: ``gid``. +Given this global ID of a cell, the method will return the number of spike *sources* on the cell. We have defined +our cell with one spike detector, on one location on the morphology, so we return 1. + +Step **(5)** overrides the :meth:`arbor.recipe.num_targets` method. It takes one argument: ``gid``. +Given the gid, this method returns the number of *targets* on the cell. These are typically synapses on the cell +that are capable of receiving events from other cells. We have defined our cell with 0 synapses, so we return 0. + +Step **(6)** overrides the :meth:`arbor.recipe.cell_kind` method. It takes one argument: ``gid``. +Given the gid, this method returns the kind of the cell. Our defined cell is a +:class:`arbor.cell_kind.cable`, so we simply return that. + +Step **(7)** overrides the :meth:`arbor.recipe.cell_description` method. It takes one argument: ``gid``. +Given the gid, this method returns the cell description which is the cell object passed to the constructor +of the recipe. We return ``self.the_cell``. + +Step **(8)** overrides the :meth:`arbor.recipe.get_probes` method. It takes one argument: ``gid``. +Given the gid, this method returns all the probes on the cell. The probes can be of many different kinds +measuring different quantities on different locations of the cell. We pass these probes explicitly to the recipe +and they are stored in ``self.the_probes``, so we return that variable. + +Step **(9)** overrides the :meth:`arbor.recipe.connections_on` method. It takes one argument: ``gid``. +Given the gid, this method returns all the connections ending on that cell. These are typically synapse +connections from other cell *sources* to specific *targets* on the cell with id ``gid``. Since we are +simulating a single cell, and self-connections are not possible, we return an empty list. + +Step **(10)** overrides the :meth:`arbor.recipe.gap_junctions_on` method. It takes one argument: ``gid``. +Given the gid, this method returns all the gap junctions on that cell. Gap junctions require 2 separate cells. +Since we are simulating a single cell, we return an empty list. + +Step **(11)** overrides the :meth:`arbor.recipe.event_generators` method. It takes one argument: ``gid``. +Given the gid, this method returns *event generators* on that cell. These generators trigger events (or +spikes) on specific *targets* on the cell. They can be used to simulate spikes from other cells, to kick-start +a simulation for example. Our cell uses a current clamp as a stimulus, and has no targets, so we return +an empty list. + +Step **(12)** overrides the :meth:`arbor.recipe.global_properties` method. It takes one argument: ``kind``. +This method returns the default global properties of the model which apply to all cells in the network of +that kind. We return ``self.the_props`` which we defined in step **(1)**. + +.. Note:: + + You may wonder why the methods: :meth:`arbor.recipe.num_sources`, :meth:`arbor.recipe.num_targets`, + and :meth:`arbor.recipe.cell_kind` are required, since they can be inferred by examining the cell description. + The recipe was designed to allow building simulations efficiently in a distributed system with minimum + communication. Some parts of the model initialization require only the cell kind, or the number of + sources and targets, not the full cell description which can be quite expensive to build. Providing these + descriptions separately saves time and resources for the user. + + More information on the recipe can be found :ref:`here <modelrecipe>`. + +Now we can intantiate a ``single_recipe`` object using the ``cell`` and ``probe`` we created in the +previous section: + +.. code-block:: python + + # Instantiate recipe + # Pass the probe in a list because that it what single_recipe expects. + recipe = single_recipe(cell, [probe]) + +The execution context +********************* + +The execution context contains all system-specific information needed by the simulation: it contains the +thread pool which handles multi-threaded optimization on the CPU; it knows the relevant GPU attributes +if a GPU is available; and it holds the MPI communicator for distributed simulations. In the previous +examples, the :class:`arbor.single_cell_model` object created the execution context :class:`arbor.context` +behind the scenes. + +The details of the execution context can be customized by the user. We may specify the number of threads +in the thread pool; determine the id of the GPU to be used; or create our own MPI communicator. However, +the ideal settings can usually be inferred from the system, and arbor can do that with a simple command. + +.. code-block:: python + + context = arbor.context() + +The domain decomposition +************************ + +The domain decomposition describes the distribution of the cells over the available computational resources. +The :class:`arbor.single_cell_model` also handled that without our knowledge in the previous examples. +Now, we have to define it ourselves. + +The :class:`arbor.domain_decomposition` class can be manually created by the user, by deciding which cells +go on which ranks. Or we can use a load balancer that can partition the cells across ranks according to +some rules. Arbor provides :class:`arbor.partition_load_balance`, which, using the recipe and execution +context, creates the :class:`arbor.domain_decomposition` object for us. + +Our example is a simple one, with just one cell. We don't need any sophisticated partitioning algorithms, so +we can use the load balancer, which does a good job distributing simple networks. + +.. code-block:: python + + domains = arbor.partition_load_balance(recipe, context) + +The simulation +************** + +Finally we have the 3 components needed to create a :class:`arbor.simulation` object. + +.. code-block:: python + + sim = arbor.simulation(recipe, domains, context) + +Before we run the simulation, however, we need to register what results we expect once execution is over. +This was handled by the :class:`arbor.single_cell_model` object in the previous example. + +We would like to get a list of the spikes on the cell during the runtime of the simulation, and we would like +to plot the voltage registered by the probe on the "custom_terminal" locset. + +.. code-block:: python + + # Instruct the simulation to record the spikes + sim.record(arbor.spike_recording.all) + + # Instruct the simulation to sample the probe (0, 0) + # at a regular schedule with period = 0.02 ms (50000 Hz) + probe_id = arbor.cell_member(0,0) + handle = sim.sample(probe_id, arbor.regular_schedule(0.02)) + +The lines handling probe sampling warrant a second look. First, we declared ``probe_id`` to be a +:class:`arbor.cell_member`, with :class:`arbor.cell_member.gid` = 0 and :class:`arbor.cell_member.index` = 0. +This variable serves as a global identifier of a probe on a cell, namely the first declared probe on the +cell with gid = 0, which is id of the :ref:`only probe <tutorialsinglecellrecipe-probe>` we created on +the only cell in the model. + +Next, we instructed the simulation to sample ``probe_id`` at a frequency of 50KHz. That function returns a +``handle`` which we will use to extract the results of the sampling after running the simulation. + +The execution +************* + +We can now run the simulation we just instantiated for a duration of 100ms with a time step of 0.025 ms. + +.. code-block:: python + + sim.run(tfinal=100, dt=0.025) + + +The results +*********** + +The last step is result collection. We instructed the simulation to record the spikes on the cell, and +to sample the probe. + +We can print the times of the spikes: + +.. code-block:: python + + spikes = sim.spikes() + + # Print the number of spikes. + print(len(spikes), 'spikes recorded:') + + # Print the spike times. + for s in spikes: + print(s) + +The probe results, again, warrant some more explanation: + +.. code-block:: python + + data = [] + meta = [] + for d, m in sim.samples(handle): + data.append(d) + meta.append(m) + +``sim.samples()`` takes a ``handle`` of the probe we wish to examine. It returns a list +of ``(data, meta)`` terms: ``data`` being the time and value series of the probed quantity; and +``meta`` being the location of the probe. The size of the returned list depends on the number of +discrete locations pointed to by the handle. We placed the probe on the "custom_terminal" locset which is +represented by 2 locations on the morphology. We therefore expect the length of ``sim.samples(handle)`` +to be 2. + +We plot the results using pandas and seaborn as we did in the previous example, and expect the same results: + +.. code-block:: python + + df = pandas.DataFrame() + for i in range(len(data)): + df = df.append(pandas.DataFrame({'t/ms': data[i][:, 0], 'U/mV': data[i][:, 1], 'Location': str(meta[i])})) + seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV",hue="Location",col="Variable",ci=None).savefig('single_cell_detailed_recipe_result.svg') + +The following plot is generated. Identical to the plot of the previous example. + +.. figure:: single_cell_detailed_result.svg + :width: 400 + :align: center + + +The full code +************* + +You can find the full code of the example at ``python/examples/single_cell_detailed_recipe.py``. \ No newline at end of file diff --git a/doc/tutorial/single_cell_detailed_result.svg b/doc/tutorial/single_cell_detailed_result.svg new file mode 100644 index 0000000000000000000000000000000000000000..6c1cf96b15897f7d8ef196f219e97df6757c82d9 --- /dev/null +++ b/doc/tutorial/single_cell_detailed_result.svg @@ -0,0 +1,1244 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Created with matplotlib (https://matplotlib.org/) --> +<svg height="365.754687pt" version="1.1" viewBox="0 0 463.847712 365.754687" width="463.847712pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <metadata> + <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <cc:Work> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:date>2020-12-11T12:43:31.908629</dc:date> + <dc:format>image/svg+xml</dc:format> + <dc:creator> + <cc:Agent> + <dc:title>Matplotlib v3.3.1, https://matplotlib.org/</dc:title> + </cc:Agent> + </dc:creator> + </cc:Work> + </rdf:RDF> + </metadata> + <defs> + <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style> + </defs> + <g id="figure_1"> + <g id="patch_1"> + <path d="M 0 365.754687 +L 463.847713 365.754687 +L 463.847713 0 +L 0 0 +z +" style="fill:#ffffff;"/> + </g> + <g id="axes_1"> + <g id="patch_2"> + <path d="M 48.982813 328.198437 +L 354.421712 328.198437 +L 354.421712 20.798437 +L 48.982813 20.798437 +z +" style="fill:#ffffff;"/> + </g> + <g id="matplotlib.axis_1"> + <g id="xtick_1"> + <g id="line2d_1"> + <defs> + <path d="M 0 0 +L 0 3.5 +" id="m211d7749e0" style="stroke:#000000;stroke-width:0.8;"/> + </defs> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="62.866399" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_1"> + <!-- 0 --> + <g transform="translate(59.685149 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 31.78125 66.40625 +Q 24.171875 66.40625 20.328125 58.90625 +Q 16.5 51.421875 16.5 36.375 +Q 16.5 21.390625 20.328125 13.890625 +Q 24.171875 6.390625 31.78125 6.390625 +Q 39.453125 6.390625 43.28125 13.890625 +Q 47.125 21.390625 47.125 36.375 +Q 47.125 51.421875 43.28125 58.90625 +Q 39.453125 66.40625 31.78125 66.40625 +z +M 31.78125 74.21875 +Q 44.046875 74.21875 50.515625 64.515625 +Q 56.984375 54.828125 56.984375 36.375 +Q 56.984375 17.96875 50.515625 8.265625 +Q 44.046875 -1.421875 31.78125 -1.421875 +Q 19.53125 -1.421875 13.0625 8.265625 +Q 6.59375 17.96875 6.59375 36.375 +Q 6.59375 54.828125 13.0625 64.515625 +Q 19.53125 74.21875 31.78125 74.21875 +z +" id="DejaVuSans-48"/> + </defs> + <use xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_2"> + <g id="line2d_2"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="118.414631" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_2"> + <!-- 20 --> + <g transform="translate(112.052131 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 19.1875 8.296875 +L 53.609375 8.296875 +L 53.609375 0 +L 7.328125 0 +L 7.328125 8.296875 +Q 12.9375 14.109375 22.625 23.890625 +Q 32.328125 33.6875 34.8125 36.53125 +Q 39.546875 41.84375 41.421875 45.53125 +Q 43.3125 49.21875 43.3125 52.78125 +Q 43.3125 58.59375 39.234375 62.25 +Q 35.15625 65.921875 28.609375 65.921875 +Q 23.96875 65.921875 18.8125 64.3125 +Q 13.671875 62.703125 7.8125 59.421875 +L 7.8125 69.390625 +Q 13.765625 71.78125 18.9375 73 +Q 24.125 74.21875 28.421875 74.21875 +Q 39.75 74.21875 46.484375 68.546875 +Q 53.21875 62.890625 53.21875 53.421875 +Q 53.21875 48.921875 51.53125 44.890625 +Q 49.859375 40.875 45.40625 35.40625 +Q 44.1875 33.984375 37.640625 27.21875 +Q 31.109375 20.453125 19.1875 8.296875 +z +" id="DejaVuSans-50"/> + </defs> + <use xlink:href="#DejaVuSans-50"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_3"> + <g id="line2d_3"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="173.962864" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_3"> + <!-- 40 --> + <g transform="translate(167.600364 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 37.796875 64.3125 +L 12.890625 25.390625 +L 37.796875 25.390625 +z +M 35.203125 72.90625 +L 47.609375 72.90625 +L 47.609375 25.390625 +L 58.015625 25.390625 +L 58.015625 17.1875 +L 47.609375 17.1875 +L 47.609375 0 +L 37.796875 0 +L 37.796875 17.1875 +L 4.890625 17.1875 +L 4.890625 26.703125 +z +" id="DejaVuSans-52"/> + </defs> + <use xlink:href="#DejaVuSans-52"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_4"> + <g id="line2d_4"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="229.511096" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_4"> + <!-- 60 --> + <g transform="translate(223.148596 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 33.015625 40.375 +Q 26.375 40.375 22.484375 35.828125 +Q 18.609375 31.296875 18.609375 23.390625 +Q 18.609375 15.53125 22.484375 10.953125 +Q 26.375 6.390625 33.015625 6.390625 +Q 39.65625 6.390625 43.53125 10.953125 +Q 47.40625 15.53125 47.40625 23.390625 +Q 47.40625 31.296875 43.53125 35.828125 +Q 39.65625 40.375 33.015625 40.375 +z +M 52.59375 71.296875 +L 52.59375 62.3125 +Q 48.875 64.0625 45.09375 64.984375 +Q 41.3125 65.921875 37.59375 65.921875 +Q 27.828125 65.921875 22.671875 59.328125 +Q 17.53125 52.734375 16.796875 39.40625 +Q 19.671875 43.65625 24.015625 45.921875 +Q 28.375 48.1875 33.59375 48.1875 +Q 44.578125 48.1875 50.953125 41.515625 +Q 57.328125 34.859375 57.328125 23.390625 +Q 57.328125 12.15625 50.6875 5.359375 +Q 44.046875 -1.421875 33.015625 -1.421875 +Q 20.359375 -1.421875 13.671875 8.265625 +Q 6.984375 17.96875 6.984375 36.375 +Q 6.984375 53.65625 15.1875 63.9375 +Q 23.390625 74.21875 37.203125 74.21875 +Q 40.921875 74.21875 44.703125 73.484375 +Q 48.484375 72.75 52.59375 71.296875 +z +" id="DejaVuSans-54"/> + </defs> + <use xlink:href="#DejaVuSans-54"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_5"> + <g id="line2d_5"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="285.059329" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_5"> + <!-- 80 --> + <g transform="translate(278.696829 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 31.78125 34.625 +Q 24.75 34.625 20.71875 30.859375 +Q 16.703125 27.09375 16.703125 20.515625 +Q 16.703125 13.921875 20.71875 10.15625 +Q 24.75 6.390625 31.78125 6.390625 +Q 38.8125 6.390625 42.859375 10.171875 +Q 46.921875 13.96875 46.921875 20.515625 +Q 46.921875 27.09375 42.890625 30.859375 +Q 38.875 34.625 31.78125 34.625 +z +M 21.921875 38.8125 +Q 15.578125 40.375 12.03125 44.71875 +Q 8.5 49.078125 8.5 55.328125 +Q 8.5 64.0625 14.71875 69.140625 +Q 20.953125 74.21875 31.78125 74.21875 +Q 42.671875 74.21875 48.875 69.140625 +Q 55.078125 64.0625 55.078125 55.328125 +Q 55.078125 49.078125 51.53125 44.71875 +Q 48 40.375 41.703125 38.8125 +Q 48.828125 37.15625 52.796875 32.3125 +Q 56.78125 27.484375 56.78125 20.515625 +Q 56.78125 9.90625 50.3125 4.234375 +Q 43.84375 -1.421875 31.78125 -1.421875 +Q 19.734375 -1.421875 13.25 4.234375 +Q 6.78125 9.90625 6.78125 20.515625 +Q 6.78125 27.484375 10.78125 32.3125 +Q 14.796875 37.15625 21.921875 38.8125 +z +M 18.3125 54.390625 +Q 18.3125 48.734375 21.84375 45.5625 +Q 25.390625 42.390625 31.78125 42.390625 +Q 38.140625 42.390625 41.71875 45.5625 +Q 45.3125 48.734375 45.3125 54.390625 +Q 45.3125 60.0625 41.71875 63.234375 +Q 38.140625 66.40625 31.78125 66.40625 +Q 25.390625 66.40625 21.84375 63.234375 +Q 18.3125 60.0625 18.3125 54.390625 +z +" id="DejaVuSans-56"/> + </defs> + <use xlink:href="#DejaVuSans-56"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="xtick_6"> + <g id="line2d_6"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="340.607561" xlink:href="#m211d7749e0" y="328.198437"/> + </g> + </g> + <g id="text_6"> + <!-- 100 --> + <g transform="translate(331.063811 342.796875)scale(0.1 -0.1)"> + <defs> + <path d="M 12.40625 8.296875 +L 28.515625 8.296875 +L 28.515625 63.921875 +L 10.984375 60.40625 +L 10.984375 69.390625 +L 28.421875 72.90625 +L 38.28125 72.90625 +L 38.28125 8.296875 +L 54.390625 8.296875 +L 54.390625 0 +L 12.40625 0 +z +" id="DejaVuSans-49"/> + </defs> + <use xlink:href="#DejaVuSans-49"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + <use x="127.246094" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="text_7"> + <!-- t/ms --> + <g transform="translate(190.582731 356.475)scale(0.1 -0.1)"> + <defs> + <path d="M 18.3125 70.21875 +L 18.3125 54.6875 +L 36.8125 54.6875 +L 36.8125 47.703125 +L 18.3125 47.703125 +L 18.3125 18.015625 +Q 18.3125 11.328125 20.140625 9.421875 +Q 21.96875 7.515625 27.59375 7.515625 +L 36.8125 7.515625 +L 36.8125 0 +L 27.59375 0 +Q 17.1875 0 13.234375 3.875 +Q 9.28125 7.765625 9.28125 18.015625 +L 9.28125 47.703125 +L 2.6875 47.703125 +L 2.6875 54.6875 +L 9.28125 54.6875 +L 9.28125 70.21875 +z +" id="DejaVuSans-116"/> + <path d="M 25.390625 72.90625 +L 33.6875 72.90625 +L 8.296875 -9.28125 +L 0 -9.28125 +z +" id="DejaVuSans-47"/> + <path d="M 52 44.1875 +Q 55.375 50.25 60.0625 53.125 +Q 64.75 56 71.09375 56 +Q 79.640625 56 84.28125 50.015625 +Q 88.921875 44.046875 88.921875 33.015625 +L 88.921875 0 +L 79.890625 0 +L 79.890625 32.71875 +Q 79.890625 40.578125 77.09375 44.375 +Q 74.3125 48.1875 68.609375 48.1875 +Q 61.625 48.1875 57.5625 43.546875 +Q 53.515625 38.921875 53.515625 30.90625 +L 53.515625 0 +L 44.484375 0 +L 44.484375 32.71875 +Q 44.484375 40.625 41.703125 44.40625 +Q 38.921875 48.1875 33.109375 48.1875 +Q 26.21875 48.1875 22.15625 43.53125 +Q 18.109375 38.875 18.109375 30.90625 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 21.1875 51.21875 25.484375 53.609375 +Q 29.78125 56 35.6875 56 +Q 41.65625 56 45.828125 52.96875 +Q 50 49.953125 52 44.1875 +z +" id="DejaVuSans-109"/> + <path d="M 44.28125 53.078125 +L 44.28125 44.578125 +Q 40.484375 46.53125 36.375 47.5 +Q 32.28125 48.484375 27.875 48.484375 +Q 21.1875 48.484375 17.84375 46.4375 +Q 14.5 44.390625 14.5 40.28125 +Q 14.5 37.15625 16.890625 35.375 +Q 19.28125 33.59375 26.515625 31.984375 +L 29.59375 31.296875 +Q 39.15625 29.25 43.1875 25.515625 +Q 47.21875 21.78125 47.21875 15.09375 +Q 47.21875 7.46875 41.1875 3.015625 +Q 35.15625 -1.421875 24.609375 -1.421875 +Q 20.21875 -1.421875 15.453125 -0.5625 +Q 10.6875 0.296875 5.421875 2 +L 5.421875 11.28125 +Q 10.40625 8.6875 15.234375 7.390625 +Q 20.0625 6.109375 24.8125 6.109375 +Q 31.15625 6.109375 34.5625 8.28125 +Q 37.984375 10.453125 37.984375 14.40625 +Q 37.984375 18.0625 35.515625 20.015625 +Q 33.0625 21.96875 24.703125 23.78125 +L 21.578125 24.515625 +Q 13.234375 26.265625 9.515625 29.90625 +Q 5.8125 33.546875 5.8125 39.890625 +Q 5.8125 47.609375 11.28125 51.796875 +Q 16.75 56 26.8125 56 +Q 31.78125 56 36.171875 55.265625 +Q 40.578125 54.546875 44.28125 53.078125 +z +" id="DejaVuSans-115"/> + </defs> + <use xlink:href="#DejaVuSans-116"/> + <use x="39.208984" xlink:href="#DejaVuSans-47"/> + <use x="72.900391" xlink:href="#DejaVuSans-109"/> + <use x="170.3125" xlink:href="#DejaVuSans-115"/> + </g> + </g> + </g> + <g id="matplotlib.axis_2"> + <g id="ytick_1"> + <g id="line2d_7"> + <defs> + <path d="M 0 0 +L -3.5 0 +" id="m1f9db64551" style="stroke:#000000;stroke-width:0.8;"/> + </defs> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="326.293839"/> + </g> + </g> + <g id="text_8"> + <!-- −80 --> + <g transform="translate(20.878125 330.093058)scale(0.1 -0.1)"> + <defs> + <path d="M 10.59375 35.5 +L 73.1875 35.5 +L 73.1875 27.203125 +L 10.59375 27.203125 +z +" id="DejaVuSans-8722"/> + </defs> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-56"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_2"> + <g id="line2d_8"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="278.29663"/> + </g> + </g> + <g id="text_9"> + <!-- −60 --> + <g transform="translate(20.878125 282.095849)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-54"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_3"> + <g id="line2d_9"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="230.299421"/> + </g> + </g> + <g id="text_10"> + <!-- −40 --> + <g transform="translate(20.878125 234.09864)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-52"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_4"> + <g id="line2d_10"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="182.302212"/> + </g> + </g> + <g id="text_11"> + <!-- −20 --> + <g transform="translate(20.878125 186.10143)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-8722"/> + <use x="83.789062" xlink:href="#DejaVuSans-50"/> + <use x="147.412109" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_5"> + <g id="line2d_11"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="134.305003"/> + </g> + </g> + <g id="text_12"> + <!-- 0 --> + <g transform="translate(35.620312 138.104221)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_6"> + <g id="line2d_12"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="86.307793"/> + </g> + </g> + <g id="text_13"> + <!-- 20 --> + <g transform="translate(29.257812 90.107012)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-50"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="ytick_7"> + <g id="line2d_13"> + <g> + <use style="stroke:#000000;stroke-width:0.8;" x="48.982813" xlink:href="#m1f9db64551" y="38.310584"/> + </g> + </g> + <g id="text_14"> + <!-- 40 --> + <g transform="translate(29.257812 42.109803)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-52"/> + <use x="63.623047" xlink:href="#DejaVuSans-48"/> + </g> + </g> + </g> + <g id="text_15"> + <!-- U/mV --> + <g transform="translate(14.798438 188.132812)rotate(-90)scale(0.1 -0.1)"> + <defs> + <path d="M 8.6875 72.90625 +L 18.609375 72.90625 +L 18.609375 28.609375 +Q 18.609375 16.890625 22.84375 11.734375 +Q 27.09375 6.59375 36.625 6.59375 +Q 46.09375 6.59375 50.34375 11.734375 +Q 54.59375 16.890625 54.59375 28.609375 +L 54.59375 72.90625 +L 64.5 72.90625 +L 64.5 27.390625 +Q 64.5 13.140625 57.4375 5.859375 +Q 50.390625 -1.421875 36.625 -1.421875 +Q 22.796875 -1.421875 15.734375 5.859375 +Q 8.6875 13.140625 8.6875 27.390625 +z +" id="DejaVuSans-85"/> + <path d="M 28.609375 0 +L 0.78125 72.90625 +L 11.078125 72.90625 +L 34.1875 11.53125 +L 57.328125 72.90625 +L 67.578125 72.90625 +L 39.796875 0 +z +" id="DejaVuSans-86"/> + </defs> + <use xlink:href="#DejaVuSans-85"/> + <use x="73.193359" xlink:href="#DejaVuSans-47"/> + <use x="106.884766" xlink:href="#DejaVuSans-109"/> + <use x="204.296875" xlink:href="#DejaVuSans-86"/> + </g> + </g> + </g> + <g id="line2d_14"> + <path clip-path="url(#p27c2e72490)" d="M 62.866399 266.297328 +L 63.213575 272.565188 +L 64.046799 282.252843 +L 64.810587 288.79854 +L 65.50494 293.084911 +L 66.199293 296.057164 +L 66.82421 297.845429 +L 67.449128 299.010307 +L 68.074046 299.731108 +L 68.768399 300.178107 +L 69.532187 300.391808 +L 70.504281 300.414252 +L 71.892987 300.199951 +L 74.531528 299.519014 +L 82.724892 297.337801 +L 88.418586 296.084638 +L 90.70995 295.548663 +L 90.848821 294.62402 +L 91.057127 291.055939 +L 91.612609 275.276411 +L 92.584703 248.985439 +L 94.806633 193.803202 +L 95.29268 169.102319 +L 96.056468 116.26417 +L 96.750821 73.64769 +L 97.167432 59.31021 +L 97.514609 53.360893 +L 97.79235 51.469962 +L 97.931221 51.24033 +L 98.070091 51.392852 +L 98.278397 52.213811 +L 98.625574 54.799825 +L 99.181056 61.098813 +L 100.014279 73.453403 +L 101.680726 102.281189 +L 104.735879 154.750099 +L 107.443855 196.824168 +L 109.457479 229.129741 +L 110.568444 250.681216 +L 112.998679 300.990347 +L 113.554161 306.512098 +L 114.040208 309.239551 +L 114.526255 310.694668 +L 114.942867 311.324555 +L 115.428914 311.646179 +L 115.984396 311.723244 +L 116.81762 311.585335 +L 118.484067 311.038004 +L 123.136231 309.21485 +L 141.675454 301.800165 +L 146.327618 300.124085 +L 146.466489 299.21613 +L 146.674795 295.813802 +L 147.299712 279.515222 +L 148.132936 260.490001 +L 149.2439 241.117122 +L 149.521642 239.621793 +L 149.938253 238.796048 +L 150.215994 237.130412 +L 150.563171 233.130994 +L 150.979783 225.39166 +L 151.46583 212.219569 +L 152.090747 188.511912 +L 152.854536 150.013327 +L 153.9655 94.626821 +L 154.451547 80.973416 +L 154.868159 75.101886 +L 155.1459 73.55161 +L 155.354206 73.37036 +L 155.493077 73.641945 +L 155.770818 74.971719 +L 156.187429 78.501516 +L 156.812347 86.09257 +L 157.853876 101.914703 +L 164.103053 201.709279 +L 165.838935 229.711351 +L 166.9499 251.200493 +L 169.310699 299.510636 +L 169.866182 305.284467 +L 170.352229 308.292156 +L 170.838276 310.041218 +L 171.324323 311.003566 +L 171.81037 311.476076 +L 172.296417 311.65122 +L 172.99077 311.629781 +L 174.17117 311.312504 +L 177.087452 310.22743 +L 184.655899 307.142818 +L 194.724016 303.083122 +L 200.695451 300.925772 +L 201.875851 300.440929 +L 202.014721 299.534656 +L 202.223027 296.145854 +L 202.847945 279.97421 +L 203.611733 262.555625 +L 204.722698 243.300722 +L 205.000439 241.405157 +L 205.208745 241.281072 +L 205.417051 241.190203 +L 205.625356 240.641783 +L 205.903098 238.879501 +L 206.250274 234.871245 +L 206.666886 227.386779 +L 207.222368 212.754232 +L 207.847286 189.63848 +L 208.611074 152.446292 +L 209.722039 98.115653 +L 210.208086 84.128064 +L 210.624697 77.838988 +L 210.902439 76.012596 +L 211.110744 75.637781 +L 211.249615 75.788838 +L 211.457921 76.531475 +L 211.805097 78.899927 +L 212.291144 83.959649 +L 213.124368 95.46392 +L 214.790815 122.561759 +L 217.984838 174.136379 +L 221.734344 233.344628 +L 222.845309 255.586734 +L 224.858932 297.427563 +L 225.414414 303.945479 +L 225.900461 307.439142 +L 226.386508 309.512293 +L 226.872555 310.688649 +L 227.358602 311.303395 +L 227.844649 311.567383 +L 228.469567 311.616471 +L 229.441661 311.410973 +L 231.52472 310.678952 +L 237.42672 308.317185 +L 251.383213 302.686057 +L 257.215777 300.619611 +L 262.63173 298.93537 +L 268.047683 297.48472 +L 273.671941 296.211698 +L 279.643376 295.091553 +L 286.170294 294.098495 +L 293.391564 293.22893 +L 301.584928 292.470373 +L 311.097563 291.817992 +L 322.484951 291.267728 +L 336.510879 290.822258 +L 340.538126 290.728038 +L 340.538126 290.728038 +" style="fill:none;stroke:#1f77b4;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_15"> + <path clip-path="url(#p27c2e72490)" d="M 62.866399 266.297328 +L 63.352446 277.258791 +L 63.907928 284.473344 +L 64.602281 290.455262 +L 65.296634 294.670227 +L 65.990987 297.556616 +L 66.615905 299.267395 +L 67.240822 300.359684 +L 67.86574 301.016714 +L 68.560093 301.404903 +L 69.323881 301.567368 +L 70.295975 301.540063 +L 71.754116 301.255628 +L 74.670398 300.408557 +L 81.961104 298.279641 +L 87.515927 296.909933 +L 90.987692 296.010973 +L 91.195997 295.395074 +L 91.473739 293.612905 +L 91.89035 289.134206 +L 93.209621 270.856843 +L 94.876068 245.966783 +L 95.362115 233.637985 +L 95.848162 214.081082 +L 96.334209 183.49183 +L 96.889691 131.193282 +L 97.653479 58.892148 +L 98.000656 43.325294 +L 98.347832 36.629177 +L 98.625574 34.886958 +L 98.764444 34.771165 +L 98.903315 35.029097 +L 99.181056 36.417923 +L 99.597668 40.087479 +L 100.222585 47.930399 +L 101.194679 63.330924 +L 103.347173 102.106558 +L 105.916279 146.762638 +L 108.346514 184.959199 +L 110.776749 223.423611 +L 111.748843 242.537285 +L 112.651502 264.979221 +L 114.109643 302.787834 +L 114.59569 309.393114 +L 115.012302 312.26049 +L 115.359479 313.415149 +L 115.706655 313.953442 +L 116.123267 314.190452 +L 116.678749 314.211135 +L 117.789714 313.963003 +L 120.844867 312.984642 +L 125.497031 311.266209 +L 131.468466 308.804729 +L 145.702701 302.845614 +L 146.535924 302.461431 +L 146.74423 302.031488 +L 147.021971 300.705291 +L 147.438583 297.264395 +L 149.521642 277.702687 +L 150.007689 275.277951 +L 150.771477 272.151959 +L 151.188089 269.25094 +L 151.674136 264.140499 +L 152.229618 255.53056 +L 152.7851 243.195005 +L 153.340583 225.731784 +L 153.896065 200.829965 +L 154.451547 165.480474 +L 155.840253 67.925121 +L 156.187429 58.616015 +L 156.534606 54.536814 +L 156.742912 53.778165 +L 156.881782 53.789432 +L 157.090088 54.424133 +L 157.437265 56.752602 +L 157.923312 61.878327 +L 158.6871 72.591541 +L 160.075806 95.764418 +L 163.825311 159.07199 +L 168.199735 230.16584 +L 169.102394 250.480776 +L 170.143923 279.613278 +L 170.977146 300.999358 +L 171.463193 308.475943 +L 171.879805 311.835614 +L 172.226982 313.216169 +L 172.574158 313.870236 +L 172.99077 314.168666 +L 173.476817 314.221484 +L 174.379476 314.051137 +L 176.879146 313.277053 +L 181.323005 311.674608 +L 186.877828 309.421594 +L 202.153592 302.858033 +L 202.361898 302.286353 +L 202.639639 300.759088 +L 203.125686 296.499692 +L 204.722698 281.610184 +L 205.27818 277.906367 +L 205.764227 276.283613 +L 206.250274 274.700579 +L 206.666886 272.508303 +L 207.152933 268.508113 +L 207.708415 261.638852 +L 208.263897 251.792339 +L 208.81938 238.058344 +L 209.374862 218.86067 +L 209.930344 191.778208 +L 210.555262 148.864771 +L 211.527356 79.039535 +L 211.943968 64.015458 +L 212.291144 58.157917 +L 212.568885 56.375719 +L 212.707756 56.179728 +L 212.846627 56.351277 +L 213.054933 57.17924 +L 213.402109 59.742699 +L 213.957591 65.968658 +L 214.86025 79.271892 +L 216.735003 111.273059 +L 219.58185 158.850842 +L 223.886838 228.776934 +L 224.789497 248.820044 +L 225.831026 277.716707 +L 226.733685 301.0801 +L 227.219732 308.508303 +L 227.636344 311.838944 +L 227.98352 313.205666 +L 228.330696 313.852283 +L 228.747308 314.146312 +L 229.233355 314.196752 +L 230.136014 314.024363 +L 232.635684 313.24679 +L 237.148978 311.612286 +L 242.773237 309.322544 +L 259.576577 302.318754 +L 264.99253 300.402026 +L 270.200177 298.792904 +L 275.546694 297.375807 +L 281.101517 296.134674 +L 287.072952 295.029792 +L 293.59987 294.050268 +L 300.890575 293.18429 +L 309.22281 292.424933 +L 318.874315 291.775983 +L 330.331138 291.235872 +L 340.538126 290.902247 +L 340.538126 290.902247 +" style="fill:none;stroke:#ff7f0e;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_16"/> + <g id="line2d_17"/> + <g id="patch_3"> + <path d="M 48.982813 328.198437 +L 48.982813 20.798437 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="patch_4"> + <path d="M 48.982813 328.198437 +L 354.421712 328.198437 +" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/> + </g> + <g id="text_16"> + <!-- Variable = voltage --> + <g transform="translate(155.406169 14.798437)scale(0.1 -0.1)"> + <defs> + <path d="M 34.28125 27.484375 +Q 23.390625 27.484375 19.1875 25 +Q 14.984375 22.515625 14.984375 16.5 +Q 14.984375 11.71875 18.140625 8.90625 +Q 21.296875 6.109375 26.703125 6.109375 +Q 34.1875 6.109375 38.703125 11.40625 +Q 43.21875 16.703125 43.21875 25.484375 +L 43.21875 27.484375 +z +M 52.203125 31.203125 +L 52.203125 0 +L 43.21875 0 +L 43.21875 8.296875 +Q 40.140625 3.328125 35.546875 0.953125 +Q 30.953125 -1.421875 24.3125 -1.421875 +Q 15.921875 -1.421875 10.953125 3.296875 +Q 6 8.015625 6 15.921875 +Q 6 25.140625 12.171875 29.828125 +Q 18.359375 34.515625 30.609375 34.515625 +L 43.21875 34.515625 +L 43.21875 35.40625 +Q 43.21875 41.609375 39.140625 45 +Q 35.0625 48.390625 27.6875 48.390625 +Q 23 48.390625 18.546875 47.265625 +Q 14.109375 46.140625 10.015625 43.890625 +L 10.015625 52.203125 +Q 14.9375 54.109375 19.578125 55.046875 +Q 24.21875 56 28.609375 56 +Q 40.484375 56 46.34375 49.84375 +Q 52.203125 43.703125 52.203125 31.203125 +z +" id="DejaVuSans-97"/> + <path d="M 41.109375 46.296875 +Q 39.59375 47.171875 37.8125 47.578125 +Q 36.03125 48 33.890625 48 +Q 26.265625 48 22.1875 43.046875 +Q 18.109375 38.09375 18.109375 28.8125 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 20.953125 51.171875 25.484375 53.578125 +Q 30.03125 56 36.53125 56 +Q 37.453125 56 38.578125 55.875 +Q 39.703125 55.765625 41.0625 55.515625 +z +" id="DejaVuSans-114"/> + <path d="M 9.421875 54.6875 +L 18.40625 54.6875 +L 18.40625 0 +L 9.421875 0 +z +M 9.421875 75.984375 +L 18.40625 75.984375 +L 18.40625 64.59375 +L 9.421875 64.59375 +z +" id="DejaVuSans-105"/> + <path d="M 48.6875 27.296875 +Q 48.6875 37.203125 44.609375 42.84375 +Q 40.53125 48.484375 33.40625 48.484375 +Q 26.265625 48.484375 22.1875 42.84375 +Q 18.109375 37.203125 18.109375 27.296875 +Q 18.109375 17.390625 22.1875 11.75 +Q 26.265625 6.109375 33.40625 6.109375 +Q 40.53125 6.109375 44.609375 11.75 +Q 48.6875 17.390625 48.6875 27.296875 +z +M 18.109375 46.390625 +Q 20.953125 51.265625 25.265625 53.625 +Q 29.59375 56 35.59375 56 +Q 45.5625 56 51.78125 48.09375 +Q 58.015625 40.1875 58.015625 27.296875 +Q 58.015625 14.40625 51.78125 6.484375 +Q 45.5625 -1.421875 35.59375 -1.421875 +Q 29.59375 -1.421875 25.265625 0.953125 +Q 20.953125 3.328125 18.109375 8.203125 +L 18.109375 0 +L 9.078125 0 +L 9.078125 75.984375 +L 18.109375 75.984375 +z +" id="DejaVuSans-98"/> + <path d="M 9.421875 75.984375 +L 18.40625 75.984375 +L 18.40625 0 +L 9.421875 0 +z +" id="DejaVuSans-108"/> + <path d="M 56.203125 29.59375 +L 56.203125 25.203125 +L 14.890625 25.203125 +Q 15.484375 15.921875 20.484375 11.0625 +Q 25.484375 6.203125 34.421875 6.203125 +Q 39.59375 6.203125 44.453125 7.46875 +Q 49.3125 8.734375 54.109375 11.28125 +L 54.109375 2.78125 +Q 49.265625 0.734375 44.1875 -0.34375 +Q 39.109375 -1.421875 33.890625 -1.421875 +Q 20.796875 -1.421875 13.15625 6.1875 +Q 5.515625 13.8125 5.515625 26.8125 +Q 5.515625 40.234375 12.765625 48.109375 +Q 20.015625 56 32.328125 56 +Q 43.359375 56 49.78125 48.890625 +Q 56.203125 41.796875 56.203125 29.59375 +z +M 47.21875 32.234375 +Q 47.125 39.59375 43.09375 43.984375 +Q 39.0625 48.390625 32.421875 48.390625 +Q 24.90625 48.390625 20.390625 44.140625 +Q 15.875 39.890625 15.1875 32.171875 +z +" id="DejaVuSans-101"/> + <path id="DejaVuSans-32"/> + <path d="M 10.59375 45.40625 +L 73.1875 45.40625 +L 73.1875 37.203125 +L 10.59375 37.203125 +z +M 10.59375 25.484375 +L 73.1875 25.484375 +L 73.1875 17.1875 +L 10.59375 17.1875 +z +" id="DejaVuSans-61"/> + <path d="M 2.984375 54.6875 +L 12.5 54.6875 +L 29.59375 8.796875 +L 46.6875 54.6875 +L 56.203125 54.6875 +L 35.6875 0 +L 23.484375 0 +z +" id="DejaVuSans-118"/> + <path d="M 30.609375 48.390625 +Q 23.390625 48.390625 19.1875 42.75 +Q 14.984375 37.109375 14.984375 27.296875 +Q 14.984375 17.484375 19.15625 11.84375 +Q 23.34375 6.203125 30.609375 6.203125 +Q 37.796875 6.203125 41.984375 11.859375 +Q 46.1875 17.53125 46.1875 27.296875 +Q 46.1875 37.015625 41.984375 42.703125 +Q 37.796875 48.390625 30.609375 48.390625 +z +M 30.609375 56 +Q 42.328125 56 49.015625 48.375 +Q 55.71875 40.765625 55.71875 27.296875 +Q 55.71875 13.875 49.015625 6.21875 +Q 42.328125 -1.421875 30.609375 -1.421875 +Q 18.84375 -1.421875 12.171875 6.21875 +Q 5.515625 13.875 5.515625 27.296875 +Q 5.515625 40.765625 12.171875 48.375 +Q 18.84375 56 30.609375 56 +z +" id="DejaVuSans-111"/> + <path d="M 45.40625 27.984375 +Q 45.40625 37.75 41.375 43.109375 +Q 37.359375 48.484375 30.078125 48.484375 +Q 22.859375 48.484375 18.828125 43.109375 +Q 14.796875 37.75 14.796875 27.984375 +Q 14.796875 18.265625 18.828125 12.890625 +Q 22.859375 7.515625 30.078125 7.515625 +Q 37.359375 7.515625 41.375 12.890625 +Q 45.40625 18.265625 45.40625 27.984375 +z +M 54.390625 6.78125 +Q 54.390625 -7.171875 48.1875 -13.984375 +Q 42 -20.796875 29.203125 -20.796875 +Q 24.46875 -20.796875 20.265625 -20.09375 +Q 16.0625 -19.390625 12.109375 -17.921875 +L 12.109375 -9.1875 +Q 16.0625 -11.328125 19.921875 -12.34375 +Q 23.78125 -13.375 27.78125 -13.375 +Q 36.625 -13.375 41.015625 -8.765625 +Q 45.40625 -4.15625 45.40625 5.171875 +L 45.40625 9.625 +Q 42.625 4.78125 38.28125 2.390625 +Q 33.9375 0 27.875 0 +Q 17.828125 0 11.671875 7.65625 +Q 5.515625 15.328125 5.515625 27.984375 +Q 5.515625 40.671875 11.671875 48.328125 +Q 17.828125 56 27.875 56 +Q 33.9375 56 38.28125 53.609375 +Q 42.625 51.21875 45.40625 46.390625 +L 45.40625 54.6875 +L 54.390625 54.6875 +z +" id="DejaVuSans-103"/> + </defs> + <use xlink:href="#DejaVuSans-86"/> + <use x="60.658203" xlink:href="#DejaVuSans-97"/> + <use x="121.9375" xlink:href="#DejaVuSans-114"/> + <use x="163.050781" xlink:href="#DejaVuSans-105"/> + <use x="190.833984" xlink:href="#DejaVuSans-97"/> + <use x="252.113281" xlink:href="#DejaVuSans-98"/> + <use x="315.589844" xlink:href="#DejaVuSans-108"/> + <use x="343.373047" xlink:href="#DejaVuSans-101"/> + <use x="404.896484" xlink:href="#DejaVuSans-32"/> + <use x="436.683594" xlink:href="#DejaVuSans-61"/> + <use x="520.472656" xlink:href="#DejaVuSans-32"/> + <use x="552.259766" xlink:href="#DejaVuSans-118"/> + <use x="611.439453" xlink:href="#DejaVuSans-111"/> + <use x="672.621094" xlink:href="#DejaVuSans-108"/> + <use x="700.404297" xlink:href="#DejaVuSans-116"/> + <use x="739.613281" xlink:href="#DejaVuSans-97"/> + <use x="800.892578" xlink:href="#DejaVuSans-103"/> + <use x="864.369141" xlink:href="#DejaVuSans-101"/> + </g> + </g> + </g> + <g id="legend_1"> + <g id="text_17"> + <!-- Location --> + <g transform="translate(386.418025 170.958594)scale(0.1 -0.1)"> + <defs> + <path d="M 9.8125 72.90625 +L 19.671875 72.90625 +L 19.671875 8.296875 +L 55.171875 8.296875 +L 55.171875 0 +L 9.8125 0 +z +" id="DejaVuSans-76"/> + <path d="M 48.78125 52.59375 +L 48.78125 44.1875 +Q 44.96875 46.296875 41.140625 47.34375 +Q 37.3125 48.390625 33.40625 48.390625 +Q 24.65625 48.390625 19.8125 42.84375 +Q 14.984375 37.3125 14.984375 27.296875 +Q 14.984375 17.28125 19.8125 11.734375 +Q 24.65625 6.203125 33.40625 6.203125 +Q 37.3125 6.203125 41.140625 7.25 +Q 44.96875 8.296875 48.78125 10.40625 +L 48.78125 2.09375 +Q 45.015625 0.34375 40.984375 -0.53125 +Q 36.96875 -1.421875 32.421875 -1.421875 +Q 20.0625 -1.421875 12.78125 6.34375 +Q 5.515625 14.109375 5.515625 27.296875 +Q 5.515625 40.671875 12.859375 48.328125 +Q 20.21875 56 33.015625 56 +Q 37.15625 56 41.109375 55.140625 +Q 45.0625 54.296875 48.78125 52.59375 +z +" id="DejaVuSans-99"/> + <path d="M 54.890625 33.015625 +L 54.890625 0 +L 45.90625 0 +L 45.90625 32.71875 +Q 45.90625 40.484375 42.875 44.328125 +Q 39.84375 48.1875 33.796875 48.1875 +Q 26.515625 48.1875 22.3125 43.546875 +Q 18.109375 38.921875 18.109375 30.90625 +L 18.109375 0 +L 9.078125 0 +L 9.078125 54.6875 +L 18.109375 54.6875 +L 18.109375 46.1875 +Q 21.34375 51.125 25.703125 53.5625 +Q 30.078125 56 35.796875 56 +Q 45.21875 56 50.046875 50.171875 +Q 54.890625 44.34375 54.890625 33.015625 +z +" id="DejaVuSans-110"/> + </defs> + <use xlink:href="#DejaVuSans-76"/> + <use x="53.962891" xlink:href="#DejaVuSans-111"/> + <use x="115.144531" xlink:href="#DejaVuSans-99"/> + <use x="170.125" xlink:href="#DejaVuSans-97"/> + <use x="231.404297" xlink:href="#DejaVuSans-116"/> + <use x="270.613281" xlink:href="#DejaVuSans-105"/> + <use x="298.396484" xlink:href="#DejaVuSans-111"/> + <use x="359.578125" xlink:href="#DejaVuSans-110"/> + </g> + </g> + <g id="line2d_18"> + <path d="M 360.285212 182.136719 +L 380.285212 182.136719 +" style="fill:none;stroke:#1f77b4;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_19"/> + <g id="text_18"> + <!-- (location 3 1) --> + <g transform="translate(388.285212 185.636719)scale(0.1 -0.1)"> + <defs> + <path d="M 31 75.875 +Q 24.46875 64.65625 21.28125 53.65625 +Q 18.109375 42.671875 18.109375 31.390625 +Q 18.109375 20.125 21.3125 9.0625 +Q 24.515625 -2 31 -13.1875 +L 23.1875 -13.1875 +Q 15.875 -1.703125 12.234375 9.375 +Q 8.59375 20.453125 8.59375 31.390625 +Q 8.59375 42.28125 12.203125 53.3125 +Q 15.828125 64.359375 23.1875 75.875 +z +" id="DejaVuSans-40"/> + <path d="M 40.578125 39.3125 +Q 47.65625 37.796875 51.625 33 +Q 55.609375 28.21875 55.609375 21.1875 +Q 55.609375 10.40625 48.1875 4.484375 +Q 40.765625 -1.421875 27.09375 -1.421875 +Q 22.515625 -1.421875 17.65625 -0.515625 +Q 12.796875 0.390625 7.625 2.203125 +L 7.625 11.71875 +Q 11.71875 9.328125 16.59375 8.109375 +Q 21.484375 6.890625 26.8125 6.890625 +Q 36.078125 6.890625 40.9375 10.546875 +Q 45.796875 14.203125 45.796875 21.1875 +Q 45.796875 27.640625 41.28125 31.265625 +Q 36.765625 34.90625 28.71875 34.90625 +L 20.21875 34.90625 +L 20.21875 43.015625 +L 29.109375 43.015625 +Q 36.375 43.015625 40.234375 45.921875 +Q 44.09375 48.828125 44.09375 54.296875 +Q 44.09375 59.90625 40.109375 62.90625 +Q 36.140625 65.921875 28.71875 65.921875 +Q 24.65625 65.921875 20.015625 65.03125 +Q 15.375 64.15625 9.8125 62.3125 +L 9.8125 71.09375 +Q 15.4375 72.65625 20.34375 73.4375 +Q 25.25 74.21875 29.59375 74.21875 +Q 40.828125 74.21875 47.359375 69.109375 +Q 53.90625 64.015625 53.90625 55.328125 +Q 53.90625 49.265625 50.4375 45.09375 +Q 46.96875 40.921875 40.578125 39.3125 +z +" id="DejaVuSans-51"/> + <path d="M 8.015625 75.875 +L 15.828125 75.875 +Q 23.140625 64.359375 26.78125 53.3125 +Q 30.421875 42.28125 30.421875 31.390625 +Q 30.421875 20.453125 26.78125 9.375 +Q 23.140625 -1.703125 15.828125 -13.1875 +L 8.015625 -13.1875 +Q 14.5 -2 17.703125 9.0625 +Q 20.90625 20.125 20.90625 31.390625 +Q 20.90625 42.671875 17.703125 53.65625 +Q 14.5 64.65625 8.015625 75.875 +z +" id="DejaVuSans-41"/> + </defs> + <use xlink:href="#DejaVuSans-40"/> + <use x="39.013672" xlink:href="#DejaVuSans-108"/> + <use x="66.796875" xlink:href="#DejaVuSans-111"/> + <use x="127.978516" xlink:href="#DejaVuSans-99"/> + <use x="182.958984" xlink:href="#DejaVuSans-97"/> + <use x="244.238281" xlink:href="#DejaVuSans-116"/> + <use x="283.447266" xlink:href="#DejaVuSans-105"/> + <use x="311.230469" xlink:href="#DejaVuSans-111"/> + <use x="372.412109" xlink:href="#DejaVuSans-110"/> + <use x="435.791016" xlink:href="#DejaVuSans-32"/> + <use x="467.578125" xlink:href="#DejaVuSans-51"/> + <use x="531.201172" xlink:href="#DejaVuSans-32"/> + <use x="562.988281" xlink:href="#DejaVuSans-49"/> + <use x="626.611328" xlink:href="#DejaVuSans-41"/> + </g> + </g> + <g id="line2d_20"> + <path d="M 360.285212 196.814844 +L 380.285212 196.814844 +" style="fill:none;stroke:#ff7f0e;stroke-linecap:square;stroke-width:1.5;"/> + </g> + <g id="line2d_21"/> + <g id="text_19"> + <!-- (location 4 1) --> + <g transform="translate(388.285212 200.314844)scale(0.1 -0.1)"> + <use xlink:href="#DejaVuSans-40"/> + <use x="39.013672" xlink:href="#DejaVuSans-108"/> + <use x="66.796875" xlink:href="#DejaVuSans-111"/> + <use x="127.978516" xlink:href="#DejaVuSans-99"/> + <use x="182.958984" xlink:href="#DejaVuSans-97"/> + <use x="244.238281" xlink:href="#DejaVuSans-116"/> + <use x="283.447266" xlink:href="#DejaVuSans-105"/> + <use x="311.230469" xlink:href="#DejaVuSans-111"/> + <use x="372.412109" xlink:href="#DejaVuSans-110"/> + <use x="435.791016" xlink:href="#DejaVuSans-32"/> + <use x="467.578125" xlink:href="#DejaVuSans-52"/> + <use x="531.201172" xlink:href="#DejaVuSans-32"/> + <use x="562.988281" xlink:href="#DejaVuSans-49"/> + <use x="626.611328" xlink:href="#DejaVuSans-41"/> + </g> + </g> + </g> + </g> + <defs> + <clipPath id="p27c2e72490"> + <rect height="307.4" width="305.4389" x="48.982813" y="20.798437"/> + </clipPath> + </defs> +</svg> diff --git a/doc/tutorial/single_cell_model.rst b/doc/tutorial/single_cell_model.rst index 5e98fbd5095665d08b4c986576756c127848f1a5..ac6acdf3aa66e6aa7927b403bbdb3510cb49261e 100644 --- a/doc/tutorial/single_cell_model.rst +++ b/doc/tutorial/single_cell_model.rst @@ -1,23 +1,24 @@ -.. _gs_single_cell: +.. _tutorialsimplecell: -A single cell model -================================ +A simple single cell model +========================== Building and testing detailed models of individual cells, then optimizing their parameters is usually the first step in building models with multi-compartment cells. Arbor supports a *single cell model* workflow for this purpose, which is a good way to introduce Arbor's cell modelling concepts and approach. -This guide will walk through a series of single cell models of increasing complexity. -Links are provided to separate documentation that covers relevant topics in more detail. +.. Note:: -In an interactive Python interpreter, you can use ``help()`` on any class or function to -obtain some documentation. (Try, for example, ``help(arbor.simulation``). + **Concepts covered in this example:** -.. _single_soma: + 1. Intro to building a morphology from a :class:`arbor.segment_tree`. + 2. Intro to region and locset expressions. + 3. Intro to decors and cell decorations. + 4. Building a :class:`arbor.cable_cell` object. + 5. Building a :class:`arbor.single_cell_model` object. + 6. Running a simulation and visualising the results. -Single segment cell with HH dynamics ----------------------------------------------------- The most trivial representation of a cell in Arbor is to model the entire cell as a single cylinder. The following example shows the steps required to construct a model of a diff --git a/python/example/morph.swc b/python/example/morph.swc new file mode 100644 index 0000000000000000000000000000000000000000..597322185d60f9a6d5f424246495028aaba568e0 --- /dev/null +++ b/python/example/morph.swc @@ -0,0 +1,15 @@ +# id, tag, x, y, z, r, parent + 1 1 0.0 0.0 0.0 2.0 -1 # seg0 prox / seg9 prox + 2 1 40.0 0.0 0.0 2.0 1 # seg0 dist + 3 3 40.0 0.0 0.0 0.8 2 # seg1 prox + 4 3 80.0 0.0 0.0 0.8 3 # seg1 dist / seg2 prox + 5 3 120.0 -5.0 0.0 0.8 4 # seg2 dist / seg3 prox + 6 3 200.0 40.0 0.0 0.4 5 # seg3 dist / seg4 prox + 7 3 260.0 60.0 0.0 0.2 6 # seg4 dist + 8 3 120.0 -5.0 0.0 0.5 5 # seg5 prox + 9 3 190.0 -30.0 0.0 0.5 8 # seg5 dist / seg6 prox / seg7 prox + 10 4 240.0 -70.0 0.0 0.2 9 # seg6 dist + 11 4 230.0 -10.0 0.0 0.2 9 # seg7 dist / seg8 prox + 12 4 360.0 -20.0 0.0 0.2 11 # seg8 dist + 13 2 -70.0 0.0 0.0 0.4 1 # seg9 dist / seg10 prox + 14 2 -100.0 0.0 0.0 0.4 13 # seg10 dist diff --git a/python/example/single_cell_detailed.py b/python/example/single_cell_detailed.py new file mode 100755 index 0000000000000000000000000000000000000000..140b4d900cf6880aa1e36e1892997f2e29e0c067 --- /dev/null +++ b/python/example/single_cell_detailed.py @@ -0,0 +1,116 @@ +import arbor +import pandas +import seaborn +import sys +from arbor import mechanism as mech + +#(1) Read the morphology from an SWC file. + +# Read the SWC filename from input +# Example from docs: morph.swc + +if len(sys.argv) < 2: + print("No SWC file passed to the program") + sys.exit(0) + +filename = sys.argv[1] +morph = arbor.load_swc_arbor(filename) + +#(2) Create and populate the label dictionary. + +labels = arbor.label_dict() + +# Regions: + +labels['soma'] = '(tag 1)' +labels['axon'] = '(tag 2)' +labels['dend'] = '(tag 3)' +labels['last'] = '(tag 4)' + +labels['all'] = '(all)' + +labels['gt_1.5'] = '(radius-ge (region "all") 1.5)' +labels['custom'] = '(join (region "last") (region "gt_1.5"))' + +# Locsets: + +labels['root'] = '(root)' +labels['terminal'] = '(terminal)' +labels['custom_terminal'] = '(restrict (locset "terminal") (region "custom"))' +labels['axon_terminal'] = '(restrict (locset "terminal") (region "axon"))' + +# (3) Create and populate the decor. + +decor = arbor.decor() + +# Set the default properties of the cell (this overrides the model defaults). + +decor.set_property(Vm =-55) + +# Override the cell defaults. + +decor.paint('"custom"', tempK=270) +decor.paint('"soma"', Vm=-50) + +# Paint density mechanisms. + +decor.paint('"all"', 'pas') +decor.paint('"custom"', 'hh') +decor.paint('"dend"', mech('Ih', {'gbar': 0.001})) + +# Place stimuli and spike detectors. + +decor.place('"root"', arbor.iclamp(10, 1, current=2)) +decor.place('"root"', arbor.iclamp(30, 1, current=2)) +decor.place('"root"', arbor.iclamp(50, 1, current=2)) +decor.place('"axon_terminal"', arbor.spike_detector(-10)) + +# Set cv_policy + +soma_policy = arbor.cv_policy_single('"soma"') +dflt_policy = arbor.cv_policy_max_extent(1.0) +policy = dflt_policy | soma_policy +decor.discretization(policy) + +# (4) Create the cell. + +cell = arbor.cable_cell(morph, labels, decor) + +# (5) Construct the model + +model = arbor.single_cell_model(cell) + +# (6) Set the model default properties + +model.properties.set_property(Vm =-65, tempK=300, rL=35.4, cm=0.01) +model.properties.set_ion('na', int_con=10, ext_con=140, rev_pot=50, method='nernst/na') +model.properties.set_ion('k', int_con=54.4, ext_con=2.5, rev_pot=-77) + +# Extend the default catalogue with the allen catalogue. + +model.catalogue.extend(arbor.allen_catalogue(), "") + +# (7) Add probes. + +model.probe('voltage', where='"custom_terminal"', frequency=50000) + +# (8) Run the simulation. + +model.run(tfinal=100, dt=0.025) + +# (9) Print the spikes. + +print(len(model.spikes), 'spikes recorded:') + +# Print the spike times. + +for s in model.spikes: + print(s) + +# (10) Plot the voltages + +df = pandas.DataFrame() +for t in model.traces: + df=df.append(pandas.DataFrame({'t/ms': t.time, 'U/mV': t.value, 'Location': str(t.location), 'Variable': t.variable})) + +seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV",hue="Location",col="Variable",ci=None).savefig('single_cell_detailed_result.svg') diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py new file mode 100644 index 0000000000000000000000000000000000000000..46a9cd6410dd5e2cf9212ba3ee1252a789983e3d --- /dev/null +++ b/python/example/single_cell_detailed_recipe.py @@ -0,0 +1,178 @@ +import arbor +import pandas +import seaborn +import sys +from arbor import mechanism as mech + +#(1) Creat a cell. + +# Create the morphology + +# Read the SWC filename from input +# Example from docs: morph.swc + +if len(sys.argv) < 2: + print("No SWC file passed to the program") + sys.exit(0) + +filename = sys.argv[1] +morph = arbor.load_swc_arbor(filename) + +# Create and populate the label dictionary. + +labels = arbor.label_dict() + +# Regions: + +labels['soma'] = '(tag 1)' +labels['axon'] = '(tag 2)' +labels['dend'] = '(tag 3)' +labels['last'] = '(tag 4)' + +labels['all'] = '(all)' + +labels['gt_1.5'] = '(radius-ge (region "all") 1.5)' +labels['custom'] = '(join (region "last") (region "gt_1.5"))' + +# Locsets: + +labels['root'] = '(root)' +labels['terminal'] = '(terminal)' +labels['custom_terminal'] = '(restrict (locset "terminal") (region "custom"))' +labels['axon_terminal'] = '(restrict (locset "terminal") (region "axon"))' + +# Create and populate the decor. + +decor = arbor.decor() + +# Set the default properties. + +decor.set_property(Vm =-55) + +# Override the defaults. + +decor.paint('"custom"', tempK=270) +decor.paint('"soma"', Vm=-50) + +# Paint density mechanisms. + +decor.paint('"all"', 'pas') +decor.paint('"custom"', 'hh') +decor.paint('"dend"', mech('Ih', {'gbar': 0.001})) + +# Place stimuli and spike detectors. + +decor.place('"root"', arbor.iclamp(10, 1, current=2)) +decor.place('"root"', arbor.iclamp(30, 1, current=2)) +decor.place('"root"', arbor.iclamp(50, 1, current=2)) +decor.place('"axon_terminal"', arbor.spike_detector(-10)) + +# Set cv_policy + +soma_policy = arbor.cv_policy_single('"soma"') +dflt_policy = arbor.cv_policy_max_extent(1.0) +policy = dflt_policy | soma_policy +decor.discretization(policy) + +# Create a cell + +cell = arbor.cable_cell(morph, labels, decor) + +# (2) Declare a probe. + +probe = arbor.cable_probe_membrane_voltage('"custom_terminal"') + +# (3) Create a recipe class and instantiate a recipe + +class single_recipe (arbor.recipe): + + def __init__(self, cell, probes): + # The base C++ class constructor must be called first, to ensure that + # all memory in the C++ class is initialized correctly. + arbor.recipe.__init__(self) + self.the_cell = cell + self.the_probes = probes + + self.the_cat = arbor.default_catalogue() + self.the_cat.extend(arbor.allen_catalogue(), "") + + self.the_props = arbor.cable_global_properties() + self.the_props.set_property(Vm=-65, tempK=300, rL=35.4, cm=0.01) + self.the_props.set_ion(ion='na', int_con=10, ext_con=140, rev_pot=50, method='nernst/na') + self.the_props.set_ion(ion='k', int_con=54.4, ext_con=2.5, rev_pot=-77) + self.the_props.set_ion(ion='ca', int_con=5e-5, ext_con=2, rev_pot=132.5) + + self.the_props.register(self.the_cat) + + def num_cells(self): + return 1 + + def num_sources(self, gid): + return 1 + + def num_targets(self, gid): + return 0 + + def cell_kind(self, gid): + return arbor.cell_kind.cable + + def cell_description(self, gid): + return self.the_cell + + def probes(self, gid): + return self.the_probes + + def connections_on(self, gid): + return [] + + def gap_junction_on(self, gid): + return [] + + def event_generators(self, gid): + return [] + + def global_properties(self, gid): + return self.the_props + +recipe = single_recipe(cell, [probe]) + +# (4) Create an execution context + +context = arbor.context() + +# (5) Create a domain decomposition + +domains = arbor.partition_load_balance(recipe, context) + +# (6) Create a simulation + +sim = arbor.simulation(recipe, domains, context) + +# Instruct the simulation to record the spikes and sample the probe + +sim.record(arbor.spike_recording.all) + +probe_id = arbor.cell_member(0,0) +handle = sim.sample(probe_id, arbor.regular_schedule(0.02)) + +# (7) Run the simulation + +sim.run(tfinal=100, dt=0.025) + +# (8) Print or display the results + +spikes = sim.spikes() +print(len(spikes), 'spikes recorded:') +for s in spikes: + print(s) + +data = [] +meta = [] +for d, m in sim.samples(handle): + data.append(d) + meta.append(m) + +df = pandas.DataFrame() +for i in range(len(data)): + df = df.append(pandas.DataFrame({'t/ms': data[i][:, 0], 'U/mV': data[i][:, 1], 'Location': str(meta[i]), 'Variable':'voltage'})) +seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV",hue="Location",col="Variable",ci=None).savefig('single_cell_recipe_result.svg') \ No newline at end of file diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index 7133178c4b57a509a0820c549570fcc92c34f6cc..5584b9f5d41176679339ef2a9763cb05113b5cda 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -23,7 +23,7 @@ if len(sys.argv) < 2: sys.exit(0) filename = sys.argv[1] -morpho = arbor.load_swc(filename) +morpho = arbor.load_swc_arbor(filename) # Define the regions and locsets in the model. defs = {'soma': '(tag 1)', # soma has tag 1 in swc files. diff --git a/python/morphology.cpp b/python/morphology.cpp index 0412b3d0ec74cb7d76c775bf24878dbfbbbb9663..fb999543fe493d9cb5f51312cff24e2ab84afaf1 100644 --- a/python/morphology.cpp +++ b/python/morphology.cpp @@ -234,7 +234,7 @@ void register_morphology(py::module& m) { // Function that creates a morphology from an swc file. // Wraps calls to C++ functions arborio::parse_swc() and arborio::load_swc_arbor(). - m.def("load_swc", + m.def("load_swc_arbor", [](std::string fname) { std::ifstream fid{fname}; if (!fid.good()) { diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 316594b01b586a07daa0d75da2ac661e3401e315..f3e09600f3e94e8d288eab1be74beaee846a3fa5 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -107,17 +107,21 @@ fi if [[ "${WITH_PYTHON}" == "true" ]]; then progress "Building python module" - make pyarb -j4 || error "building pyarb" + make pyarb -j4 || error "building pyarb" progress "Python unit tests" - python$PY $python_path/test/unit/runner.py -v2 || error "running python unit tests (serial)" + python$PY $python_path/test/unit/runner.py -v2 || error "running python unit tests (serial)" progress "Python example: network_ring" - python$PY $python_path/example/network_ring.py || error "running python network_ring example" + python$PY $python_path/example/network_ring.py || error "running python network_ring example" progress "Python example: single_cell_model" - python$PY $python_path/example/single_cell_model.py || error "running python single_cell_model example" + python$PY $python_path/example/single_cell_model.py || error "running python single_cell_model example" progress "Python example: single_cell_recipe" - python$PY $python_path/example/single_cell_recipe.py || error "running python single_cell_recipe example" + python$PY $python_path/example/single_cell_recipe.py || error "running python single_cell_recipe example" + progress "Python example: single_cell_detailed" + python$PY $python_path/example/single_cell_detailed.py $python_path/example/morph.swc || error "running python single_cell_detailed example" + progress "Python example: single_cell_detailed_recipe" + python$PY $python_path/example/single_cell_detailed_recipe.py $python_path/example/morph.swc || error "running python single_cell_detailed_recipe example" progress "Python example: single_cell_swc" - python$PY $python_path/example/single_cell_swc.py $base_path/test/unit/swc/pyramidal.swc || error "running python single_cell_swc example" + python$PY $python_path/example/single_cell_swc.py $base_path/test/unit/swc/pyramidal.swc || error "running python single_cell_swc example" if [[ "${WITH_DISTRIBUTED}" = "mpi" ]]; then if [[ "$TRAVIS_OS_NAME" = "osx" ]]; then progress "Python distributed unit tests (MPI)"