# ~/analyseur/rbcbg/parameters.py
#
# Documentation by Lungsi 18 Nov 2025
#
from dataclasses import dataclass, field
# from typing import List, Tuple, Set, Dict # Not needed for Python 3.9+
import math
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),
}
[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.rbcbg.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 rBCBG simulation
+----------------------+---------------------------------------+
| Parameter name | Value |
+======================+=======================================+
| `duration` | `10000` milliseconds |
+----------------------+---------------------------------------+
| `dt` | `1.0` milliseconds |
+----------------------+---------------------------------------+
| `modelID` | `9` |
+----------------------+---------------------------------------+
| `nuclei_ctx` | `["CSN", "PTN", "CTX_E", "CTX_I"]` |
+----------------------+---------------------------------------+
| `nuclei_bg` | `["FSI", "STN", "GPe", "GPiSNr",]` |
+----------------------+---------------------------------------+
| `nuclei_thal` | `["TRN", "TH"]` |
+----------------------+---------------------------------------+
--------
Use Case
--------
::
from analyseur.rbcbg.parameters import SimulationParams
simparams = SimulationParams()
.. raw:: html
<hr style="border: 2px solid red; margin: 20px 0;">
"""
duration: float = 10000 # ms
dt: float = 1.0 # ms
modelID: int = 9
nuclei_ctx: list[str] = None
nuclei_bg: list[str] = None
nuclei_thal: list[str] = None
def __post_init__(self):
if self.nuclei_ctx is None:
self.nuclei_ctx = ["CSN", "PTN", "CTX_E", "CTX_I",]
if self.nuclei_bg is None:
self.nuclei_bg = ["FSI", "STN", "GPe", "GPiSNr",]
if self.nuclei_thal is None:
self.nuclei_thal = ["TRN", "TH"]
[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.001` seconds |
+------------------------+---------------------+
| `sampling_period_ms` | `1.0` milliseconds |
+------------------------+---------------------+
| `binsz_sqrt_rule` | `3.333` |
+------------------------+---------------------+
| `binsz_rice_rule` | `2.5` |
+------------------------+---------------------+
| `binsz_10perbin` | `0.01` |
+------------------------+---------------------+
| `binsz_100perbin` | `0.1` |
+------------------------+---------------------+
| `binsz_1000perbin` | `1.0` |
+------------------------+---------------------+
--------
Use Case
--------
::
from analyseur.rbcbg.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)