"""Network model functions."""
# Authors: Nick Tolley <nicholas_tolley@brown.edu>
from pathlib import Path
from copy import deepcopy
import warnings
import numpy as np
import hnn_core
from hnn_core import read_params
from .network import Network, _create_cell_coords
from .params import _short_name
from .cells_default import (
basket,
pyramidal,
pyramidal_ca,
pyramidal_humanL5ET,
pyramidal_humanL23,
human_gen_interneuron,
)
from .externals.mne import _validate_type
from .dipole import _correct_baseline_dueckerET, _correct_baseline_neymotin2020
# Default cell metadata for the standard Jones 2009 network cell types.
# Defined here at module level so that other code (e.g. JSON
# serialisation / deserialisation) can import it without instantiating
# a full Network object.
default_cell_metadata = {
"L2_basket": {
"morpho_type": "basket",
"electro_type": "inhibitory",
"layer": "2",
"zdist_origin": 0.8, # distance to origin in percent of layer_separation
"measure_dipole": False,
"reference": "https://doi.org/10.7554/eLife.51214",
"color": "m",
"marker": "x", # shape from prev viz.py line:926
},
"L2_pyramidal": {
"morpho_type": "pyramidal",
"electro_type": "excitatory",
"layer": "2",
"zdist_origin": 1,
"measure_dipole": True,
"reference": "https://doi.org/10.7554/eLife.51214",
"color": "c",
"marker": "^",
},
"L5_basket": {
"morpho_type": "basket",
"electro_type": "inhibitory",
"layer": "5",
"zdist_origin": 0.2,
"measure_dipole": False,
"reference": "https://doi.org/10.7554/eLife.51214",
"color": "r",
"marker": "x",
},
"L5_pyramidal": {
"morpho_type": "pyramidal",
"electro_type": "excitatory",
"layer": "5",
"zdist_origin": 0,
"measure_dipole": True,
"reference": "https://doi.org/10.7554/eLife.51214",
"color": "b",
"marker": "^",
},
}
default_drive_colors = {
"proximal": "r",
"distal": "g",
"default": "#8B4513",
}
# Map of how `Network._model_variant` cases apply to different
# `Dipole._correct_baseline` functions. This is applied at the time of `Dipole`
# creation, but contains information about differences between network models, so it is
# located here.
MODEL_VARIANT_MAPPING = {
None: _correct_baseline_neymotin2020,
"neymotin_2020_model": _correct_baseline_neymotin2020,
"jones_2009_model": _correct_baseline_neymotin2020,
"law_2021_model": _correct_baseline_neymotin2020,
"calcium_model": _correct_baseline_neymotin2020,
"duecker_ET_model": _correct_baseline_dueckerET,
}
def _validate_params_for_model(
net,
params,
model_variant,
alt_variants=[],
require_variant=False,
excluded_cells=[],
):
"""Check that a param file matches the network model it is used for.
Parameters
----------
net : Instance of Network object
The network the parameters are used for.
params : dict
The parameters the network was built from.
model_variant : str
Name of the network model, e.g. 'duecker_ET_model'. The
'model_variant' entry of `params` must be this name (or of one of
`alt_variants`).
alt_variants : list of str, default=[]
Further model names that are accepted in the 'model_variant' entry of `params`,
e.g. the deprecated name of a model. If `params` defines a local 'model_variant'
that is in 'alt_variants', the returned value will be the value of
'model_variant' that is passed to this function instead.
require_variant : bool, default=False
If True, raise if `params` does not define 'model_variant'. Used for
models that share no parameters with the default model, and would
otherwise silently fall back to default values.
excluded_cells : list of str, default=[]
Short names of cells that are *not* part of this network, e.g.
('L2Basket', 'L5Basket') for a model in which basket cells are
replaced. Parameters for these cells are rejected.
Returns
-------
model_variant : str
The official model variant name.
"""
check_var = params.get("model_variant", None)
if check_var is None:
if require_variant:
raise ValueError(
f"'model_variant' is required for simulations with "
f"{model_variant}. If you are sure that you are using the "
f"correct parameters, add 'model_variant': '{model_variant}', "
"to the first line of the param .json file."
)
elif check_var not in [model_variant] + alt_variants:
raise ValueError(
f"Parameters for {check_var} used for {model_variant}."
" Ensure that your param .json file matches the network "
f"and that your model variant is one of {[model_variant] + alt_variants}. "
)
# check that the params define the cell types of this network
missing_cells = [
cell_name
for cell_name in net.cell_types
if not any(_short_name(cell_name) in key for key in params)
]
if missing_cells:
raise ValueError(
f"No parameters found for {', '.join(missing_cells)}."
" Ensure that your param .json file matches the network. "
" Reach out to us if this doesn't solve your problem. "
" https://github.com/jonescompneurolab/hnn-core/discussions"
)
# check that the params don't define cell types this network replaced
unexpected_cells = [
cell_name
for cell_name in excluded_cells
if any(cell_name in key for key in params)
]
if unexpected_cells:
raise ValueError(
f"Parameters found for {', '.join(unexpected_cells)}, which"
f" are not part of {model_variant}. Ensure that your"
" param .json file matches the network."
" Reach out to us if this doesn't solve your problem. "
" https://github.com/jonescompneurolab/hnn-core/discussions"
)
return model_variant
[docs]
def neymotin_2020_model(
params=None,
add_drives_from_params=False,
legacy_mode=False,
mesh_shape=(10, 10),
):
"""Instantiate the network model described in Neymotin et al. 2020
Parameters
----------
params : str | Path | dict | None, default=None
Custom Network parameters to use, if any. If string or Path, it is assumed to be
a path to a legacy "flat" JSON file containing the parameters in the style of
`hnn_core/param/default.json` (NOT a modern "hierarchical" JSON file like
`hnn_core/param/neymotin2020_base.json`). If dict, it is assumed to be a
dictionary of parameters in the "flat" JSON style. If None (the default), the
default parameters are used from `hnn_core/param/default.json`. Note that if you
want to use any drives defined in the params (either your provided custom params
or the default), then you must also set `add_drives_from_params` to True. If you
pass any custom params, then no default params will be used, including for
drives.
add_drives_from_params : bool, default=False
If True, add drives as defined in the params-dict. NB this is mainly
for backward-compatibility with HNN GUI, and will be deprecated in a
future release. Default: False
legacy_mode : bool, default=False
Set to False by default. Enables matching HNN GUI output when drives
are added suitably. Will be deprecated in a future release.
mesh_shape : tuple of int (default: (10, 10))
Defines the (n_x, n_y) shape of the grid of pyramidal cells.
Returns
-------
net : Instance of Network object
Network object used to store
Notes
-----
The network is composed of a square grid of pyramidal cells, arranged in
two layers (L5 and L2). The default in-plane separation of the grid points
is 1.0 um, and the layer separation 1307.4 um. These can be adjusted after
the net is created using the set_cell_positions-method. An all-to-all
connectivity pattern is applied between cells. Inhibitory basket cells are
present at a 1:3-ratio.
This network was first described in Jones et al. 2009 [1]_ , and this code provides
the implementation used in Neymotin et al. 2020 [2]_ .
References
----------
.. [1] Jones, Stephanie R., et al. "Quantitative Analysis and
Biophysically Realistic Neural Modeling of the MEG Mu Rhythm:
Rhythmogenesis and Modulation of Sensory-Evoked Responses."
Journal of Neurophysiology 102, 3554–3572 (2009).
https://doi.org/10.1152/jn.00535.2009
.. [2] Neymotin, Samuel A, et al. 2020. "Human Neocortical Neurosolver (HNN), a New
Software Tool for Interpreting the Cellular and Network Origin of Human
MEG/EEG Data." eLife 9 (January):e51214. https://doi.org/10.7554/eLife.51214
"""
hnn_core_root = Path(hnn_core.__file__).parent
_validate_type(
params, (str, Path, dict, type(None)), "params", "str | Path | dict | None"
)
if params is None:
params_fname = hnn_core_root / "param" / "default.json"
params = read_params(params_fname)
elif isinstance(params, str) or isinstance(params, Path):
params = read_params(params)
# If the user has provided params as a dict, then we don't need to load it from
# file, and its correctness will be checked later inside the `Network` initializer.
# Define cell types for Jones 2009 model
# data is here in metaData format
cell_types = {
"L2_basket": {
"cell_object": basket(cell_name="L2_basket"),
"cell_metadata": deepcopy(default_cell_metadata["L2_basket"]),
},
"L2_pyramidal": {
"cell_object": pyramidal(cell_name="L2_pyramidal"),
"cell_metadata": deepcopy(default_cell_metadata["L2_pyramidal"]),
},
"L5_basket": {
"cell_object": basket(cell_name="L5_basket"),
"cell_metadata": deepcopy(default_cell_metadata["L5_basket"]),
},
"L5_pyramidal": {
"cell_object": pyramidal(cell_name="L5_pyramidal"),
"cell_metadata": deepcopy(default_cell_metadata["L5_pyramidal"]),
},
}
# Create layer positions
layer_dict = _create_cell_coords(
n_pyr_x=mesh_shape[0],
n_pyr_y=mesh_shape[1],
z_coord=1307.4, # Default layer separation
inplane_distance=1.0, # Default in-plane distance
)
# Map cell types to layer positions
pos_dict = {
"L5_pyramidal": layer_dict["L5_bottom"],
"L2_pyramidal": layer_dict["L2_bottom"],
"L5_basket": layer_dict["L5_mid"],
"L2_basket": layer_dict["L2_mid"],
"origin": layer_dict["origin"],
}
# Create network with cell types and positions
net = Network(
params,
add_drives_from_params=add_drives_from_params,
legacy_mode=legacy_mode,
mesh_shape=mesh_shape,
pos_dict=pos_dict,
cell_types=cell_types,
)
delay = net.delay
# Ensure model_variant and params' cell types match current model
net._model_variant = _validate_params_for_model(net, params, "neymotin_2020_model")
# source of synapse is always at soma
# layer2 Pyr -> layer2 Pyr
# layer5 Pyr -> layer5 Pyr
lamtha = 3.0
loc = "proximal"
for target_cell in ["L2_pyramidal", "L5_pyramidal"]:
for receptor in ["nmda", "ampa"]:
key = (
f"gbar_{_short_name(target_cell)}_{_short_name(target_cell)}_{receptor}"
)
weight = net._params[key]
net.add_connection(
target_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# layer2 Basket -> layer2 Pyr
src_cell = "L2_basket"
target_cell = "L2_pyramidal"
lamtha = 50.0
loc = "soma"
for receptor in ["gabaa", "gabab"]:
key = f"gbar_L2Basket_L2Pyr_{receptor}"
weight = net._params[key]
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# layer5 Basket -> layer5 Pyr
src_cell = "L5_basket"
target_cell = "L5_pyramidal"
lamtha = 70.0
loc = "soma"
for receptor in ["gabaa", "gabab"]:
key = f"gbar_L5Basket_{_short_name(target_cell)}_{receptor}"
weight = net._params[key]
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# layer2 Pyr -> layer5 Pyr
src_cell = "L2_pyramidal"
lamtha = 3.0
receptor = "ampa"
for loc in ["proximal", "distal"]:
key = f"gbar_L2Pyr_{_short_name(target_cell)}"
weight = net._params[key]
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# layer2 Basket -> layer5 Pyr
src_cell = "L2_basket"
lamtha = 50.0
key = f"gbar_L2Basket_{_short_name(target_cell)}"
weight = net._params[key]
loc = "distal"
receptor = "gabaa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# xx -> layer2 Basket
src_cell = "L2_pyramidal"
target_cell = "L2_basket"
lamtha = 3.0
key = f"gbar_L2Pyr_{_short_name(target_cell)}"
weight = net._params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
src_cell = "L2_basket"
lamtha = 20.0
key = f"gbar_L2Basket_{_short_name(target_cell)}"
weight = net._params[key]
loc = "soma"
receptor = "gabaa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# xx -> layer5 Basket
src_cell = "L5_basket"
target_cell = "L5_basket"
lamtha = 20.0
loc = "soma"
receptor = "gabaa"
key = f"gbar_L5Basket_{_short_name(target_cell)}"
weight = net._params[key]
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
src_cell = "L5_pyramidal"
lamtha = 3.0
key = f"gbar_L5Pyr_{_short_name(target_cell)}"
weight = net._params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
src_cell = "L2_pyramidal"
lamtha = 3.0
key = f"gbar_L2Pyr_{_short_name(target_cell)}"
weight = net._params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
return net
def jones_2009_model(
params=None,
add_drives_from_params=False,
legacy_mode=False,
mesh_shape=(10, 10),
):
"""Instantiate the network model described in Jones et al., 2009.
DEPRECATED: This function is now deprecated in favor of using `neymotin_2020_model`
which is a more accurate name for the model being used. This function is still kept
in HNN-Core for backwards-compatibility reasons, and is not currently planned to be
removed.
Parameters
----------
params : str | Path | dict | None, default=None
Custom Network parameters to use, if any. If string or Path, it is assumed to be
a path to a legacy "flat" JSON file containing the parameters in the style of
`hnn_core/param/default.json` (NOT a modern "hierarchical" JSON file like
`hnn_core/param/neymotin2020_base.json`). If dict, it is assumed to be a
dictionary of parameters in the "flat" JSON style. If None (the default), the
default parameters are used from `hnn_core/param/default.json`. Note that if you
have drive parameters included, but you ALSO set the deprecated
`add_drives_from_params` to True, the drives will be added twice if the drive
names are the same.
add_drives_from_params : bool, default=False
If True, add drives as defined in the params-dict. NB this is mainly
for backward-compatibility with HNN GUI, and will be deprecated in a
future release. Default: False
legacy_mode : bool, default=False
Set to False by default. Enables matching HNN GUI output when drives
are added suitably. Will be deprecated in a future release.
mesh_shape : tuple of int (default: (10, 10))
Defines the (n_x, n_y) shape of the grid of pyramidal cells.
Returns
-------
net : Instance of Network object
Network object used to store
Notes
-----
The network is composed of a square grid of pyramidal cells, arranged in two layers
(L5 and L2). The default in-plane separation of the grid points is 1.0 um, and the
layer separation 1307.4 um. These can be adjusted after the net is created using the
set_cell_positions-method. An all-to-all connectivity pattern is applied between
cells. Inhibitory basket cells are present at a 1:3-ratio.
This network was first described in Jones et al. 2009 [1]_ , and this code provides
the implementation used in Neymotin et al. 2020 [2]_ .
References
----------
.. [1] Jones, Stephanie R., et al. "Quantitative Analysis and Biophysically
Realistic Neural Modeling of the MEG Mu Rhythm: Rhythmogenesis and Modulation
of Sensory-Evoked Responses." Journal of Neurophysiology 102, 3554–3572
(2009). https://doi.org/10.1152/jn.00535.2009
.. [2] Neymotin, Samuel A, et al. 2020. "Human Neocortical Neurosolver (HNN), a New
Software Tool for Interpreting the Cellular and Network Origin of Human
MEG/EEG Data." eLife 9 (January):e51214. https://doi.org/10.7554/eLife.51214
"""
warnings.warn(
"""
Calling the default model with `jones_2009_model` is now deprecated. Please
update your scripts to use `neymotin_2020_model`, which is a more accurate name
for the model. `jones_2009_model` will still be made available for
backwards-compatilibity purposes.
""",
FutureWarning,
)
net = neymotin_2020_model(params, add_drives_from_params, legacy_mode, mesh_shape)
return net
[docs]
def law_2021_model(
params=None,
add_drives_from_params=False,
legacy_mode=False,
mesh_shape=(10, 10),
):
"""Instantiate the network model described in Law et al., 2021.
This creates a network that is the expansion of Jones 2009 model to study beta
modulated ERPs as described in Law et al. Cereb. Cortex 2021 [1]_ .
Parameters
----------
params : str | Path | dict | None, default=None
Custom Network parameters to use, if any. If string or Path, it is assumed to be
a path to a legacy "flat" JSON file containing the parameters in the style of
`hnn_core/param/default.json` (NOT a modern "hierarchical" JSON file like
`hnn_core/param/neymotin2020_base.json`). If dict, it is assumed to be a
dictionary of parameters in the "flat" JSON style. If None (the default), the
default parameters are used from `hnn_core/param/default.json`. Note that if you
have drive parameters included, but you ALSO set the deprecated
`add_drives_from_params` to True, the drives will be added twice if the drive
names are the same.
add_drives_from_params : bool, default=False
If True, add drives as defined in the params-dict. NB this is mainly
for backward-compatibility with HNN GUI, and will be deprecated in a
future release. Default: False
legacy_mode : bool, default=False
Set to False by default. Enables matching HNN GUI output when drives
are added suitably. Will be deprecated in a future release.
mesh_shape : tuple of int (default: (10, 10))
Defines the (n_x, n_y) shape of the grid of pyramidal cells.
Returns
-------
net : Instance of Network object
Network object used to store the model used in
Law et al. 2021.
See Also
--------
jones_2009_model
Notes
-----
Model reproduces results from Law et al. 2021
This model differs from the default network model in several
parameters including
1) Increased GABAb time constants on L2/L5 pyramidal cells
2) Decrease L5_pyramidal -> L5_pyramidal nmda weight
3) Modified L5_basket -> L5_pyramidal inhibition weights
4) Removal of L5 pyramidal somatic and basal dendrite calcium channels
5) Replace L2_basket -> L5_pyramidal GABAa connection with GABAb
6) Addition of L5_basket -> L5_pyramidal distal connection
References
----------
.. [1] Law, Robert G., et al. "Thalamocortical Mechanisms Regulating the
Relationship between Transient Beta Events and Human Tactile
Perception." Cerebral Cortex, 32, 668–688 (2022).
"""
hnn_core_root = Path(hnn_core.__file__).parent
_validate_type(
params, (str, Path, dict, type(None)), "params", "str | Path | dict | None"
)
if params is None:
params_fname = hnn_core_root / "param" / "default.json"
params = read_params(params_fname)
elif isinstance(params, str) or isinstance(params, Path):
params = read_params(params)
net = neymotin_2020_model(
params,
add_drives_from_params,
legacy_mode,
mesh_shape=mesh_shape,
)
# Ensure model_variant and params' cell types match current model (same cell types
# as 'neymotin_2020_model')
net._model_variant = _validate_params_for_model(net, params, "law_2021_model")
# Update biophysics (increase gabab duration of inhibition)
net.cell_types["L2_pyramidal"]["cell_object"].synapses["gabab"]["tau1"] = 45.0
net.cell_types["L2_pyramidal"]["cell_object"].synapses["gabab"]["tau2"] = 200.0
net.cell_types["L5_pyramidal"]["cell_object"].synapses["gabab"]["tau1"] = 45.0
net.cell_types["L5_pyramidal"]["cell_object"].synapses["gabab"]["tau2"] = 200.0
# Decrease L5_pyramidal -> L5_pyramidal nmda weight
net.connectivity[2]["nc_dict"]["A_weight"] = 0.0004
# Modify L5_basket -> L5_pyramidal inhibition
net.connectivity[6]["nc_dict"]["A_weight"] = 0.02 # gabaa
net.connectivity[7]["nc_dict"]["A_weight"] = 0.005 # gabab
# Remove L5 pyramidal somatic and basal dendrite calcium channels
for sec in ["soma", "basal_1", "basal_2", "basal_3"]:
del net.cell_types["L5_pyramidal"]["cell_object"].sections[sec].mechs["ca"]
# Remove L2_basket -> L5_pyramidal gabaa connection
del net.connectivity[10] # Original paper simply sets gbar to 0.0
# Add L2_basket -> L5_pyramidal gabab connection
delay = net.delay
src_cell = "L2_basket"
target_cell = "L5_pyramidal"
lamtha = 50.0
weight = 0.0002
loc = "distal"
receptor = "gabab"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# Add L5_basket -> L5_pyramidal distal connection
# ("Martinotti-like recurrent tuft connection")
src_cell = "L5_basket"
target_cell = "L5_pyramidal"
lamtha = 70.0
loc = "distal"
receptor = "gabaa"
key = f"gbar_L5Basket_L5Pyr_{receptor}"
weight = net._params[key]
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
return net
# Remove params argument after updating examples
# (only relevant for Jones 2009 model)
[docs]
def calcium_model(
params=None,
add_drives_from_params=False,
legacy_mode=False,
mesh_shape=(10, 10),
):
"""Instantiate the Jones 2009 model with improved calcium dynamics in
L5 pyramidal neurons. For more details on changes to calcium dynamics
see Kohl et al. Brain Topragr 2022 [1]_
Parameters
----------
params : str | Path | dict | None, default=None
Custom Network parameters to use, if any. If string or Path, it is assumed to be
a path to a legacy "flat" JSON file containing the parameters in the style of
`hnn_core/param/default.json` (NOT a modern "hierarchical" JSON file like
`hnn_core/param/neymotin2020_base.json`). If dict, it is assumed to be a
dictionary of parameters in the "flat" JSON style. If None (the default), the
default parameters are used from `hnn_core/param/default.json`. Note that if you
have drive parameters included, but you ALSO set the deprecated
`add_drives_from_params` to True, the drives will be added twice if the drive
names are the same.
add_drives_from_params : bool, default=False
If True, add drives as defined in the params-dict. NB this is mainly
for backward-compatibility with HNN GUI, and will be deprecated in a
future release. Default: False
legacy_mode : bool, default=False
Set to False by default. Enables matching HNN GUI output when drives
are added suitably. Will be deprecated in a future release.
mesh_shape : tuple of int (default: (10, 10))
Defines the (n_x, n_y) shape of the grid of pyramidal cells.
Returns
-------
net : Instance of Network object
Network object used to store the Jones 2009 model with an improved
calcium channel distribution.
See Also
--------
jones_2009_model
Notes
-----
This model builds on the Jones 2009 model by using a more biologically
accurate distribution of calcium channels on L5 pyramidal cells.
Specifically, this model introduces a distance dependent maximum
conductance (gbar) on calcium channels such that the gbar linearly
decreases along the dendrites in the direction of the soma.
References
----------
.. [1] Kohl, Carmen, et al. "Neural Mechanisms Underlying Human Auditory
Evoked Responses Revealed By Human Neocortical Neurosolver."
Brain Topography, 35, 19–35 (2022).
"""
hnn_core_root = Path(hnn_core.__file__).parent
_validate_type(
params, (str, Path, dict, type(None)), "params", "str | Path | dict | None"
)
if params is None:
params_fname = hnn_core_root / "param" / "default.json"
params = read_params(params_fname)
elif isinstance(params, str) or isinstance(params, Path):
params = read_params(params)
net = jones_2009_model(
params,
add_drives_from_params,
legacy_mode,
mesh_shape=mesh_shape,
)
# Ensure model_variant and params' cell types match current model (same cell types
# as 'neymotin_2020_model')
net._model_variant = _validate_params_for_model(net, params, "calcium_model")
# Replace L5 pyramidal cell template with updated calcium
cell_name = "L5_pyramidal"
pos = net.cell_types[cell_name]["cell_object"].pos
net.cell_types[cell_name]["cell_object"] = pyramidal_ca(
cell_name=cell_name, pos=pos
)
return net
def duecker_ET_model(params=None, add_erp_drives=False, mesh_shape=(10, 10)):
"""Instantiate the Duecker human-derived model (publication pending)
Parameters
----------
params : str | dict | None
The path to the parameter file for constructing the network.
If None, parameters loaded from 'default_duecker_ET.json'
Default: None
add_erp_drives : bool, default=False
If True, add evoked drives used to generate Event-Related Potentials (ERPs) in
the upcoming Duecker model publication. Note that these drives are SUBJECT TO
CHANGE until the time of publication! The current values will probably not be
the final values used in the publication.
mesh_shape : tuple of int (default: (10, 10))
Defines the (n_x, n_y) shape of the grid of pyramidal cells.
Returns
-------
net : Instance of Network object
Network object used to store
"""
# Prepare parameters used to create the initial Network object
# ----------------------------------------------------------------------------------
hnn_core_root = Path(hnn_core.__file__).parent
_validate_type(
params, (str, Path, dict, type(None)), "params", "str | Path | dict | None"
)
if params is None:
params_fname = hnn_core_root / "param" / "default_duecker_ET.json"
params = read_params(params_fname)
elif isinstance(params, str) or isinstance(params, Path):
params = read_params(params)
cell_types = {
"L2_inhibitory": {
"cell_object": human_gen_interneuron(
cell_name=_short_name("L2_inhibitory"), layer=2
),
"cell_metadata": {
"morpho_type": "interneuron",
"electro_type": "inhibitory",
"layer": "2",
"zdist_origin": 0.8,
"measure_dipole": False,
"reference": "",
"color": "#daa69c",
"marker": "o",
},
},
"L2_pyramidal": {
"cell_object": pyramidal_humanL23(cell_name=_short_name("L2_pyramidal")),
"cell_metadata": {
"morpho_type": "pyramidal",
"electro_type": "excitatory",
"layer": "2",
"zdist_origin": 1,
"measure_dipole": True,
"reference": "",
"color": "#a41e4f",
"marker": "^",
},
},
"L5_inhibitory": {
"cell_object": human_gen_interneuron(
cell_name=_short_name("L5_inhibitory"), layer=5
),
"cell_metadata": {
"morpho_type": "interneuron",
"electro_type": "inhibitory",
"layer": "5",
"zdist_origin": 0.2,
"measure_dipole": False,
"reference": "",
"color": "#77a1bb",
"marker": "o",
},
},
"L5_pyramidal": {
"cell_object": pyramidal_humanL5ET(cell_name=_short_name("L5_pyramidal")),
"cell_metadata": {
"morpho_type": "pyramidal",
"electro_type": "excitatory",
"layer": "5",
"zdist_origin": 0,
"measure_dipole": True,
"reference": "",
"color": "#5c73b7",
"marker": "^",
},
},
}
# Create layer positions
layer_dict = _create_cell_coords(
n_pyr_x=mesh_shape[0],
n_pyr_y=mesh_shape[1],
z_coord=1307.4, # Default layer separation
inplane_distance=1.0, # in-plane distance appropriate for LFP recordings
)
# Map cell types to layer positions
pos_dict = {
"L5_pyramidal": layer_dict["L5_bottom"],
"L2_pyramidal": layer_dict["L2_bottom"],
"L5_inhibitory": layer_dict["L5_mid"],
"L2_inhibitory": layer_dict["L2_mid"],
"origin": layer_dict["origin"],
}
# Created the Network object, and do some housekeeping
# ----------------------------------------------------------------------------------
# Create network with cell types and positions
net = Network(
params,
add_drives_from_params=False,
legacy_mode=False,
mesh_shape=mesh_shape,
pos_dict=pos_dict,
cell_types=cell_types,
)
# check variant and cell types. Basket cells are replaced by
# interneurons in duecker_ET_model, so their parameters are rejected
net._model_variant = _validate_params_for_model(
net,
params,
"duecker_ET_model",
require_variant=True,
excluded_cells=("L2Basket", "L5Basket"),
)
delay = net.delay
# Add recurrent connections
# ----------------------------------------------------------------------------------
# layer2 Pyr -> layer2 Pyr
lamtha = 6.125 # calculated from human data Campganola et al. 2022
loc = "proximal"
target_cell = "L2_pyramidal"
for receptor in ["nmda", "ampa"]:
key = f"gbar_{_short_name(target_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
target_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# layer5 Pyr -> layer5 Pyr
target_cell = "L5_pyramidal"
for receptor in ["nmda", "ampa"]:
key = f"gbar_{_short_name(target_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
target_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# layer2 inhibitory -> layer2 Pyr
src_cell = "L2_inhibitory"
target_cell = "L2_pyramidal"
lamtha = 6.125 # *0.8 # shorter space constant (Campagnola, 2022, mice data)
loc = "soma"
for receptor in ["gabaa", "gabab"]:
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
loc = "soma"
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# layer5 inhibitory -> layer5 Pyr
src_cell = "L5_inhibitory"
target_cell = "L5_pyramidal"
lamtha = 6.125 # *0.8 # shorter space constant (Campagnola, 2022, mice data)
loc = "soma"
for receptor in ["gabaa", "gabab"]:
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# layer2 Pyr -> layer5 Pyr
src_cell = "L2_pyramidal"
lamtha = 6.125
for receptor in ["ampa", "nmda"]:
for loc in ["proximal", "apical_2"]:
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
src_cell, target_cell, loc, receptor, weight, delay, lamtha
)
# layer2 inhibitory -> layer5 Pyr
src_cell = "L2_inhibitory"
receptor = "gabaa_slow"
lamtha = 6.125
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
# add GABAA connection to apical_2 as Martinotti-like inhibition (SST cells)
loc = "apical_2"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# this connection is set to 0 as we're not simulating NGF cells.
loc = "apical_tuft"
receptor = "gabab"
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# xx -> layer2 inhibitory
src_cell = "L2_pyramidal"
target_cell = "L2_inhibitory"
lamtha = 6.125 * 0.8 # shorter space constant (Campagnola, 2022, mice data)
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}"
weight = params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
weight = params[key] * 0.18 # see Koh 1995; Kriener 2022
receptor = "nmda"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
src_cell = "L2_inhibitory"
lamtha = 6.125
receptor = "gabaa"
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
loc = "soma"
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
receptor = "gabab"
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
loc = "soma"
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
# xx -> layer5 Basket
src_cell = "L5_inhibitory"
target_cell = "L5_inhibitory"
lamtha = 6.125
loc = "soma"
receptor = "gabaa"
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
receptor = "gabab"
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}_{receptor}"
weight = params[key]
net.add_connection(
src_cell,
target_cell,
loc,
receptor,
weight,
delay,
lamtha,
allow_autapses=False,
)
src_cell = "L5_pyramidal"
lamtha = 6.125 * 0.8 # shorter space constant (Campagnola, 2022, mice data)
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}"
weight = params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
weight = params[key] * 0.2 # see Koh 1995; Kriener 2022
receptor = "nmda"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
src_cell = "L2_pyramidal"
lamtha = 6.125 * 0.8 # shorter space constant (Campagnola, 2022, mice data)
key = f"gbar_{_short_name(src_cell)}_{_short_name(target_cell)}"
weight = params[key]
loc = "soma"
receptor = "ampa"
net.add_connection(src_cell, target_cell, loc, receptor, weight, delay, lamtha)
# Add drives, if requested
# ----------------------------------------------------------------------------------
if add_erp_drives:
weights_ampa_p1 = {
"L2_inhibitory": np.float64(0.007588122527131926),
"L2_pyramidal": np.float64(0.018642951414638384),
"L5_inhibitory": np.float64(7.6438985456048e-15),
"L5_pyramidal": np.float64(0.013984204478845295),
}
weights_nmda_p1 = {
"L2_inhibitory": np.float64(0.012050887238347885),
"L2_pyramidal": np.float64(0.029485349160794205),
"L5_inhibitory": np.float64(1.002171790226989e-15),
"L5_pyramidal": np.float64(0.005379056342376538),
}
synaptic_delays_p1 = {
"L2_inhibitory": 0.1,
"L2_pyramidal": 0.1,
"L5_inhibitory": 1.0,
"L5_pyramidal": 1.0,
}
net.add_evoked_drive(
"prox1",
location="proximal",
cell_specific=True,
mu=15.59527767691755,
sigma=2.792948035872017,
numspikes=1,
weights_ampa=weights_ampa_p1,
weights_nmda=weights_nmda_p1,
synaptic_delays=synaptic_delays_p1,
)
weights_ampa_d1 = {
"L2_inhibitory": np.float64(4.378900714089845e-06),
"L2_pyramidal": np.float64(8.300973449010934e-05),
"L5_pyramidal": np.float64(1.1808240677861086),
}
weights_nmda_d1 = {
"L2_inhibitory": np.float64(2.1176135674160224e-12),
"L2_pyramidal": np.float64(0.00010941280722617688),
"L5_pyramidal": np.float64(0.013519066989944073),
}
synaptic_delays_d1 = {
"L2_inhibitory": 0.1,
"L2_pyramidal": 0.1,
"L5_pyramidal": 0.1,
}
net.add_evoked_drive(
"dist1",
location="distal",
cell_specific=True,
mu=59.187666976120305,
sigma=4.446160189077672,
numspikes=4,
weights_ampa=weights_ampa_d1,
weights_nmda=weights_nmda_d1,
synaptic_delays=synaptic_delays_d1,
)
weights_ampa_p2 = {
"L2_inhibitory": np.float64(1.0000447086287455e-15),
"L2_pyramidal": np.float64(0.015172234470615126),
"L5_inhibitory": np.float64(2.247668491134226e-07),
"L5_pyramidal": np.float64(0.2984173605845747),
}
weights_nmda_p2 = {
"L2_inhibitory": np.float64(1.495423386739259e-15),
"L2_pyramidal": np.float64(1.9874771611430298e-05),
"L5_inhibitory": np.float64(7.46394548638012e-08),
"L5_pyramidal": np.float64(0.0012506313370813725),
}
synaptic_delays_p2 = {
"L2_inhibitory": 0.1,
"L2_pyramidal": 0.1,
"L5_inhibitory": 1.0,
"L5_pyramidal": 1.0,
}
net.add_evoked_drive(
"prox2",
location="proximal",
cell_specific=True,
mu=122.49858703637535,
sigma=14.42431814878363,
numspikes=3,
weights_ampa=weights_ampa_p2,
weights_nmda=weights_nmda_p2,
synaptic_delays=synaptic_delays_p2,
)
return net
def add_erp_drives_to_jones_model(net, tstart=0.0):
"""Add drives necessary for an event related potential (ERP)
Parameters
----------
net : Instance of Network object
Network object that will be updated with ERP drives.
Drives are updated in place.
tstart : float | int
Start time of sensory input in ms. (Default 0.0 ms)
Notes
-----
The first proximal input arrives at cortex ~20 ms after sensory
stimulus. The exact delay depends random number generator due to
random sampling of times from a gaussian.
"""
_validate_type(net, Network, "net", "Network")
_validate_type(tstart, (float, int), "tstart", "float or int")
# Add distal drive
weights_ampa_d1 = {
"L2_basket": 0.006562,
"L2_pyramidal": 7e-6,
"L5_pyramidal": 0.142300,
}
weights_nmda_d1 = {
"L2_basket": 0.019482,
"L2_pyramidal": 0.004317,
"L5_pyramidal": 0.080074,
}
synaptic_delays_d1 = {"L2_basket": 0.1, "L2_pyramidal": 0.1, "L5_pyramidal": 0.1}
net.add_evoked_drive(
"evdist1",
mu=63.53 + tstart,
sigma=3.85,
numspikes=1,
weights_ampa=weights_ampa_d1,
weights_nmda=weights_nmda_d1,
location="distal",
synaptic_delays=synaptic_delays_d1,
event_seed=274,
)
# Add proximal drives
weights_ampa_p1 = {
"L2_basket": 0.08831,
"L2_pyramidal": 0.01525,
"L5_basket": 0.19934,
"L5_pyramidal": 0.00865,
}
synaptic_delays_prox = {
"L2_basket": 0.1,
"L2_pyramidal": 0.1,
"L5_basket": 1.0,
"L5_pyramidal": 1.0,
}
net.add_evoked_drive(
"evprox1",
mu=26.61 + tstart,
sigma=2.47,
numspikes=1,
weights_ampa=weights_ampa_p1,
weights_nmda=None,
location="proximal",
synaptic_delays=synaptic_delays_prox,
event_seed=544,
)
weights_ampa_p2 = {
"L2_basket": 0.000003,
"L2_pyramidal": 1.438840,
"L5_basket": 0.008958,
"L5_pyramidal": 0.684013,
}
net.add_evoked_drive(
"evprox2",
mu=137.12 + tstart,
sigma=8.33,
numspikes=1,
weights_ampa=weights_ampa_p2,
location="proximal",
synaptic_delays=synaptic_delays_prox,
event_seed=814,
)