This page was generated from docs/example_spatial_customsetup.ipynb. Interactive online version: Colab badge Deepnote badge. Binder badge Download notebook.

Arbitrary parameters on spatially detailed cellsΒΆ

It is often the case that biophysical properties vary spatially across the spatial extent of a neuron. NeuroML has a provision for modelling the variability of the base conductance( \(\bar g\) ) of ion channel distributions, as a user-specified function of distance from the soma (measured along neurites). Still, modellers may desire more types of variability, and on different parameters as well. This article shows how Eden’s <π™²πšžπšœπšπš˜πš–πš‚πšŽπšπšžπš™> extension can be used to model any kind of variability, on any attribute of a mechanism present on spatially-detailed cells.

As seen on the main article about this extension, the model attributes of neurons can be customised in any conceivable way through set cell statements. For point neurons, the location list is always all. For spatially-detailed cell models, the list can be much more specific - and we’ll see right now how to use it.

Note

This feature is still in development.

First, let’s get a sample cell to try variability on:


nmldb_cell_name = 'NMLCL000001'; zip_file_name = f'{nmldb_cell_name}.zip'
# Download the zip file with the model
import urllib.request
# Because NMLDB is hoving some issues with HTTPS, don't demand HTTPS verification
import ssl; ssl._create_default_https_context = ssl._create_unverified_context
urllib.request.urlretrieve(f'http://neuroml-db.org/GetModelZip?modelID={nmldb_cell_name}&version=NeuroML', zip_file_name)
# and unpack it
from zipfile import ZipFile
with ZipFile(zip_file_name, 'r') as zipp: zipp.extractall(nmldb_cell_name+'/')
# Find the cell's .nml file
import os
cell_filenames = [ nmldb_cell_name+'/'+name for name in os.listdir(nmldb_cell_name) if name.endswith('.cell.nml') ]
assert len(cell_filenames) == 1, "Didn't find a lonely cell nml file!"; cell_filename = cell_filenames[0]

Sampling the cell’s morphologyΒΆ

Let’s get some facts about the neuron on which we’ll define variability:


import eden_simulator
cells_info = eden_simulator.experimental.explain_cell(cell_filenames[0])
# one cell in the model file, get it
cell_info = list(cells_info.values())[0]
nComps = len(cell_info['comp_midpoint'])
comp_midpoint = cell_info['comp_midpoint']
# list(cell_info.keys())
As mentioned in a previous chapter, spatially-modelled cells eventually have to be split into discrete compartments, within which every biophysical quantity is considered to have the same value. Hence, spatially-varying quantities on a modelled cell may be assigned values that differ for only individual compartments.
Note: This is the way that any distribution, deterministic or stochastic, of parameters can be faithfully applied on neurons; the particular values are entirely controlled by the modeller.

Given the information from explain_cell, it is straightforward to write the set cell statement: Set the location list field as a comma-separated list of seg.fractionAlong cell locators, and add as desired for variability, multi (to set one cell) or multi_location, multi_cell or multi_multi (for multiple cells in a go), and the values lines that follow.

Note

When overriding parameters over spans of the neuron, make sure to not miss any spots (especially for <πš…πšŠπš›πš’πšŠπš‹πš•πšŽπšπšŽπššπšžπš’πš›πšŽπš–πšŽπš—πš>s!). A reliable way is using explain_cell as shown here to get cell-infothe list of compartments and their segment+fractionAlong midpoints, and the list of compartments per segment group of interest as cell_info['segment_groups'][groupName].

The following examples will show how to implement different frequently used distributions for biophysical parameters. In all cases, variability within a cell boils down to a vector of values, one for each compartment. Here’s a helper routine to write a CustomSetup file, that sets a cell with different values all over:


def set_cell_vals(cell_info, popname, instances, attribute, vals, units):
    locs = ','.join([str(loc) for loc in eden_simulator.experimental.GetLemsLocatorsForCell(cell_info)])
    lines = f'set cell {popname} {instances} {locs} {attribute} multi {units}\n'\
        'values '+'\t'.join(map(str,vals))+'\n'
    return lines

In the following, we’ll use these routines to visualise the different cases of variability over the neuron. We’ll use PyVista for 3-D graphics, and EDEN’s helper pyvista.plot_neuron to colour a mesh per compartment.


