"""Network model functions."""
# Authors: Nick Tolley <nicholas_tolley@brown.edu>
from pathlib import Path
from copy import deepcopy
import warnings
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_drives_from_params=False,
legacy_mode=False,
mesh_shape=(10, 10),
):
"""Initiate the Duecker model (like old calcium model and then replace with new cells)
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
"""
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"],
}
# 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,
)
# 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
# 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)
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,
)