# ~/analyseur/cbgtc/parameters.py
#
# Documentation by Lungsi 20 Oct 2025
#
# This contains function for loading the files
#
from dataclasses import dataclass, field
# from typing import List, Tuple, Set, Dict # Not needed for Python 3.9+
import math
DEFAULT_CONDUCTANCES = {
"cortex": { # 0.05
"g_L": 1.0, # This is called g_L_mean in CBGTC and unit is mS.cm-2
},
"bg": {},
"thalamus": {
"g_L": 0.05, # This is called g_L_mean in CBGTC and unit is mS.cm-2
},
}
DEFAULT_FEEDFORWORD_CURRENTS = {
"cortex": { # Case: NOT 'cortex_alone'
"CSN": 1.124,
"PTN": 1.25,
"IN": 1.178,
},
"bg": { # Case: NOT 'bg_alone'
"MSN": 28.3, # * mvolt
"FSI": 12.0, # * mvolt
"STN": 19.5, # * mvolt
"GPe": 26.0, # * mvolt
"GPi": 15.0, # * mvolt
# NOTE: for bg these are NOT current values
},
"thalamus": { # Case: NOT 'thalamus_alone'
"MD": 1.15,
"TRN": 1.2,
},
}
DEFAULT_EQUILIBRIUM_POTENTIALS = {
"cortex": {
"V_L": -70.0, # mV
"V_AMPA": 0.0, # mV
"V_GABAA": -70.0, # mV
"V_NMDA": 0.0, # mV
"V_GABAB": -90.0, # mV
},
"bg": {
"V_L": -70.0, # mV
"V_AMPA": 0.0, # mV
"V_NMDA": 0.0, # mV
"V_GABAA": -70.0, # mV
"V_GABAB": -90.0, # mV
},
"thalamus": {
"V_L": -70.0, # mV
"V_AMPA": 0.0, # mV
"V_GABAA": -70.0, # mV
"V_NMDA": 0.0, # mV
"V_GABAB": -90.0, # mV
}
}
DEFAULT_FREQUENCY_BANDS = {
"Delta": (1, 4),
"Theta": (4, 8),
"Alpha": (8, 12),
"Beta": (12, 30),
"Gamma": (30, 100),
"Low Gamma": (30, 80),
"High Gamma": (80, 150),
}
DEFAULT_SIZE_INFO = {
"cortex": {
"TOTAL_NUMBER_OF_POPULATIONS": 4, # TOTAL_NUMBER_OF_CHANNELS_MODEL
"scale": 4, # The number of neurons will be multiplied by this value
"TOTAL_NUMBER_OF_CHANNELS": 4,
},
"bg": {
"TOTAL_NUMBER_OF_POPULATIONS": 4,
"scale": 4,
"TOTAL_NUMBER_OF_CHANNELS": 4,
},
"thalamus": {
"TOTAL_NUMBER_OF_POPULATIONS": 4,
"scale": 4,
"TOTAL_NUMBER_OF_CHANNELS": 4,
}
}
DEFAULT_PROJECTIONS_TYPES = {
"bg": {
'MD->MSN' : 'focused', # TODO check if should be diffuse instead?
'MD->FSI' : 'focused', # TODO check if should be diffuse instead?
'ProtGPe->STN': 'focused', # ^
'GPe->STN': 'focused', # ^
'PTN->MSN': 'focused', # ^
'CSN->MSN': 'focused', #
'CMPf->MSN': 'diffuse', # ^
'MSN->MSN': 'diffuse', # ^
'FSI->MSN': 'diffuse', # ^
'STN->MSN': 'diffuse', # ^
'GPe->MSN': 'diffuse', # ^
'CSN->FSI': 'focused', # ^
'PTN->FSI': 'focused', # ^
'CMPf->FSI': 'diffuse', # ^
'FSI->FSI': 'diffuse', # ^
'STN->FSI': 'diffuse', # ^
'GPe->FSI': 'diffuse', # ^
'PTN->STN': 'focused', # ^
'CMPf->STN': 'diffuse', # ^
'GPe->STN': 'focused', # ^
'CMPf->GPe': 'diffuse', # ^
'MSN->GPe': 'focused', # ^
'STN->GPe': 'diffuse', # ^
'GPe->GPe': 'diffuse', # ^
'CMPf->GPi': 'diffuse', # ^
'MSN->GPi': 'focused', # ^
'STN->GPi': 'diffuse', # ^
'GPe->GPi': 'diffuse', #
'ArkyGPe->MSN': 'diffuse', # ^
'ArkyGPe->FSI': 'diffuse', # ^
'CMPf->ArkyGPe': 'diffuse', # ^
'MSN->ArkyGPe': 'focused', # ^
'STN->ArkyGPe': 'diffuse', # ^
'ArkyGPe->ArkyGPe': 'diffuse', # ^
'ProtGPe->ArkyGPe': 'diffuse', # ^
'CMPf->ProtGPe': 'diffuse', # ^
'MSN->ProtGPe': 'focused', # ^
'STN->ProtGPe': 'diffuse', # ^
'ArkyGPe->ProtGPe': 'diffuse', # ^
'ProtGPe->ProtGPe': 'diffuse', # ^
'ProtGPe->GPi': 'diffuse', # ^
},
"cortex": {
'CSN->CSN': 'diffuse',
'CSN->PTN': 'diffuse',
'CSN->IN': 'diffuse',
'PTN->CSN': 'diffuse',
'PTN->PTN': 'diffuse',
'PTN->IN': 'diffuse',
'IN->CSN': 'diffuse',
'IN->IN': 'diffuse',
'IN->PTN': 'diffuse',
'MD->IN': 'diffuse',
'MD->PTN': 'focused',
'MD->CSN': 'focused'
},
"thalamus": {
'TRN->MD': 'diffuse',
'MD->TRN': 'focused',
'TRN->TRN' : 'diffuse',
'GPi->MD': 'focused',
'PTN->MD': 'focused',
'PTN->TRN': 'focused',
'CMPf->TRN': 'diffuse'
},
}
DEFAULT_CONNECTED_REGIONS = {
("Cortex", "Cortex"),
("Cortex", "BasalGanglia"),
("Cortex", "Thalamus"),
("BasalGanglia", "Thalamus"),
("Thalamus", "Cortex"),
("BasalGanglia", "BasalGanglia"),
("Thalamus", "Thalamus"),
}
[docs]
def bin_size_by_rule(total_time=None, rule=None, frequency=None):
"""
Returns bin size by rule
- Square Root Rule (`rule="Square Root"`)
- general purpose, quick estimate
- Rice Rule (`rule="Rice Rule"`)
- suitable for larger datasets
- Periodic (`rule="Periodic"`)
- for periodic signal
-------
Formula
-------
.. table::
============================================ ======================================================
Definitions Interpretation
============================================ ======================================================
duration, :math:`dur` total time in seconds
sampling period, :math:`T` sampling period in seconds
total samples, :math:`N = dur / T` total number of samples
============================================ ======================================================
Formula: Square Root Rule
-------------------------
.. math::
n_{bins} &= \\sqrt(N) \n
binsz &= \\frac{dur}{n_{bins}}
Formula: Rice Rule
------------------
.. math::
n_{bins} &= 2 \\sqrt[3](N) \n
binsz &= \\frac{dur}{n_{bins}}
Formula: Periodic Rule
----------------------
Let the periodic signal be oscillating at :math:`\\nu` frequency, then
.. math::
T &= \\frac{1}{\\nu} \n
binsz &= m \\times T
where `m` is the number of periods. Generally, :math:`m=2`.
--------
Use Case
--------
::
from analyseur.cbgtc.parameters import bin_size_by_rule
dur = 10 # seconds
nu = 80 # Hz
binsz_sqrt = bin_size_by_rule(total_time=dur, rule="Square Root")
binsz_rice = bin_size_by_rule(total_time=dur, rule="Rice Rule")
binsz_osc = bin_size_by_rule(frequency=nu, rule="Periodic")
.. raw:: html
<hr style="border: 2px solid red; margin: 20px 0;">
"""
match rule:
case "Square Root":
n_bins = round( math.sqrt(total_time) )
case "Rice Rule":
n_bins = round( 2 * math.cbrt(total_time) )
case "Periodic":
period = 1 / frequency
if rule in ["Square Root", "Rice Rule"]:
return total_time / n_bins
else:
return 2 * period # two periods in one bin
[docs]
@dataclass
class SimulationParams:
"""
================
SimulationParams
================
Default parameters from CBGT simulation
+----------------------+---------------------------------------+
| Parameter name | Value |
+======================+=======================================+
| `duration` | `10000` milliseconds |
+----------------------+---------------------------------------+
| `t_start_recording` | `2000` milliseconds |
+----------------------+---------------------------------------+
| `dt` | `0.1` milliseconds |
+----------------------+---------------------------------------+
| `nuclei_ctx` | `["CSN", "PTN", "IN"]` |
+----------------------+---------------------------------------+
| `nuclei_bg` | `["FSI", "GPe", "GPi", "MSN", "STN"]` |
+----------------------+---------------------------------------+
| `nuclei_thal` | `["TRN", "MD"]` |
+----------------------+---------------------------------------+
| `neurotrans` | `['AMPA', 'NMDA', 'GABAA', 'GABAB']` |
+----------------------+---------------------------------------+
| `conductance` | `DEFAULT_CONDUCTANCES` |
+----------------------+---------------------------------------+
| `ff_currents` | `DEFAULT_FEEDFORWORD_CURRENTS` |
+----------------------+---------------------------------------+
--------
Use Case
--------
::
from analyseur.cbgtc.parameters import SimulationParams
simparams = SimulationParams()
.. raw:: html
<hr style="border: 2px solid red; margin: 20px 0;">
"""
# --- Class constants ---
REGION_ALIASES = {"cortex": "Cortex",
"ctx": "Cortex",
"basalganglia": "BasalGanglia",
"bg": "BasalGanglia",
"basal_ganglia": "BasalGanglia",
"thalamus": "Thalamus",
"thal": "Thalamus",}
# --- Instance fields ---
duration: float = 10000 # ms
t_start_recording = 2000 # ms
dt: float = 0.1 # ms
nuclei_ctx: list[str] = None
nuclei_bg: list[str] = None
nuclei_thal: list[str] = None
neurotrans: list[str] = None
conductance: dict = field(default_factory=lambda: DEFAULT_CONDUCTANCES.copy())
ff_currents: dict = field(default_factory=lambda: DEFAULT_FEEDFORWORD_CURRENTS.copy())
size_info: dict = field(default_factory=lambda: DEFAULT_SIZE_INFO.copy())
modelParamsID: int = 9
projection_types: dict = field(default_factory=lambda: DEFAULT_PROJECTIONS_TYPES.copy())
connected_regions: set[tuple[str, str]] = field(default_factory=lambda: DEFAULT_CONNECTED_REGIONS.copy())
def __post_init__(self):
if self.nuclei_ctx is None:
self.nuclei_ctx = ["CSN", "PTN", "IN",]
if self.nuclei_bg is None:
self.nuclei_bg = ["FSI", "GPe", "GPi", "MSN", "STN",]
if self.nuclei_thal is None:
self.nuclei_thal = ["MD", "TRN",] # "CMPf" is not accompanied with myriad raw datasets
if self.neurotrans is None:
self.neurotrans = ['AMPA', 'NMDA', 'GABAA', 'GABAB']
[docs]
@dataclass
class SignalAnalysisParams:
"""
====================
SignalAnalysisParams
====================
Default parameters for signal analysis
+------------------------+---------------------+
| Parameter name | Value |
+========================+=====================+
| `_1000ms` | `1000` milliseconds |
+------------------------+---------------------+
| `decimal_places` | `3` |
+------------------------+---------------------+
| `decimal_places_ephys` | `5` |
+------------------------+---------------------+
| `window` | `(0, 10)` seconds |
+------------------------+---------------------+
| `sampling_period` | `0.0001` seconds |
+------------------------+---------------------+
| `sampling_period_ms` | `0.1` milliseconds |
+------------------------+---------------------+
| `binsz_sqrt_rule` | `100` |
+------------------------+---------------------+
| `binsz_rice_rule` | `232.558` |
+------------------------+---------------------+
| `binsz_10perbin` | `0.001` |
+------------------------+---------------------+
| `binsz_100perbin` | `0.01` |
+------------------------+---------------------+
| `binsz_1000perbin` | `0.1` |
+------------------------+---------------------+
--------
Use Case
--------
::
from analyseur.cbgtc.parameters import SignalAnalysisParams
siganal = SignalAnalysisParams()
.. raw:: html
<hr style="border: 2px solid red; margin: 20px 0;">
"""
_1000ms: int = 1000
decimal_places: int = 3
decimal_places_ephys: int = 5 # very small values for disinhibition experiments
window: tuple[float, float] = (0, SimulationParams.duration / _1000ms)
sampling_period_ms: float = SimulationParams.dt
sampling_period: float = SimulationParams.dt / _1000ms
binsz_sqrt_rule: float = bin_size_by_rule(SimulationParams.duration / _1000ms, "Square Root")
binsz_rice_rule: float = bin_size_by_rule(SimulationParams.duration / _1000ms, "Rice Rule")
binsz_10perbin: float = 10 * sampling_period
binsz_100perbin: float = 100 * sampling_period
binsz_1000perbin: float = 1000 * sampling_period
std_Gaussian_kernel: float = 2
freq_bands: dict = field(default_factory=lambda: DEFAULT_FREQUENCY_BANDS.copy())
def validate(self):
if any(binsz <= 0 for binsz in [binsz_sqrt_rule, binsz_rice_rule,
binsz_10perbin, binsz_100perbin,
binsz_1000perbin]):
raise ValueError("bin size must be positive")
if self.window[1] <= self.window[0]:
raise ValueError("time window end must be greater than start")
#custom_param = SpikeAnalysisParams(window=(0,5), binsz=0.02)