import pyvista as pv

def plot_neuron(cell_info, vals, valsname, cmap=None, bgcol=None):
    import pyvista as pv
    from eden_simulator.display.spatial.pyvista  import get_neuron_mesh
    mesh = get_neuron_mesh(cell_info)
    mesh[valsname] = vals

    pl = pv.Plotter();
    pl.add_mesh(mesh, clim = None,cmap=cmap)
    pl.background_color = "#fefefe"
    if bgcol: pl.background_color = bgcol

    pl.camera.position = (000, 150, +1050)
    pl.camera.focal_point = (0.2, 0.3, -5.3)
    pl.camera.up = (0.0, 1., 1.)
    pl.camera.zoom(.8)
    # pl.camera.position = (1000, 150, -50)
    # pl.camera.focal_point = (0.2, 150.3, -50.3)
    # pl.camera.up = (0.0, 1.0, 0.6)
    # pl.camera.zoom(.8)
    return pl, mesh
def show_plot(pl):
    # displays won't show except with this trick... https://github.com/pyvista/pyvista/pull/5168
    scene = pl.show(jupyter_backend="html", return_viewer=True)
    # but scene.value is not present on older versions ...
    # s = scene.value if hasattr(scene,'value') else ''
    # as seen on https://github.com/mikedh/trimesh/blob/4.5.1/trimesh/viewer/notebook.py#L72
    height=500
    s = pl.export_html(None).read()
    import html; s = html.escape(s)
    s = f'<div><iframe srcdoc="{s}" \
    width="100%" height="{height}px" \
    style="border:none;"></iframe></div>'
    import types; scene_wrapper = types.SimpleNamespace( # note: __repr__ needs an actual class to override
        scene=scene, _repr_html_=lambda : s, _repr_latex_=lambda : '')
    from IPython import display; display.display(scene_wrapper)
    return scene_wrapper
# pl,_ = plot_neuron(cell_info, vals, 'Distance from plane (ΞΌm)','reds','azure')
# Plot_Vert = pl; Show_Vert = show_plot(pl);

Variability as distance from a planeΒΆ

One may wish to model a quantity with a lateral distribution over the neuron; that is to say, a quantity whose value is increasing (or decreasing) towards a certain direction. Let \(\hat{\mathbf{n}}\) be the unit vector pointing to that direction.

Then one can assume a signed directional distance metric from a reference point \(\mathbf{p}_0\), for every point in space \(\mathbf{x}\), as follows:

\[d_{\hat{\mathbf{n}},\mathbf{p}_0}(\mathbf{x}) = (\mathbf{x} - \mathbf{p}_0) \boldsymbol{\cdot} \hat{\mathbf{n}}\]

which can be converted to β€œquantity” units as desired. Note that \(d\) has the same value over every plane perpendicular to \(\hat{\mathbf{n}}\).

Let’s sample \(d\) for the midpoint of each compartment and see the result.


import numpy as np
from numpy.linalg import norm
def unit_vec(v): return v / norm(v, axis=-1)
def dist_by_plane(point_0, dist_direction, sample_points):
    n = unit_vec(dist_direction)
    return (sample_points - point_0).dot(n)
# Try n = [1,0,0] also
vals = dist_by_plane([0,0,0], [0,1,0], comp_midpoint)
pl,_ = plot_neuron(cell_info, vals, 'Distance from plane (ΞΌm)','reds','azure')
Plot_Vert = pl; Show_Vert = show_plot(pl);

Variability as distance from an axleΒΆ

Another sort of spatial distribution is as a function of transverse distance from an axle (designated by a reference point \(\mathbf{p}_0\) on the axle and a direction vector \(\hat{\mathbf{n}}\)). The transverse distance vector is thus:

\[{\mathbf{d}}_{\perp,\hat{\mathbf{n}},\mathbf{p}_0}(\mathbf{x}) = \mathbf{r} - (\mathbf{r} \boldsymbol{\cdot} \hat{\mathbf{n}}) \hat{\mathbf{n}} , ~ \mathbf{r} = \mathbf{x} - \mathbf{p}_0\]

