Skip to content
Snippets Groups Projects
Unverified Commit 28a49a07 authored by Nora Abi Akar's avatar Nora Abi Akar Committed by GitHub
Browse files

Docs: various changes (#1216)

* Various DOC changes/updates, most importantly to:
* Single Cell Model
* Recipe
parent 08c037de
No related branches found
No related tags found
No related merge requests found
...@@ -10,11 +10,12 @@ Arbor cable cells are constructed from a morphology and a label dictionary, ...@@ -10,11 +10,12 @@ Arbor cable cells are constructed from a morphology and a label dictionary,
and provide a rich interface for specifying the cell's dynamics. and provide a rich interface for specifying the cell's dynamics.
.. note:: .. note::
The cable cell does not have *one* dedicated page, it has a few more! This page describes how to build a full description of a cable cell, based on three components that are broken out into their own pages: The cable cell has more than *one* dedicated page, it has a few more! This page describes how to build a full
description of a cable cell, based on three components that are broken out into their own pages:
* :ref:`morphology descriptions <morph-morphology>`; * :ref:`Morphology descriptions <morph-morphology>`
* :ref:`label dictionary <labels-dictionary>` that are used to describe :ref:`locations <labels-locset>` and :ref:`regions <labels-region>` on a cell; * :ref:`Label dictionaries <labels-dictionary>`
* :ref:`mechanisms <mechanisms>`. * :ref:`Mechanisms <mechanisms>`
It can be helpful to consult those pages for some of the sections of this page. It can be helpful to consult those pages for some of the sections of this page.
...@@ -24,13 +25,13 @@ Decoration ...@@ -24,13 +25,13 @@ Decoration
---------------- ----------------
A cable cell is *decorated* by specifying the distribution and placement of dynamics A cable cell is *decorated* by specifying the distribution and placement of dynamics
on the cell to produce a full description on the cell. The decorations, coupled with a description of a cell morphology, are all
of a cell morphology and its dynamics with all information required to build that is required to build a standalone single-cell model, or a cell that is part of
a standalone single-cell model, or as part of a larger network. a larger network.
Decoration uses region and locset descriptions, with Decorations use :ref:`region <labels-region>` and :ref:`locset <labels-locset>`
their respective use for this purpose reflected in the two broad classes descriptions, with their respective use for this purpose reflected in the two broad
of dynamics in Arbor: classes of dynamics in Arbor:
* *Painted dynamics* are applied to regions of a cell, and are associated with * *Painted dynamics* are applied to regions of a cell, and are associated with
an area of the membrane or volume of the cable. an area of the membrane or volume of the cable.
...@@ -163,7 +164,7 @@ Every mechanism is described by a string with its name, and ...@@ -163,7 +164,7 @@ Every mechanism is described by a string with its name, and
an optional list of key-value pairs that define its range parameters. an optional list of key-value pairs that define its range parameters.
Because a global parameter is fixed over the entire spatial extent Because a global parameter is fixed over the entire spatial extent
of a density mechanism, a new mechanism has to created for every of a density mechanism, a new mechanism has to be created for every
combination of global parameter values. combination of global parameter values.
Take for example a mechanism passive leaky dynamics: Take for example a mechanism passive leaky dynamics:
......
...@@ -76,23 +76,28 @@ Cell kinds ...@@ -76,23 +76,28 @@ Cell kinds
* **Gap Junction Sites**: These refer to the sites of :ref:`gap junctions <modelgapjunctions>`. * **Gap Junction Sites**: These refer to the sites of :ref:`gap junctions <modelgapjunctions>`.
They are declared by specifying a location on a branch of the cell. They are declared by specifying a location on a branch of the cell.
Because cable cells are the main cell kind in Arbor and have more properties than listed here, they have a :ref:`dedicated page <cablecell>`. Because cable cells are the main cell kind in Arbor and have more properties than listed here, they have a
:ref:`dedicated page <cablecell>`.
2. **LIF Cells** 2. **LIF Cells**
A single compartment leaky integrate and fire neuron with one **source** and one **target**. LIF cells are single compartment leaky integrate and fire neurons with one **source** and one **target**.
LIF cells does not support adding additional **sources** or **targets** or gap junctions. LIF cells do not support adding additional **sources** or **targets**. They do not support **gap junctions**.
They are typically used to simulate point-neuron networks.
3. **Spiking Cells** 3. **Spiking Cells**
Spike source from values inserted via a `schedule description`. It is a point neuron with one built-in **source** and no **targets**. Spiking cells act as spike sources from values inserted via a `schedule description`.
It does not support adding additional **sources** or **targets**. It does not support gap junctions. They are point neurons with one built-in **source** and no **targets**.
They do not support adding additional **sources** or **targets**. They do not support **gap junctions**.
4. **Benchmark Cells** 4. **Benchmark Cells**
Proxy cell used for benchmarking, and used by developers to benchmark the spike exchange and event delivery infrastructure. Benchmark cells are proxy cells used for benchmarking, and used by developers to benchmark the spike exchange and
event delivery infrastructure.
Most Arbor users will want to use the cable cell, because it's the only cell kind that supports complex morphologies and user-defined mechanisms. See cable cells :ref:`dedicated page <cablecell>`. The LIF cell can be used to build networks with point-neurons. Most Arbor users will want to use the cable cell, which is the only cell kind that supports complex morphologies
and user-defined mechanisms. You can visit the :ref:`cable cell page <cablecell>` for more information.
API API
--- ---
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Concepts Concepts
==================== ====================
To understand how to use Arbor, it is helpful if you understand some of its concepts. To learn how to use Arbor, it is helpful to understand some of its concepts.
Arbor's design aims to enable scalability through abstraction. Arbor's design aims to enable scalability through abstraction.
...@@ -17,6 +17,10 @@ To be able to simulate a model, three basic steps need to be considered: ...@@ -17,6 +17,10 @@ To be able to simulate a model, three basic steps need to be considered:
2. Define the computational resources available to execute the model; 2. Define the computational resources available to execute the model;
3. Initiate and execute a simulation of the recipe on the chosen hardware resources. 3. Initiate and execute a simulation of the recipe on the chosen hardware resources.
The python front-end further abstracts away some of these steps for single cell models, where users only need to
describe the cell and simulation; and the details of the recipe and computational resources construction are
handled under the hood. Generally speaking though, these 3 steps are the building blocks of an Arbor application.
:ref:`Recipes <modelrecipe>` represent a set of neuron constructions and connections with *mechanisms* specifying ion channel and synapse dynamics in a cell-oriented manner. This has the advantage that cell data can be initiated in parallel. :ref:`Recipes <modelrecipe>` represent a set of neuron constructions and connections with *mechanisms* specifying ion channel and synapse dynamics in a cell-oriented manner. This has the advantage that cell data can be initiated in parallel.
A cell represents the smallest unit of computation and forms the smallest unit of work distributed across processes. Arbor has built-in support for different :ref:`cell types <modelcells>`, which can be extended by adding new cell types to the C++ cell group interface. A cell represents the smallest unit of computation and forms the smallest unit of work distributed across processes. Arbor has built-in support for different :ref:`cell types <modelcells>`, which can be extended by adding new cell types to the C++ cell group interface.
......
...@@ -5,8 +5,8 @@ Interconnectivity ...@@ -5,8 +5,8 @@ Interconnectivity
Networks can be regarded as a sort of graph, where the nodes are cells and the edges Networks can be regarded as a sort of graph, where the nodes are cells and the edges
describe the communications between them. In Arbor, two sorts of edges are modelled: a describe the communications between them. In Arbor, two sorts of edges are modelled: a
connection abstracts the propagation of action potentials (spikes) through the network, **connection** abstracts the propagation of action potentials (spikes) through the network,
while a gap junction is used to describe a direct electrical connection between two cells. while a **gap junction** is used to describe a direct electrical connection between two cells.
Connections only capture the propagation delay and attenuation associated with spike Connections only capture the propagation delay and attenuation associated with spike
connectivity: the biophysical modelling of the chemical synapses themselves is the connectivity: the biophysical modelling of the chemical synapses themselves is the
responsibility of the target cell model. responsibility of the target cell model.
......
...@@ -236,7 +236,7 @@ Locset expressions ...@@ -236,7 +236,7 @@ Locset expressions
The result of ``(location 1 0.5)``, which corresponds to the mid point of branch 1. The result of ``(location 1 0.5)``, which corresponds to the mid point of branch 1.
.. label:: (terminal} .. label:: (terminal)
The location of terminal points, which are the most distal locations on the morphology. The location of terminal points, which are the most distal locations on the morphology.
These will typically correspond to the tips, or end points, of dendrites and axons. These will typically correspond to the tips, or end points, of dendrites and axons.
......
...@@ -90,9 +90,7 @@ tag 2 coloured grey for axon; tag 3 coloured blue for basal dendrites. ...@@ -90,9 +90,7 @@ tag 2 coloured grey for axon; tag 3 coloured blue for basal dendrites.
:align: center :align: center
Example Python code to generate this morphology is in the :class:`segment_tree<arbor.segment_tree>` Example Python code to generate this morphology is in the :class:`segment_tree<arbor.segment_tree>`
documentation :ref:`below <morph-label-seg-code>`. documentation :ref:`here <morph-label-seg-code>`.
We can apply the following labels to the segments:
* The tree is composed of 11 segments (1 soma, 2 axon, 8 dendrite). * The tree is composed of 11 segments (1 soma, 2 axon, 8 dendrite).
* The proximal ends of segments 0 and 9 (the soma and axon hillock respectively) are attached to the root of the tree. * The proximal ends of segments 0 and 9 (the soma and axon hillock respectively) are attached to the root of the tree.
......
...@@ -14,6 +14,7 @@ building phase to provide information about cells in the model, such as: ...@@ -14,6 +14,7 @@ building phase to provide information about cells in the model, such as:
* the number of gap junction sites; * the number of gap junction sites;
* incoming network connections from other cells terminating on a cell; * incoming network connections from other cells terminating on a cell;
* gap junction connections on a cell. * gap junction connections on a cell.
* probes on a cell.
Why recipes? Why recipes?
-------------- --------------
...@@ -22,7 +23,7 @@ The interface and design of Arbor recipes was motivated by the following aims: ...@@ -22,7 +23,7 @@ The interface and design of Arbor recipes was motivated by the following aims:
* Building a simulation from a recipe description must be possible in a * Building a simulation from a recipe description must be possible in a
distributed system efficiently with minimal communication. distributed system efficiently with minimal communication.
* To minimise the amount of memory used in model building, to make it * Minimising the amount of memory used in model building, making it
possible to build and run simulations in one run. possible to build and run simulations in one run.
Recipe descriptions are cell-oriented, in order that the building phase can Recipe descriptions are cell-oriented, in order that the building phase can
......
...@@ -84,7 +84,7 @@ We recommend using GCC or Clang, for which Arbor has been tested and optimised. ...@@ -84,7 +84,7 @@ We recommend using GCC or Clang, for which Arbor has been tested and optimised.
... ...
.. Note:: .. Note::
Is is commonly assumed that to get the best performance one should use a vendor-specific It is commonly assumed that to get the best performance one should use a vendor-specific
compiler (e.g. the Intel, Cray or IBM compilers). These compilers are often better at compiler (e.g. the Intel, Cray or IBM compilers). These compilers are often better at
auto-vectorizing loops, however for everything else GCC and Clang nearly always generate auto-vectorizing loops, however for everything else GCC and Clang nearly always generate
more efficient code. more efficient code.
...@@ -92,7 +92,7 @@ We recommend using GCC or Clang, for which Arbor has been tested and optimised. ...@@ -92,7 +92,7 @@ We recommend using GCC or Clang, for which Arbor has been tested and optimised.
The main computational loops in Arbor are generated from The main computational loops in Arbor are generated from
`NMODL <https://www.neuron.yale.edu/neuron/static/docs/help/neuron/nmodl/nmodl.html>`_. `NMODL <https://www.neuron.yale.edu/neuron/static/docs/help/neuron/nmodl/nmodl.html>`_.
The generated code is explicitly vectorised, obviating the need for vendor compilers, The generated code is explicitly vectorised, obviating the need for vendor compilers,
and we can take advantage of their benefits of GCC and Clang: and we can take advantage of the benefits of GCC and Clang:
faster compilation times; fewer compiler bugs; and better support for C++ standards. faster compilation times; fewer compiler bugs; and better support for C++ standards.
.. Note:: .. Note::
...@@ -177,7 +177,7 @@ Building and installing Arbor ...@@ -177,7 +177,7 @@ Building and installing Arbor
Once the Arbor code has been checked out, first run CMake to configure the build, then run make. Once the Arbor code has been checked out, first run CMake to configure the build, then run make.
Below is a simple workflow for: **1)** getting the source; **2)** configuring the build; Below is a simple workflow for: **1)** getting the source; **2)** configuring the build;
**3)** building; **4)** running tests; **5)** install. **3)** building; **4)** running tests; **5)** installing.
For more detailed build configuration options, see the `quick start <quickstart_>`_ guide. For more detailed build configuration options, see the `quick start <quickstart_>`_ guide.
...@@ -334,7 +334,8 @@ for the architecture, enabling ``ARB_VECTORIZE`` will lead to a compilation erro ...@@ -334,7 +334,8 @@ for the architecture, enabling ``ARB_VECTORIZE`` will lead to a compilation erro
With this flag set, the library will use architecture-specific vectorization intrinsics With this flag set, the library will use architecture-specific vectorization intrinsics
to implement these kernels. Arbor currently has vectorization support for x86 architectures to implement these kernels. Arbor currently has vectorization support for x86 architectures
with AVX, AVX2 or AVX512 ISA extensions, and for ARM architectures with support for AArch64 NEON intrinsics (first available on ARMv8-A). with AVX, AVX2 or AVX512 ISA extensions; and for AArch64 ARM architectures with NEON and SVE
(first available on ARMv8-A).
.. _install-gpu: .. _install-gpu:
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
Get Arbor Get Arbor
========= =========
Currently we offer three ways to get Arbor. Currently we offer two ways to get Arbor.
1. **Python Package**. To get started quickly with Arbor using its Python API on your personal machine, see the :ref:`Python installation guide <in_python>`. 1. **Python Package**. To get started quickly with Arbor using its Python API on your personal machine, see the :ref:`Python installation guide <in_python>`.
2. **Build and install from source**. To build and install Arbor, on your own machine or HPC environment, see :ref:`in_build_install`. 2. **Build and install from source**. To build and install Arbor, on your own machine or HPC environment, see :ref:`in_build_install`.
If you wish to use the C++ API, you'll need to build Arbor from source. Note that you can build the Python bindings with both of those as well. If you wish to use the C++ API, you'll need to build Arbor from source. Note that you can also build the Python bindings using this method.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
......
...@@ -84,7 +84,7 @@ below demonstrate this for both pip and ``setup.py``. ...@@ -84,7 +84,7 @@ below demonstrate this for both pip and ``setup.py``.
pip3 install --install-option='--mpi' ./arbor pip3 install --install-option='--mpi' ./arbor
python3 ./arbor/setup.py install --mpi python3 ./arbor/setup.py install --mpi
**Compile with** :ref:`vectorization <install-vectorize>` on a system with SkyLake: **Compile with** :ref:`vectorization <install-vectorize>` on a system with a SkyLake
:ref:`architecture <install-architecture>`: :ref:`architecture <install-architecture>`:
.. code-block:: bash .. code-block:: bash
......
...@@ -12,7 +12,7 @@ This guide will walk through a series of single cell models of increasing comple ...@@ -12,7 +12,7 @@ This guide will walk through a series of single cell models of increasing comple
Links are provided to separate documentation that covers relevant topics in more detail. Links are provided to separate documentation that covers relevant topics in more detail.
In an interactive Python interpreter, you can use ``help()`` on any class or function to In an interactive Python interpreter, you can use ``help()`` on any class or function to
obtain some documentation. obtain some documentation. (Try, for example, ``help(arbor.simulation``).
.. _single_soma: .. _single_soma:
...@@ -21,8 +21,8 @@ Single segment cell with HH dynamics ...@@ -21,8 +21,8 @@ Single segment cell with HH dynamics
The most trivial representation of a cell in Arbor is to model the entire cell as a 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 single cylinder. The following example shows the steps required to construct a model of a
cylindrical cell with radius 3 μm, Hodgkin–Huxley dynamics and a current clamp stimulus, cylindrical cell with a length of 6 μm and a radius of 3 μm; Hodgkin–Huxley dynamics
then run the model for 30 ms. and a current clamp stimulus, then run the model for 30 ms.
The first step is to construct the cell. In Arbor, the abstract representation used to The first step is to construct the cell. In Arbor, the abstract representation used to
define a cell with branching "cable" morphology is a ``cable_cell``, which holds a define a cell with branching "cable" morphology is a ``cable_cell``, which holds a
...@@ -53,40 +53,43 @@ create the ``cable_cell`` that represents it are as follows: ...@@ -53,40 +53,43 @@ create the ``cable_cell`` that represents it are as follows:
Let's unpack that. Let's unpack that.
Step **(1)** above shows how the cell is represented using a :class:`arbor.segment_tree` Step **(1)** constructs a :class:`arbor.segment_tree` (see also :ref:`segment tree<morph-segment_tree>`).
to which a single segment is added. Arbor's cell morphologies are constructed from a The segment tree is the representation used to construct the morphology of a cell. A segment is
:ref:`segment tree<morph-segment_tree>` which is a list of segments, which are tapered a tapered cone with a tag; the tag can be used to classify the type of the segment (for example
cones with a *tag*. :meth:`arbor.segment_tree.append` takes 4 arguments, starting with soma, dendrite etc). To create a segment tree representing our single-cylinder cell, we need to add
the parent segment. The first segment added has no parent, which is made clear by one segment to our ``tree`` object. We use the :meth:`arbor.segment_tree.append` method, which takes
using :class:`arbor.mnpos`. Then two :class:`arbor.mpoint` s are supplied, the proximal 4 arguments: the parent segment which does not exist for the first segment, so we use :class:`arbor.mnpos`;
and distal endpoints of the segment. Finally, an integer value is supplied to tag the the proximal :class:`arbor.mpoint` (location and radius) of the segment; the distal :class:`arbor.mpoint`
segment for future reference. of the segment; and the tag.
In step **(2)** a dictionary of labels is created using :class:`arbor.label_dict<arbor.label_dict>`. Step **(2)** creates a dictionary of labels (:class:`arbor.label_dict<arbor.label_dict>`). Labels give
Cell builders need to refer to *regions* and *locations* on a cell morphology. Arbor uses a domain names to :ref:`regions<labels-region>` and :ref:`location<labels-locset>` described using a DSL
specific language (DSL) to describe regions and locations, which are given labels. We add two labels: based on s-expressions. Labels from the dictionary can then be used to facilitate adding synapses,
dynamics, stimulii and probes to the cell. We add two labels:
* ``soma`` defines a *region* with ``(tag 1)``. Note that this corresponds to the * ``soma`` defines a *region* with ``(tag 1)``. Note that this corresponds to the
``tag`` parameter that was used to define the single segment in step (1). ``tag`` parameter that was used to define the single segment in step (1).
* ``center`` defines a *location* at ``(location 0 0.5)``, which is the mid point ``0.5`` * ``center`` defines a *location* at ``(location 0 0.5)``, which is the mid point ``0.5``
of branch ``0``, which corresponds to the center of the soma on the morphology defined in step (1). of branch ``0``, which corresponds to the center of the soma on the morphology defined in step (1).
In step **(3)** a :class:`arbor.cable_cell` is constructed by combining the segment tree Step **(3)** constructs the :class:`arbor.cable_cell` from the segment tree and dictionary of labeled
with the named regions and locations. regions and locations. The resulting cell's default properties can be modified, and we can use
:meth:`arbor.cable_cell.paint` and :meth:`arbor.cable_cell.place` to further customise it in the
* "Cell-wide" properties are set through :meth:`arbor.cable_cell.set_properties`. Here, following way:
the initial membrane potential is set to -40 mV everywhere on the cell.
* Properties can also be set to a region of the cell, which Arbor calls 'painting'. This * :meth:`arbor.cable_cell.set_properties` is used to set some default properties on the entire cell.
is meant to convey placement is not precise: we wouldn't want to manually place ion In the above example we set the initial membrane potential to -40 mV.
channels all over the surface of the cell. :meth:`arbor.cable_cell.paint` lets us * :meth:`arbor.cable_cell.paint` is used to set properties or add dynamics to a region of the cell.
instruct Arbor to use HH dynamics on the region we've labelled soma and sort the details We call this method 'painting' to convey that we are working on sections of a cell, as opposed to
out for us. precise locations: for example, we might want to ``paint`` an ion channel on all dendrites, and then
* Other properties should be added to the cell on a precise :class:`arbor.location`. This is ``place`` a synapse at the tip of the axon. In the above example we :meth:`arbor.cable_cell.paint`
done using the :meth:`arbor.cable_cell.place<arbor.cable_cell.place>` method. HH dynamics on the region we previously named 'soma' in our label dictionary.
We place a current stimulus :class:`arbor.iclamp<arbor.iclamp>` with a duration of 2 ms * :meth:`arbor.cable_cell.place<arbor.cable_cell.place>` is used to add objects on a precise
and a current of 0.8 nA, starting at 10 ms on the location we've labelled 'center'. We also :class:`arbor.location` on a cell. Examples of objects that are ``placed`` are synapses,
place a :class:`arbor.spike_detector<arbor.spike_detector>` with a threshold of -10 mV on the spike detectors, current stimulii, and probes. In the above example we place a current stimulus
same location. :class:`arbor.iclamp<arbor.iclamp>` with a duration of 2 ms and a current of 0.8 nA, starting at 10 ms
on the location we previously labelled 'center'. We also place a :class:`arbor.spike_detector<arbor.spike_detector>`
with a threshold of -10 mV on the same location.
Single cell model Single cell model
---------------------------------------------------- ----------------------------------------------------
...@@ -112,12 +115,12 @@ recording potentials and running the simulation more easily. ...@@ -112,12 +115,12 @@ recording potentials and running the simulation more easily.
Step **(4)** instantiates the :class:`arbor.single_cell_model<arbor.single_cell_model>` Step **(4)** instantiates the :class:`arbor.single_cell_model<arbor.single_cell_model>`
with our single-compartment cell. with our single-compartment cell.
In step **(5)** a :meth:`arbor.single_cell_model.probe()<arbor.single_cell_model.probe>` Step **(5)** adds a :meth:`arbor.single_cell_model.probe()<arbor.single_cell_model.probe>`
is used to record variables from the model. Three pieces of information are used to record variables from the model. Three pieces of information are
provided: the type of quantity we want probed (voltage), the location where we want to provided: the type of quantity we want probed (voltage), the location where we want to
probe ('"center"'), and the frequency at which we want to sample (10kHz). probe ('"center"'), and the frequency at which we want to sample (10kHz).
Finally, step **(6)** starts the actual simulation for a duration of 30 ms. Step **(6)** runs the actual simulation for a duration of 30 ms.
Results Results
---------------------------------------------------- ----------------------------------------------------
...@@ -141,13 +144,13 @@ results! Let's take a look at what the spike detector and a voltage probes from ...@@ -141,13 +144,13 @@ results! Let's take a look at what the spike detector and a voltage probes from
df = pandas.DataFrame({'t/ms': m.traces[0].time, 'U/mV': m.traces[0].value}) df = pandas.DataFrame({'t/ms': m.traces[0].time, 'U/mV': m.traces[0].value})
seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV").savefig('single_cell_model_result.svg') seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV").savefig('single_cell_model_result.svg')
In step **(7)** we access :meth:`arbor.single_cell_model.spikes<arbor.single_cell_model.spikes>` Step **(7)** accesses :meth:`arbor.single_cell_model.spikes<arbor.single_cell_model.spikes>`
to access the spike time. A single spike at a little over 10 ms should be printed, to print the spike times. A single spike should be generated at around the same time the stimulus
which matches the stimulus we have provided in step (3). we provided in step (3) gets activated (10ms).
The other measurement we have is that of the potential, which we plot in step **(8)**. Step **(8)** plots the measured potentials during the runtime of the simulation. The sampled quantities
Arbor stores sampled quantities under :meth:`arbor.single_cell_model.traces<arbor.single_cell_model.traces>`. can be accessed through :meth:`arbor.single_cell_model.traces<arbor.single_cell_model.traces>`.
You should be seeing something like this: We should be seeing something like this:
.. figure:: single_cell_model_result.svg .. figure:: single_cell_model_result.svg
:width: 400 :width: 400
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment