text: A simple single cell recipe¶
  This example builds the same single cell model as A simple single cell model,
  except using a
  arbor.recipe and
  arbor.simulation instead of a
  arbor.single_cell_model.
  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.
  Note
  Concepts covered in this example:
  Building a
  arbor.recipe.
  Using the recipe, default context and domain decomposition to create an
  arbor.simulation
  Running the simulation and visualizing the results.
  The cell¶
  Let’s copy the cell description from the original example, where construction of the cell is explained in detail.
  import arbor #  cell and set properties decor = ( arbor.decor() .set_property(Vm=-40) .paint('"soma"', arbor.density("hh")) .place('"midpoint"', arbor.iclamp(10, 2, 0.8), "iclamp") .place('"midpoint"', arbor.threshold_detector(-10), "detector")
  The recipe¶
  In the original example, the
  arbor.single_cell_model creates
  a
  arbor.recipe under the hood, and abstracts away a few details that you may want control over
  in more complex simulations. Let’s go into those abstractions and create an analogous
  arbor.recipe
  manually.
  Creating a recipe starts with creating a class that inherits from
  arbor.recipe. There are a number of
  methods that must be overridden, and a number than can optionally be overridden, as explained in the
  arbor.recipe documentation. Beyond this, it is up to you, the user, to structure your code as you
  find convenient.
  One of the methods that must be overridden is
  arbor.recipe.num_cells(). It returns 0 by default and
  models without cells are quite boring!
  # (4) Define a recipe for a single cell and set of probes upon it. # This constitutes the corresponding generic recipe version of # `single_cell_model.py`. class single_recipe(arbor.recipe): # (4.1) The base class constructor must be called first, to ensure that # all memory in the wrapped C++ class is initialized correctly. def __init__(self): arbor.recipe.__init__(self) self.the_props = arbor.neuron_cable_properties() # (4.2) Override the num_cells method def num_cells(self): return 1 # (4.3) Override the cell_kind method def cell_kind(self, gid): return arbor.cell_kind.cable # (4.4) Override the cell_description method def cell_description(self, gid): return cell # (4.5) Override the probes method with a voltage probe located on "midpoint" def probes(self, gid): return [arbor.cable_probe_membrane_voltage('"midpoint"')] # (4.6) Override the global_properties method def global_properties(self, kind): return self.the_props
  Step (4) describes the recipe that will reflect our single cell model.
  Step (4.1) defines the class constructor. It can take any shape you need, but it
  is important to call base class’ constructor. If the overridden methods of the class
  need to return an object, it may be a good idea to have the returned object be a
  member of the class. With this constructor, we could easily change the cell and probes
  of the model, should we want to do so. Here we initialize the cell properties to match
  Neuron’s defaults using Arbor’s built-in
  arbor.neuron_cable_properties() and
  extend with Arbor’s own
  arbor.default_catalogue().
  Step (4.2) defines that this model has one cell.
  Step (4.3) returns
  arbor.cell_kind.cable, the
  arbor.cell_kind
  associated with the cable cell defined above. If you mix multiple cell kinds and
  descriptions in one recipe, make sure a particular
  gid returns matching cell kinds
  and descriptions.
  Step (4.4) returns the cell description defined earlier. If we
  were modelling multiple cells of different kinds, we would need to make sure that the
  cell returned by
  arbor.recipe.cell_description() has the same cell kind as
  returned by
  arbor.recipe.cell_kind() for every
  gid.
  Step (4.5) returns the same probe as in the
  single_cell_model: a single voltage probe located at “midpoint”.
  Step (4.6) returns the properties that will be applied to all cells of that kind in the model.
  More methods can be overridden if your model requires that, see
  arbor.recipe for options.
  Now we instantiate the recipe
  # (5) Instantiate recipe. recipe = single_recipe()
  The simulation¶
  arbor.single_cell_model does not only take care of the recipe, it also takes
  care of defining how the simulation will be run. When you create and use your own
  recipe, you can to do this manually, in the form of defining a execution context
  and a domain decomposition. Fortunately, the default constructors of
  arbor.context and
  arbor.partition_load_balance are sufficient for
  this model, and is what
  arbor.single_cell_model does under the hood!
  In addition, if all you need is the default context and domain decomposition, they can be
  left out and the
  arbor.simulation object can be contructed from just the recipe.
  The details of manual hardware configuration will be left for another tutorial.
  # (6) Create simulation. When their defaults are sufficient, context and domain decomposition don't # have to be manually specified and the simulation can be created with just the recipe as argument. sim = arbor.simulation(recipe) # (7) Create and run simulation and set up 10 kHz (every 0.1 ms) sampling on the probe. # The probe is located on cell 0, and is the 0th probe on that cell, thus has probeset_id (0, 0). sim.record(arbor.spike_recording.all) handle = sim.sample((0, 0), arbor.regular_schedule(0.1)) sim.run(tfinal=30)
  Step (6) instantiates the simulation.
  Step (7) sets up the probe added in step 5. In the
  arbor.single_cell_model version of this example, the probe frequency and
  simulation duration are the same. Note that the frequency is set with a
  arbor.regular_schedule,
  which takes a time and not a frequency. Also note that spike recording must be
  switched on. For extraction of the probe traces later on, we store a handle.
  The results¶
  Apart from creating
  arbor.recipe ourselves, we have changed nothing
  about this simulation compared to the original tutorial.
  If we create the same analysis of the results we therefore expect the same results.
  # (8) Collect results. spikes = sim.spikes() data, meta = sim.samples(handle)[0] if len(spikes) > 0: print("{} spikes:".format(len(spikes))) for t in spikes["time"]: print("{:3.3f}".format(t)) else: print("no spikes") print("Plotting results ...") df = pandas.DataFrame({"t/ms": data[:, 0], "U/mV": data[:, 1]}) seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV", ci=None).savefig( "single_cell_recipe_result.svg" ) df.to_csv("single_cell_recipe_result.csv", float_format="%g")
  Step (8) plots the measured potentials during the runtime of the simulation.
  Retrieving the sampled quantities is a little different, these have to be accessed
  through the simulation object:
  arbor.simulation.spikes() and
  arbor.simulation.samples().
  We should be seeing something like this:
  Plot of the potential over time for the voltage probe added in step (5).¶
  You can find the source code for this example in full at
  python/examples/single_cell_recipe.py.