in which case, the distance is the same over each tube centered at the axle.

Let’s distribute the reversal potential \(E_{_{Na}}\) of the ion channel distribution Na_all as a function of that, then:


def dist_by_axle(p0, axle_direction, x):
    n = unit_vec(axle_direction)
    d = (x - p0)
    aligned =  d.dot(n)[:,None]*n[None,:]
    return norm(d - aligned, axis=-1)

vals = dist_by_axle([0,0,0], [0,1,0], comp_midpoint) * (10/100) + 40
pl,_ = plot_neuron(cell_info, vals, 'Reversal Potential (mV)','blues','gray')
with open('cell_axle.txt', 'w') as f:
    f.write(set_cell_vals(cell_info, 'pop', 'all', 'biophysicalProperties/membraneProperties/Na_all/erev', vals, 'mV'))
Plot_Axle = pl; Show_Axle = show_plot(pl);

Variability as by distance from a point in spaceΒΆ

A simpler type of distribution is radial distance from a reference point \(\mathbf{p}_0\), that’s simply \({\mathbf{d}} = \mathbf{x} - \mathbf{p}_0\). In this case, the distance is the same over each sphere centered at \(\mathbf{p}_0\).

Let’s distribute the base conductivity \(\bar{g}_{_{Na}}\) of Na_all as a function of euclidean distance from a reference point:


def dist_by_point(p0, x):
    return norm(x - p0, axis=-1)

vals = dist_by_point([0,100,0], comp_midpoint) * (3/10) + 10
pl,_ = plot_neuron(cell_info, vals, 'Conductance Density (S/cmΒ²)','Greens','gray')
with open('cell_dist.txt', 'w') as f:
    f.write(set_cell_vals(cell_info, 'pop', 'all', 'biophysicalProperties/membraneProperties/Na_all/condDensity', vals, 'S_per_cm2'))
Plot_Dist = pl; Show_Dist = show_plot(pl);

Randomised parametersΒΆ

Finally, a quantity may simply exhibit a slight variation which could follow any random distribution. As mentioned in the previous article, it’s often better to assume some variation across neuron and mechanism instances, though this will also require more memory to store the variability.

For example, the steady-state concentration of a <πšπšŽπšŒπšŠπš’πš’πš—πšπ™Ώπš˜πš˜πš•π™²πš˜πš—πšŒπšŽπš—πšπš›πšŠπšπš’πš˜πš—π™Όπš˜πšπšŽπš•> used for a <species> distribution may vary following an uniform distribution:


rng = np.random.default_rng(seed=43264326)
vals = rng.uniform(0,100,nComps)

pl,_ = plot_neuron(cell_info, vals, 'Steady-state [Ca²⁺] (nM)','RdPu','azure')
with open('cell_rand.txt', 'w') as f:
    f.write(set_cell_vals(cell_info, 'pop', 'all', 'biophysicalProperties/intracellularProperties/ca_conc_all/restingConc', vals, 'nM'))
Plot_Rand = pl; Show_Rand = show_plot(pl);

Beside the above suggestions, a distribution may depend on multiple factors, always following the intent of the modeller (or undisputed biophysical findings). By evaluating the values of quantities for each compartment, distributions can be implemented in the simulation with full precision.

Aside for NEURON usersΒΆ

Readers familiar with the NEURON simulator may have assigned spatial variability for parameters over the cell, with πš‚πšžπš‹πšœπšŽπšπ™³πš˜πš–πšŠπš’πš—π™ΈπšπšŽπš›πšŠπšπš˜πš› or in the general case through HOC code (or the equivalent Python):

forsec <section_name or all>{
    for(x){
        // x is fraction along section for the compartment, and area(x), ri(x), x,y,z3d(x) and others are properties of the compartment.
        // Users can then set RANGE properties however they like based on the above properties.
    }
}

As shown, EDEN provides a similar mode of setting variability, albeit in a β€˜flatter’ way. explain_cell provides a list of per compartments and associated quantities over the whole cell, thus one loop over the explicitly listed compartments equates to forsec ... for(x) ... in HOC. Variability over a section is then done by iterating only over the appropriate subset of compartments, provided by cell_info['segment_groups'][groupName] for the section’s named <segmentGroup>.