From 9f3f199e8081050c5242cc71b1682333f81ed280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:25:06 +0200 Subject: [PATCH 01/17] Replace RANDOM_GENERATOR/SEED imports with get_random_generator accessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous design bound RANDOM_GENERATOR and SEED at module import time via `from myogen import RANDOM_GENERATOR, SEED`. Python's from-import copies the current binding into the importing module's namespace — so when set_random_seed() later rebinds myogen.RANDOM_GENERATOR, existing importers keep their stale reference. The SEED constant was never updated at all, so seeds derived from it (cells.py Cython Mersenne seeds, motor_unit_sim.py KMeans random_state) were frozen to 180319 for the life of the process. Fix: introduce get_random_generator() and get_random_seed() accessors in myogen/__init__.py. A function lookup always reads the current module global, so subsequent set_random_seed() calls propagate correctly. The RANDOM_GENERATOR and SEED module attributes still resolve via a module- level __getattr__ that emits DeprecationWarning and returns the current state, preserving backwards compatibility for external callers. Updated sites: - myogen/simulator/neuron/network.py (5 RNG usages) - myogen/simulator/neuron/cells.py (3 SEED-derived Cython seeds + 1 RNG draw) - myogen/simulator/core/muscle/muscle.py (3 RNG usages) - myogen/simulator/core/emg/surface/surface_emg.py (3 RNG usages) - myogen/simulator/core/emg/intramuscular/motor_unit_sim.py (1 KMeans random_state, 2 rng=RANDOM_GENERATOR caches, 1 draw) - myogen/simulator/core/emg/intramuscular/intramuscular_emg.py (1 RNG draw) Cell-specific seeds now use `get_random_seed() + (class_id+1)*(global_id+1)` — same mathematical structure as before, but the base seed tracks set_random_seed(). Tests: added tests/test_determinism.py with 8 regression tests covering byte-level reproducibility, seed propagation to downstream seed-derivation sites, and DeprecationWarning emission for the legacy attribute names. Added pytest>=8.0 to the dev dependency group. --- myogen/__init__.py | 65 +- .../emg/intramuscular/intramuscular_emg.py | 4 +- .../core/emg/intramuscular/motor_unit_sim.py | 10 +- .../simulator/core/emg/surface/surface_emg.py | 8 +- myogen/simulator/core/muscle/muscle.py | 10 +- myogen/simulator/neuron/cells.py | 10 +- myogen/simulator/neuron/network.py | 12 +- pyproject.toml | 4 + tests/__init__.py | 0 tests/test_determinism.py | 113 +++ uv.lock | 785 +----------------- 11 files changed, 236 insertions(+), 785 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_determinism.py diff --git a/myogen/__init__.py b/myogen/__init__.py index d0954f24..963e6617 100644 --- a/myogen/__init__.py +++ b/myogen/__init__.py @@ -1,24 +1,73 @@ +import warnings + import numpy as np from numpy.random import Generator -SEED: int = 180319 # Seed for reproducibility -RANDOM_GENERATOR: Generator = np.random.default_rng(SEED) +_DEFAULT_SEED: int = 180319 +_current_seed: int = _DEFAULT_SEED +_random_generator: Generator = np.random.default_rng(_DEFAULT_SEED) + +def get_random_generator() -> Generator: + """ + Return the current global RNG. -def set_random_seed(seed: int = SEED) -> None: + Always reflects the most recent ``set_random_seed`` call. Prefer this + accessor over importing ``RANDOM_GENERATOR`` directly — a direct import + captures a stale reference that will not update when the seed changes. + """ + return _random_generator + + +def get_random_seed() -> int: + """Return the seed currently in effect.""" + return _current_seed + + +def set_random_seed(seed: int = _DEFAULT_SEED) -> None: """ Set the random seed for reproducibility. + Rebuilds the global NumPy ``Generator``. All modules that read the RNG + through :func:`get_random_generator` will observe the new state on their + next draw; this includes seeds derived for non-NumPy RNGs (e.g. sklearn + ``random_state`` arguments or Cython Mersenne generators), which are now + drawn from the global RNG rather than read from a frozen module constant. + Parameters ---------- seed : int, optional - Seed value to set, by default SEED + Seed value to set, by default 180319. """ - global RANDOM_GENERATOR - RANDOM_GENERATOR = np.random.default_rng(seed) + global _random_generator, _current_seed + _current_seed = seed + _random_generator = np.random.default_rng(seed) print(f"Random seed set to {seed}.") +def __getattr__(name: str): + """Backwards-compatible access for the deprecated ``RANDOM_GENERATOR`` and ``SEED`` module attributes.""" + if name == "RANDOM_GENERATOR": + warnings.warn( + "myogen.RANDOM_GENERATOR is deprecated; use myogen.get_random_generator() " + "to always retrieve the current RNG. Module-level imports of " + "RANDOM_GENERATOR capture a stale reference that does not update when " + "set_random_seed() is called.", + DeprecationWarning, + stacklevel=2, + ) + return _random_generator + if name == "SEED": + warnings.warn( + "myogen.SEED is deprecated; use myogen.get_random_seed() to retrieve " + "the seed currently in effect.", + DeprecationWarning, + stacklevel=2, + ) + return _current_seed + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + class MyoGenSetupError(Exception): """Exception raised when MyoGen setup fails.""" @@ -217,8 +266,8 @@ def error(msg): _nmodl_loaded = load_nmodl_mechanisms(quiet=True, strict=False) __all__ = [ - "RANDOM_GENERATOR", - "SEED", + "get_random_generator", + "get_random_seed", "set_random_seed", "load_nmodl_mechanisms", "NMODLLoadError", diff --git a/myogen/simulator/core/emg/intramuscular/intramuscular_emg.py b/myogen/simulator/core/emg/intramuscular/intramuscular_emg.py index 456c3b55..44a1afac 100644 --- a/myogen/simulator/core/emg/intramuscular/intramuscular_emg.py +++ b/myogen/simulator/core/emg/intramuscular/intramuscular_emg.py @@ -26,7 +26,7 @@ from neo import AnalogSignal, Block, Segment from tqdm import tqdm -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator.core.emg.electrodes import IntramuscularElectrodeArray from myogen.simulator.core.muscle import Muscle from myogen.utils.decorators import beartowertype @@ -825,7 +825,7 @@ def add_noise(self, snr__dB: float, noise_type: str = "gaussian") -> INTRAMUSCUL # Generate noise if noise_type.lower() == "gaussian": # Generate standard normal noise, then scale per channel - noise = RANDOM_GENERATOR.normal(loc=0.0, scale=1.0, size=emg_array.shape) + noise = get_random_generator().normal(loc=0.0, scale=1.0, size=emg_array.shape) # Broadcast noise_std_per_channel along time axis # noise shape: (time, n_electrodes) # noise_std_per_channel shape: (n_electrodes,) diff --git a/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py b/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py index bc65b54d..2d3af4e0 100644 --- a/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py +++ b/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py @@ -15,7 +15,7 @@ from sklearn.cluster import KMeans from tqdm import tqdm -from myogen import RANDOM_GENERATOR, SEED +from myogen import get_random_generator, get_random_seed from myogen.utils.decorators import beartowertype from .bioelectric import ( get_current_density, @@ -131,14 +131,14 @@ def sim_nmj_branches_two_layers( arborization_z_std : float Standard deviation of secondary arborization in mm """ - rng = RANDOM_GENERATOR + rng = get_random_generator() self.nerve_paths = np.zeros( (self._number_of_muscle_fibers, 2) ) # Point coordinates kmeans = KMeans( - n_clusters=n_branches, init="k-means++", max_iter=100, random_state=SEED + n_clusters=n_branches, init="k-means++", max_iter=100, random_state=get_random_seed() ) idx = kmeans.fit_predict(self.muscle_fiber_centers__mm) c = kmeans.cluster_centers_ @@ -197,7 +197,7 @@ def sim_nmj_branches_gaussian(self, endplate_center: float, branches_z_std: floa branches_z_std : float Standard deviation of NMJ distribution in mm """ - rng = RANDOM_GENERATOR + rng = get_random_generator() self.nmj_z = rng.normal(endplate_center, branches_z_std, self.Nmf) # Simplified nerve paths (single segment) @@ -408,7 +408,7 @@ def calc_muap(self, jitter_std: float = 0.0) -> np.ndarray: raise ValueError("_dt not set - call calc_sfaps() first") if jitter_std != 0: - delays = jitter_std * RANDOM_GENERATOR.standard_normal( + delays = jitter_std * get_random_generator().standard_normal( size=(self._number_of_muscle_fibers, 1) ) jittered_sfaps = np.zeros_like(self.sfaps) diff --git a/myogen/simulator/core/emg/surface/surface_emg.py b/myogen/simulator/core/emg/surface/surface_emg.py index 3d6084b8..e9cf7efe 100644 --- a/myogen/simulator/core/emg/surface/surface_emg.py +++ b/myogen/simulator/core/emg/surface/surface_emg.py @@ -28,7 +28,7 @@ from scipy.signal import resample from tqdm import tqdm -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator.core.emg.electrodes import SurfaceElectrodeArray from myogen.simulator.core.emg.surface.simulate_fiber import simulate_fiber_v2, _simulate_fiber_v2_python from myogen.simulator.core.muscle import Muscle @@ -267,7 +267,7 @@ def simulate_muaps(self, n_jobs: int = -2, verbose: bool = True) -> SURFACE_MUAP n_motor_units = len(number_of_fibers_per_MUs) # Pre-calculate innervation zones for all MUs - innervation_zones = RANDOM_GENERATOR.uniform( + innervation_zones = get_random_generator().uniform( low=-innervation_zone_variance / 2, high=innervation_zone_variance / 2, size=n_motor_units, @@ -338,7 +338,7 @@ def _process_single_mu( innervation_zone = innervation_zones[MU_index] # Batch generate random fiber lengths (optimization: single RNG call) - fiber_length_variations = RANDOM_GENERATOR.uniform( + fiber_length_variations = get_random_generator().uniform( low=-self._var_fiber_length__mm, high=self._var_fiber_length__mm, size=number_of_fibers, @@ -859,7 +859,7 @@ def add_noise(self, snr__dB: float, noise_type: str = "gaussian") -> SURFACE_EMG # Generate noise if noise_type.lower() == "gaussian": # Generate standard normal noise, then scale per channel - noise = RANDOM_GENERATOR.normal(loc=0.0, scale=1.0, size=emg_array.shape) + noise = get_random_generator().normal(loc=0.0, scale=1.0, size=emg_array.shape) # Broadcast noise_std_per_channel along time axis # noise shape: (time, rows, cols) # noise_std_per_channel shape: (rows, cols) diff --git a/myogen/simulator/core/muscle/muscle.py b/myogen/simulator/core/muscle/muscle.py index b821200f..3baf1785 100644 --- a/myogen/simulator/core/muscle/muscle.py +++ b/myogen/simulator/core/muscle/muscle.py @@ -12,7 +12,7 @@ from tqdm import tqdm import quantities as pq -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.utils.types import ( RECRUITMENT_THRESHOLDS__ARRAY, Quantity__S_per_m, @@ -317,7 +317,7 @@ def _generate_fiber_properties(self) -> None: std_diameter__mm = 15e-3 # mm (15 um) self._muscle_fiber_diameters__mm = ( - RANDOM_GENERATOR.lognormal(mean=np.log(mean_diameter__mm), sigma=0.3, size=n_fibers) + get_random_generator().lognormal(mean=np.log(mean_diameter__mm), sigma=0.3, size=n_fibers) * pq.mm ) @@ -334,7 +334,7 @@ def _generate_fiber_properties(self) -> None: # Add some biological variability cv_base = k * self._muscle_fiber_diameters__mm + c - cv_noise = RANDOM_GENERATOR.normal(0, 0.2, n_fibers) * pq.m / pq.s # 20% CV variation + cv_noise = get_random_generator().normal(0, 0.2, n_fibers) * pq.m / pq.s # 20% CV variation self._muscle_fiber_conduction_velocities__mm_per_s = cv_base + cv_noise @@ -717,7 +717,7 @@ def probfun(y, x): # Assignment procedure self._assignment = np.full(self._number_of_muscle_fibers, np.nan) - randomized_mf = RANDOM_GENERATOR.permutation(self._number_of_muscle_fibers) + randomized_mf = get_random_generator().permutation(self._number_of_muscle_fibers) for mf in tqdm(randomized_mf, desc="Assigning muscle fibers to motor neurons", unit="MF", disable=not verbose): # Vectorized computation of likelihoods for all motor units @@ -753,7 +753,7 @@ def probfun(y, x): probs = np.ones(self._number_of_neurons) / self._number_of_neurons # Sample from the probability distribution (equivalent to MATLAB's randsample) - self._assignment[mf] = RANDOM_GENERATOR.choice(self._number_of_neurons, p=probs) + self._assignment[mf] = get_random_generator().choice(self._number_of_neurons, p=probs) if verbose: print(f"Assignment completed. {self._number_of_muscle_fibers} muscle fibers assigned.") diff --git a/myogen/simulator/neuron/cells.py b/myogen/simulator/neuron/cells.py index 11d2117b..99c50aa3 100644 --- a/myogen/simulator/neuron/cells.py +++ b/myogen/simulator/neuron/cells.py @@ -9,7 +9,7 @@ import quantities as pq from neuron import h -from myogen import RANDOM_GENERATOR, SEED +from myogen import get_random_generator, get_random_seed from myogen.simulator.neuron._cython._gamma_process_generator import ( _GammaProcessGenerator__Cython, ) @@ -409,7 +409,7 @@ def __init__(self, N, dt, pool__ID: int | None = None): self.ns = h.DUMMY() # Dummy cell _Cell.__init__(self, next(self._ids2), pool__ID) _PoissonProcessGenerator__Cython.__init__( - self, SEED + (self.class__ID + 1) * (self.global__ID + 1), N, dt + self, get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), N, dt ) def __repr__(self) -> str: @@ -494,7 +494,7 @@ def __init__( _Cell.__init__(self, next(self._ids2), pool__ID) _GammaProcessGenerator__Cython.__init__( self, - SEED + (self.class__ID + 1) * (self.global__ID + 1), + get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), shape, timestep__ms.magnitude, ) @@ -584,12 +584,12 @@ def __init__( self.ns = h.DUMMY() # Dummy cell self.RT = RT # Recruitment Threshold - self.IFR = RANDOM_GENERATOR.normal(5, 2.5) # Individual variability + self.IFR = get_random_generator().normal(5, 2.5) # Individual variability _Cell.__init__(self, class__ID if class__ID is not None else next(self._ids2), pool__ID) _GammaProcessGenerator__Cython.__init__( self, - seed=SEED + (self.class__ID + 1) * (self.global__ID + 1), + seed=get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), shape=N, # Shape parameter controls ISI CV = 1/sqrt(N) dt=timestep__ms.magnitude, ) diff --git a/myogen/simulator/neuron/network.py b/myogen/simulator/neuron/network.py index 59afa3e1..c7a111c1 100644 --- a/myogen/simulator/neuron/network.py +++ b/myogen/simulator/neuron/network.py @@ -10,7 +10,7 @@ import quantities as pq from neuron import h -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator.neuron.populations import ( AffIa__Pool, AffII__Pool, @@ -79,10 +79,10 @@ def _select_synapse(target_neuron, inhibitory: bool = False): ] if matching_synapses: - return RANDOM_GENERATOR.choice(matching_synapses) + return get_random_generator().choice(matching_synapses) else: # Fallback to random selection if no matching synapses found - return RANDOM_GENERATOR.choice(synapse_list) + return get_random_generator().choice(synapse_list) # Helper functions for create_netcon @@ -263,7 +263,7 @@ def _connect_population_to_population( for source_neuron in populations[source_pop]: # Randomly select exactly n_connections target neurons - selected_targets = RANDOM_GENERATOR.choice( + selected_targets = get_random_generator().choice( target_neurons, size=n_connections, replace=False ) @@ -289,7 +289,7 @@ def _connect_population_to_population( # Probabilistic connectivity: each pair has probability of connecting for source_neuron in populations[source_pop]: for target_neuron in target_neurons: - if RANDOM_GENERATOR.uniform() < connection_probability: + if get_random_generator().uniform() < connection_probability: target_synapse = _select_synapse(target_neuron, inhibitory=inhibitory) netcon = _create_netcon( source_neuron, @@ -431,7 +431,7 @@ def _connect_one_to_one( connections = [] for source_neuron, target_neuron in zip(source_neurons, target_neurons): # Check if this pair should be connected - if RANDOM_GENERATOR.uniform() < connection_probability: + if get_random_generator().uniform() < connection_probability: target_synapse = _select_synapse(target_neuron, inhibitory=inhibitory) netcon = _create_netcon( source_neuron, diff --git a/pyproject.toml b/pyproject.toml index 14ac2fa4..2ab4ba0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,8 +71,12 @@ possibly-unbound-import = "ignore" dev = [ "pandas-stubs>=2.3.0.250703", "poethepoet>=0.37.0", + "pytest>=8.0", "scipy-stubs>=1.16.1.0", ] + +[tool.pytest.ini_options] +testpaths = ["tests"] docs = [ "enum-tools[sphinx]>=0.12.0", "linkify-it-py>=2.0.3", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_determinism.py b/tests/test_determinism.py new file mode 100644 index 00000000..9c6f0291 --- /dev/null +++ b/tests/test_determinism.py @@ -0,0 +1,113 @@ +"""Regression tests for the RNG architecture (R1 Code Review 1 and 2). + +These tests verify that ``set_random_seed`` propagates correctly through the +accessor pattern and that no module captures a stale reference to the global +RNG or to the deprecated module-level ``SEED`` constant. +""" + +from __future__ import annotations + +import warnings + +import numpy as np +import pytest + +import myogen +from myogen import get_random_generator, get_random_seed, set_random_seed + + +def _draw(n: int = 8) -> np.ndarray: + return get_random_generator().integers(0, 10**9, n) + + +def test_same_seed_yields_same_draws(): + set_random_seed(12345) + first = _draw() + set_random_seed(12345) + second = _draw() + assert np.array_equal(first, second) + + +def test_different_seed_yields_different_draws(): + set_random_seed(1) + first = _draw() + set_random_seed(2) + second = _draw() + assert not np.array_equal(first, second) + + +def test_get_random_seed_reflects_latest_set_call(): + set_random_seed(777) + assert get_random_seed() == 777 + set_random_seed(999) + assert get_random_seed() == 999 + + +def test_accessor_returns_current_generator_after_rebind(): + """A caller that holds ``get_random_generator`` (the function, not the generator) must see the new RNG after each ``set_random_seed`` call — this is the regression for CR1.""" + set_random_seed(100) + before = _draw() + set_random_seed(200) + after = _draw() + assert not np.array_equal(before, after) + + # Restoring the original seed must reproduce the first sequence. + set_random_seed(100) + restored = _draw() + assert np.array_equal(before, restored) + + +def test_derived_seed_pattern_respects_set_random_seed(): + """Regression for CR2: code that derives sub-seeds via ``get_random_seed() + offset`` must observe new values after ``set_random_seed``.""" + set_random_seed(10) + derived_a = get_random_seed() + 3 * 7 + set_random_seed(20) + derived_b = get_random_seed() + 3 * 7 + assert derived_a != derived_b + + +def test_deprecated_RANDOM_GENERATOR_emits_warning_and_returns_current(): + set_random_seed(42) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + gen_via_deprecated = myogen.RANDOM_GENERATOR + assert any( + issubclass(w.category, DeprecationWarning) for w in caught + ), "accessing myogen.RANDOM_GENERATOR must emit a DeprecationWarning" + assert gen_via_deprecated is get_random_generator(), ( + "deprecated attribute must return the current global generator" + ) + + # Confirm it reflects later ``set_random_seed`` calls. + set_random_seed(43) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + gen_again = myogen.RANDOM_GENERATOR + assert gen_again is get_random_generator() + assert gen_again is not gen_via_deprecated + + +def test_deprecated_SEED_emits_warning_and_returns_current_seed(): + set_random_seed(5150) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + seed_via_deprecated = myogen.SEED + assert any( + issubclass(w.category, DeprecationWarning) for w in caught + ), "accessing myogen.SEED must emit a DeprecationWarning" + assert seed_via_deprecated == 5150 + + +def test_modules_that_use_accessor_are_not_stale_after_seed_change(): + """End-to-end: reimport a module that calls ``get_random_generator()`` and confirm its RNG draws track the current seed.""" + from myogen.simulator.core.muscle import muscle as muscle_module + + # The muscle module imports ``get_random_generator`` at top level; after + # ``set_random_seed``, any call it makes to ``get_random_generator()`` must + # return the new generator. Verify by calling the function directly. + set_random_seed(11) + gen_before = muscle_module.get_random_generator() + set_random_seed(22) + gen_after = muscle_module.get_random_generator() + assert gen_before is not gen_after + assert gen_after is get_random_generator() diff --git a/uv.lock b/uv.lock index 2472378e..b51652ce 100644 --- a/uv.lock +++ b/uv.lock @@ -6,18 +6,6 @@ resolution-markers = [ "python_full_version < '3.13'", ] -[[package]] -name = "accessible-pygments" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, -] - [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -125,15 +113,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] -[[package]] -name = "alabaster" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, -] - [[package]] name = "alembic" version = "1.17.2" @@ -148,43 +127,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, ] -[[package]] -name = "apeye" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "apeye-core" }, - { name = "domdf-python-tools" }, - { name = "platformdirs" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4f/6b/cc65e31843d7bfda8313a9dc0c77a21e8580b782adca53c7cb3e511fe023/apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36", size = 99219, upload-time = "2023-08-14T15:32:41.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/7b/2d63664777b3e831ac1b1d8df5bbf0b7c8bee48e57115896080890527b1b/apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e", size = 107989, upload-time = "2023-08-14T15:32:40.064Z" }, -] - -[[package]] -name = "apeye-core" -version = "1.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "domdf-python-tools" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/4c/4f108cfd06923bd897bf992a6ecb6fb122646ee7af94d7f9a64abd071d4c/apeye_core-1.1.5.tar.gz", hash = "sha256:5de72ed3d00cc9b20fea55e54b7ab8f5ef8500eb33a5368bc162a5585e238a55", size = 96511, upload-time = "2024-01-30T17:45:48.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/9f/fa9971d2a0c6fef64c87ba362a493a4f230eff4ea8dfb9f4c7cbdf71892e/apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf", size = 99286, upload-time = "2024-01-30T17:45:46.764Z" }, -] - -[[package]] -name = "appdirs" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, -] - [[package]] name = "asciitree" version = "0.3.3" @@ -200,27 +142,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] -[[package]] -name = "autodocsumm" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/03/96/92afe8a7912b327c01f0a8b6408c9556ee13b1aba5b98d587ac7327ff32d/autodocsumm-0.2.14.tar.gz", hash = "sha256:2839a9d4facc3c4eccd306c08695540911042b46eeafcdc3203e6d0bab40bc77", size = 46357, upload-time = "2024-10-23T18:51:47.369Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/bc/3f66af9beb683728e06ca08797e4e9d3e44f432f339718cae3ba856a9cad/autodocsumm-0.2.14-py3-none-any.whl", hash = "sha256:3bad8717fc5190802c60392a7ab04b9f3c97aa9efa8b3780b3d81d615bfe5dc0", size = 14640, upload-time = "2024-10-23T18:51:45.115Z" }, -] - -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, -] - [[package]] name = "beartype" version = "0.22.9" @@ -230,19 +151,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] -[[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, -] - [[package]] name = "bleach" version = "6.3.0" @@ -276,24 +184,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl", hash = "sha256:89a66cb8bfe85e91bce144e3ccf3c4a6f0f1347e7006282972568ea0ecacbb00", size = 7206137, upload-time = "2025-11-07T20:50:58.108Z" }, ] -[[package]] -name = "cachecontrol" -version = "0.14.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msgpack" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, -] - -[package.optional-dependencies] -filecache = [ - { name = "filelock" }, -] - [[package]] name = "certifi" version = "2025.11.12" @@ -468,18 +358,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, ] -[[package]] -name = "cssutils" -version = "2.11.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "more-itertools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/9f/329d26121fe165be44b1dfff21aa0dc348f04633931f1d20ed6cf448a236/cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2", size = 711657, upload-time = "2024-06-04T15:51:39.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747, upload-time = "2024-06-04T15:51:37.499Z" }, -] - [[package]] name = "cycler" version = "0.12.1" @@ -531,41 +409,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, ] -[[package]] -name = "dict2css" -version = "0.3.0.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cssutils" }, - { name = "domdf-python-tools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/24/eb/776eef1f1aa0188c0fc165c3a60b71027539f71f2eedc43ad21b060e9c39/dict2css-0.3.0.post1.tar.gz", hash = "sha256:89c544c21c4ca7472c3fffb9d37d3d926f606329afdb751dc1de67a411b70719", size = 7845, upload-time = "2023-11-22T11:09:20.958Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/47/290daabcf91628f4fc0e17c75a1690b354ba067066cd14407712600e609f/dict2css-0.3.0.post1-py3-none-any.whl", hash = "sha256:f006a6b774c3e31869015122ae82c491fd25e7de4a75607a62aa3e798f837e0d", size = 25647, upload-time = "2023-11-22T11:09:19.221Z" }, -] - -[[package]] -name = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, -] - -[[package]] -name = "domdf-python-tools" -version = "3.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "natsort" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/8b/ab2d8a292bba8fe3135cacc8bfd3576710a14b8f2d0a8cde19130d5c9d21/domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298", size = 100458, upload-time = "2025-02-12T17:34:05.747Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/11/208f72084084d3f6a2ed5ebfdfc846692c3f7ad6dce65e400194924f7eed/domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36", size = 126860, upload-time = "2025-02-12T17:34:04.093Z" }, -] - [[package]] name = "elephant" version = "1.1.1" @@ -585,26 +428,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/47/b775cf445a9119a782744326036690b4925162cb71b1d4599a9fa64941da/elephant-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c0f5ee07ee06e34ba6474cc579d6cd378fb5bd7acf52985e3bea9b6d3ea3910", size = 594743, upload-time = "2024-10-28T15:33:03.381Z" }, ] -[[package]] -name = "enum-tools" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/87/50091e20c2765aa495b24521844a7d8f7041d48e4f9b47dd928cd38c8606/enum_tools-0.13.0.tar.gz", hash = "sha256:0d13335e361d300dc0f8fd82c8cf9951417246f9676144f5ee1761eb690228eb", size = 18904, upload-time = "2025-04-17T15:26:59.412Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/45/cf8a8df3ebe78db691ab54525d552085b67658877f0334f4b0c08c43b518/enum_tools-0.13.0-py3-none-any.whl", hash = "sha256:e0112b16767dd08cb94105844b52770eae67ece6f026916a06db4a3d330d2a95", size = 22366, upload-time = "2025-04-17T15:26:58.34Z" }, -] - -[package.optional-dependencies] -sphinx = [ - { name = "sphinx" }, - { name = "sphinx-jinja2-compat" }, - { name = "sphinx-toolbox" }, -] - [[package]] name = "fasteners" version = "0.20" @@ -614,15 +437,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, ] -[[package]] -name = "filelock" -version = "3.20.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, -] - [[package]] name = "find-libpython" version = "0.5.0" @@ -780,7 +594,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, @@ -788,7 +601,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, @@ -796,7 +608,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, @@ -804,7 +615,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, @@ -899,19 +709,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/ab/a6aa43d45ceb88adc0e8c1358fa6935c6e6a5895537431dec67524ca2ccd/holoviews-1.22.1-py3-none-any.whl", hash = "sha256:6f4f0656336035cde1d8103ac6461d7c8ac9a60c4a6d883785cc81f2cc5b8702", size = 5946246, upload-time = "2025-12-05T14:54:19.191Z" }, ] -[[package]] -name = "html5lib" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -922,20 +719,20 @@ wheels = [ ] [[package]] -name = "imagesize" -version = "1.4.1" +name = "impi-rt" +version = "2021.17.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/31b67dfecd27656045900ad63292d62a6679bdeb2fdf9b6d16dcd6f90e67/impi_rt-2021.17.1-py2.py3-none-win_amd64.whl", hash = "sha256:614fdd114caf4323eb9d7b5e68c7101b8af1e0d8fb4e546fd0c2a4787f142f93", size = 17439430, upload-time = "2025-12-04T09:51:31.144Z" }, ] [[package]] -name = "impi-rt" -version = "2021.17.1" +name = "iniconfig" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a8/31b67dfecd27656045900ad63292d62a6679bdeb2fdf9b6d16dcd6f90e67/impi_rt-2021.17.1-py2.py3-none-win_amd64.whl", hash = "sha256:614fdd114caf4323eb9d7b5e68c7101b8af1e0d8fb4e546fd0c2a4787f142f93", size = 17439430, upload-time = "2025-12-04T09:51:31.144Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -1270,27 +1067,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "memory-profiler" -version = "0.61.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "psutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" }, -] - -[[package]] -name = "more-itertools" -version = "10.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, -] - [[package]] name = "mpi4py" version = "4.1.1" @@ -1329,50 +1105,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/ca/7e27edf78cd8ba68aacafc836004cd092a978f0d5ffc8a3eac9e904a3e0e/mpi4py-4.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:b4b3813da9a7a1fc37ffb8dad314cb396313a40cd3fe150854ab29e999a9eb8c", size = 1771707, upload-time = "2025-10-10T13:54:51.756Z" }, ] -[[package]] -name = "msgpack" -version = "1.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, - { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, - { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, - { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, - { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, - { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, - { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, - { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, - { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, - { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, - { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, - { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, - { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, - { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, - { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, - { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, -] - [[package]] name = "multidict" version = "6.7.0" @@ -1474,7 +1206,7 @@ wheels = [ [[package]] name = "myogen" -version = "0.8.3" +version = "0.8.5" source = { editable = "." } dependencies = [ { name = "beartype" }, @@ -1508,26 +1240,9 @@ nwb = [ dev = [ { name = "pandas-stubs" }, { name = "poethepoet" }, + { name = "pytest" }, { name = "scipy-stubs" }, ] -docs = [ - { name = "enum-tools", extra = ["sphinx"] }, - { name = "linkify-it-py" }, - { name = "memory-profiler" }, - { name = "nwbinspector" }, - { name = "pydata-sphinx-theme" }, - { name = "pygments" }, - { name = "pynwb" }, - { name = "rinohtype" }, - { name = "roman" }, - { name = "sphinx" }, - { name = "sphinx-autodoc-typehints" }, - { name = "sphinx-design" }, - { name = "sphinx-gallery" }, - { name = "sphinx-hoverxref" }, - { name = "sphinxcontrib-mermaid" }, - { name = "toml" }, -] [package.metadata] requires-dist = [ @@ -1559,43 +1274,9 @@ provides-extras = ["nwb"] dev = [ { name = "pandas-stubs", specifier = ">=2.3.0.250703" }, { name = "poethepoet", specifier = ">=0.37.0" }, + { name = "pytest", specifier = ">=8.0" }, { name = "scipy-stubs", specifier = ">=1.16.1.0" }, ] -docs = [ - { name = "enum-tools", extras = ["sphinx"], specifier = ">=0.12.0" }, - { name = "linkify-it-py", specifier = ">=2.0.3" }, - { name = "memory-profiler", specifier = ">=0.61.0" }, - { name = "nwbinspector", specifier = ">=0.5.0" }, - { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, - { name = "pygments", specifier = ">=2.19.2" }, - { name = "pynwb", specifier = ">=2.8.0" }, - { name = "rinohtype", specifier = ">=0.5.5" }, - { name = "roman", specifier = ">=5.2" }, - { name = "sphinx", specifier = ">=8.1.3" }, - { name = "sphinx-autodoc-typehints", specifier = ">=2.5.0" }, - { name = "sphinx-design", specifier = ">=0.6.1" }, - { name = "sphinx-gallery", specifier = ">=0.19.0" }, - { name = "sphinx-hoverxref", specifier = ">=1.4.1" }, - { name = "sphinxcontrib-mermaid", specifier = ">=1.0.0" }, - { name = "toml", specifier = ">=0.10.2" }, -] - -[[package]] -name = "myst-parser" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "pyyaml" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, -] [[package]] name = "narwhals" @@ -1978,6 +1659,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "poethepoet" version = "0.39.0" @@ -2075,52 +1765,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -[[package]] -name = "psutil" -version = "7.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d", size = 129624, upload-time = "2025-12-29T08:26:04.255Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49", size = 130132, upload-time = "2025-12-29T08:26:06.228Z" }, - { url = "https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc", size = 180612, upload-time = "2025-12-29T08:26:08.276Z" }, - { url = "https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf", size = 183201, upload-time = "2025-12-29T08:26:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f", size = 139081, upload-time = "2025-12-29T08:26:12.483Z" }, - { url = "https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672", size = 134767, upload-time = "2025-12-29T08:26:14.528Z" }, - { url = "https://files.pythonhosted.org/packages/05/c2/5fb764bd61e40e1fe756a44bd4c21827228394c17414ade348e28f83cd79/psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679", size = 129716, upload-time = "2025-12-29T08:26:16.017Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d2/935039c20e06f615d9ca6ca0ab756cf8408a19d298ffaa08666bc18dc805/psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f", size = 130133, upload-time = "2025-12-29T08:26:18.009Z" }, - { url = "https://files.pythonhosted.org/packages/77/69/19f1eb0e01d24c2b3eacbc2f78d3b5add8a89bf0bb69465bc8d563cc33de/psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129", size = 181518, upload-time = "2025-12-29T08:26:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/6d/7e18b1b4fa13ad370787626c95887b027656ad4829c156bb6569d02f3262/psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a", size = 184348, upload-time = "2025-12-29T08:26:22.215Z" }, - { url = "https://files.pythonhosted.org/packages/98/60/1672114392dd879586d60dd97896325df47d9a130ac7401318005aab28ec/psutil-7.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79", size = 140400, upload-time = "2025-12-29T08:26:23.993Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7b/d0e9d4513c46e46897b46bcfc410d51fc65735837ea57a25170f298326e6/psutil-7.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266", size = 135430, upload-time = "2025-12-29T08:26:25.999Z" }, - { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, - { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, -] - -[[package]] -name = "pydata-sphinx-theme" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -2156,6 +1800,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, ] +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2276,103 +1936,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] -[[package]] -name = "rinoh-typeface-dejavuserif" -version = "0.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rinohtype" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/d1/e221568b41d6205356ffa6340dcee02acf86d92dec5e5b04798c141b3472/rinoh-typeface-dejavuserif-0.1.3.tar.gz", hash = "sha256:8be129230ac98ab2ebfbf5b570575052ba7069e4087ce36a2d4c1d85182833ce", size = 1666226, upload-time = "2020-12-07T15:22:02.19Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a7/c3df4e93137f390d28849ee1fd7578adee2b6322dca33082c7173ac2135f/rinoh_typeface_dejavuserif-0.1.3-py3-none-any.whl", hash = "sha256:35ba67bf25e526b4b8180dc31d8fa2ff9b594d924e8790e8074699cd2f0e7da8", size = 1667463, upload-time = "2020-12-07T15:21:58.999Z" }, -] - -[[package]] -name = "rinoh-typeface-texgyrecursor" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rinohtype" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/70/b0a661118e7a93d1abf4375543644b054bb74b258924d62dd898b9860a71/rinoh-typeface-texgyrecursor-0.1.1.tar.gz", hash = "sha256:076f7dbbd0201b0d44f4c77e4f7070474e1365152dc8509b5cded04c1648308a", size = 239925, upload-time = "2016-07-18T13:31:16.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/21/1b46f0a50dfea435f90f29595d2e0b3d3c15a31b35bdde725942c83f2644/rinoh_typeface_texgyrecursor-0.1.1-py3-none-any.whl", hash = "sha256:98080e6af2271e67cff1d69033cb6c3a6baa58de41a66dce1a1102b4dd41de72", size = 242415, upload-time = "2016-07-18T13:31:20.398Z" }, -] - -[[package]] -name = "rinoh-typeface-texgyreheros" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rinohtype" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/62/3b28c30558fd53590797c455bb46ab47599c2b9145a09e7ae162dd625426/rinoh-typeface-texgyreheros-0.1.1.tar.gz", hash = "sha256:fd7082e917bccc292894447f11d15a3efce9f1386056118b57b66cc2f5a36bbd", size = 522029, upload-time = "2016-07-18T13:32:49.1Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/0d/b428d4cf5ad3bd6848b617570d5dcb061b3f66a68b72e96bf2a7909a2300/rinoh_typeface_texgyreheros-0.1.1-py3-none-any.whl", hash = "sha256:0c921a040a84b0af031e4a36d196d46b65fb18929d88df5ed26533b57100e6c9", size = 523725, upload-time = "2016-07-18T13:32:53.338Z" }, -] - -[[package]] -name = "rinoh-typeface-texgyrepagella" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rinohtype" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/2a/86db888c244f11bb0f6fed72c75b09dba9dbf8aa72e7b69c27cf7975340f/rinoh-typeface-texgyrepagella-0.1.1.tar.gz", hash = "sha256:53f4dba338c6b1df758888f23ce1ed728e5be45746f161488ad3b944e5e79fd2", size = 319023, upload-time = "2016-07-18T13:33:36.348Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/44/c27695119e6ad918358d44a87f8543cab6240f1f768068dd32ba1b65d92e/rinoh_typeface_texgyrepagella-0.1.1-py3-none-any.whl", hash = "sha256:0144e3b828a31b405ab9be1dec67f48be360d9f86d109578924fd1d7e0e1ded6", size = 321426, upload-time = "2016-07-18T13:33:39.855Z" }, -] - -[[package]] -name = "rinohtype" -version = "0.5.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appdirs" }, - { name = "docutils" }, - { name = "myst-parser" }, - { name = "packaging" }, - { name = "rinoh-typeface-dejavuserif" }, - { name = "rinoh-typeface-texgyrecursor" }, - { name = "rinoh-typeface-texgyreheros" }, - { name = "rinoh-typeface-texgyrepagella" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/21/7e2e32b16b5625a651b4f6773e1fe6ae10449f8c1f84b6731797a722a6a4/rinohtype-0.5.5.tar.gz", hash = "sha256:2efc38b5f41541693617e5a4bc6826e8932897d3c14b1b0ecd3ae4aaff404224", size = 6452152, upload-time = "2024-07-13T14:58:43.594Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/06/b9a641899db78cbd38c2d57d1878a499546336b6b844e79021b5f362fa54/rinohtype-0.5.5-py3-none-any.whl", hash = "sha256:b1c429e69c55b1b2400367ac0fcc17f30ec537ceb22e8f2dd768909282c2e642", size = 611480, upload-time = "2024-07-13T14:58:41.574Z" }, -] - -[[package]] -name = "roman" -version = "5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/7c/3901b35ed856329bf98e84da8e5e0b4d899ea0027eee222f1be42a24ff3f/roman-5.2.tar.gz", hash = "sha256:275fe9f46290f7d0ffaea1c33251b92b8e463ace23660508ceef522e7587cb6f", size = 8185, upload-time = "2025-11-11T08:03:57.025Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/14/ea3cdd7276fcd731a9003fe4abeb6b395a38110ddff6a6a509f4ee00f741/roman-5.2-py3-none-any.whl", hash = "sha256:89d3b47400388806d06ff77ea77c79ab080bc127820dea6bf34e1f1c1b8e676e", size = 6041, upload-time = "2025-11-11T08:03:56.051Z" }, -] - -[[package]] -name = "roman-numerals" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, -] - -[[package]] -name = "roman-numerals-py" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "roman-numerals" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, -] - [[package]] name = "rpds-py" version = "0.30.0" @@ -2675,257 +2238,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "snowballstemmer" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, -] - -[[package]] -name = "soupsieve" -version = "2.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals-py" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, -] - -[[package]] -name = "sphinx-autodoc-typehints" -version = "3.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/4f/4fd5583678bb7dc8afa69e9b309e6a99ee8d79ad3a4728f4e52fd7cb37c7/sphinx_autodoc_typehints-3.5.2.tar.gz", hash = "sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb", size = 37839, upload-time = "2025-10-16T00:50:15.743Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl", hash = "sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c", size = 21184, upload-time = "2025-10-16T00:50:13.973Z" }, -] - -[[package]] -name = "sphinx-design" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, -] - -[[package]] -name = "sphinx-gallery" -version = "0.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pillow" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/14/9238ac61932299b38c20c7c37dbfe60348c0348ea4d400f9ef25875b3bf7/sphinx_gallery-0.20.0.tar.gz", hash = "sha256:70281510c6183d812d3595957005ccf555c5a793f207410f6cd16a25bf08d735", size = 473502, upload-time = "2025-12-02T15:51:37.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/fd/818a53d4da56ef2da7b08f77bb3a825635941d1fcc6b6a490995dec1a81c/sphinx_gallery-0.20.0-py3-none-any.whl", hash = "sha256:188b7456e269649945825661b76cdbfbf0b70c2cfd5b75c9a11fe52519879e4d", size = 458655, upload-time = "2025-12-02T15:51:35.311Z" }, -] - -[[package]] -name = "sphinx-hoverxref" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, - { name = "sphinxcontrib-jquery" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/6d/89764d3c719ed172d94778a74661cca647b2475165ae6cde1f73c9524d63/sphinx_hoverxref-1.4.2.tar.gz", hash = "sha256:74fab961b1b8c0e9c2cf22fa195c0e783d635e78686d39dd06ff157c196ca0c9", size = 1715764, upload-time = "2024-11-18T18:01:34.965Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/3b/d981175d7b9bbbcf6894faf640d15198394efc7dedd6182332c4a791a5a6/sphinx_hoverxref-1.4.2-py2.py3-none-any.whl", hash = "sha256:4fc2e283e908d9df61ea9196589934944a7e2e6e3cb753a420b938cd6d14e220", size = 32198, upload-time = "2024-11-18T18:01:31.473Z" }, -] - -[[package]] -name = "sphinx-jinja2-compat" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "markupsafe" }, - { name = "standard-imghdr", marker = "python_full_version >= '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/98/43313781f29e8c6c46fec907430310172d6f207e95e4fbea9289990fbbfe/sphinx_jinja2_compat-0.4.1.tar.gz", hash = "sha256:0188f0802d42c3da72997533b55a00815659a78d3f81d4b4747b1fb15a5728e6", size = 5222, upload-time = "2025-08-06T20:06:25.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/c8/4fd58c1000d7f8f5572c507f4550d2e2d9741e500c68eb2e3da17cbe5a85/sphinx_jinja2_compat-0.4.1-py3-none-any.whl", hash = "sha256:64ca0d46f0d8029fbe69ea612793a55e6ef0113e1bba4a85d402158c09f17a14", size = 8123, upload-time = "2025-08-06T20:06:24.947Z" }, -] - -[[package]] -name = "sphinx-prompt" -version = "1.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "docutils" }, - { name = "idna" }, - { name = "jinja2" }, - { name = "pygments" }, - { name = "requests" }, - { name = "sphinx" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/a3/91293c0e0f0b76d0697ba7a41541929ca3f5457671d008bd84a9bde17e21/sphinx_prompt-1.10.2.tar.gz", hash = "sha256:47b592ba75caebd044b0eddf7a5a1b6e0aef6df587b034377cd101a999b686ba", size = 5566, upload-time = "2025-11-28T09:23:18.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/f4/44ce4d0179fb4e9cfe181a8aa281bba23e40158a609fb3680774529acaaa/sphinx_prompt-1.10.2-py3-none-any.whl", hash = "sha256:6594337962c4b1498602e6984634bed4a0dc7955852e3cfc255eb0af766ed859", size = 7474, upload-time = "2025-11-28T09:23:17.154Z" }, -] - -[[package]] -name = "sphinx-tabs" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/32/ab475e252dc2b704e82a91141fa404cdd8901a5cf34958fd22afacebfccd/sphinx-tabs-3.4.5.tar.gz", hash = "sha256:ba9d0c1e3e37aaadd4b5678449eb08176770e0fc227e769b6ce747df3ceea531", size = 16070, upload-time = "2024-01-21T12:13:39.392Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/9f/4ac7dbb9f23a2ff5a10903a4f9e9f43e0ff051f63a313e989c962526e305/sphinx_tabs-3.4.5-py3-none-any.whl", hash = "sha256:92cc9473e2ecf1828ca3f6617d0efc0aa8acb06b08c56ba29d1413f2f0f6cf09", size = 9904, upload-time = "2024-01-21T12:13:37.67Z" }, -] - -[[package]] -name = "sphinx-toolbox" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "apeye" }, - { name = "autodocsumm" }, - { name = "beautifulsoup4" }, - { name = "cachecontrol", extra = ["filecache"] }, - { name = "dict2css" }, - { name = "docutils" }, - { name = "domdf-python-tools" }, - { name = "filelock" }, - { name = "html5lib" }, - { name = "roman" }, - { name = "ruamel-yaml" }, - { name = "sphinx" }, - { name = "sphinx-autodoc-typehints" }, - { name = "sphinx-jinja2-compat" }, - { name = "sphinx-prompt" }, - { name = "sphinx-tabs" }, - { name = "tabulate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/57/d9f2cfa638864eac5565c263e994d117d577898a9eca91800f806a225b71/sphinx_toolbox-4.1.0.tar.gz", hash = "sha256:5da890f4bb0cacea4f1cf6cef182c5be480340d0ead43c905f51f7e5aacfc19c", size = 113632, upload-time = "2025-12-05T23:23:53.23Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/6e/e6a9f56972c971eb1556b604c82bb957c2286c68d50f39f64a7526907216/sphinx_toolbox-4.1.0-py3-none-any.whl", hash = "sha256:9024a7482b92ecf4572f83940c87ae26c2eca3ca49ff3df5f59806e88da958f6", size = 196115, upload-time = "2025-12-05T23:23:51.713Z" }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, -] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, -] - -[[package]] -name = "sphinxcontrib-mermaid" -version = "1.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/49/c6ddfe709a4ab76ac6e5a00e696f73626b2c189dc1e1965a361ec102e6cc/sphinxcontrib_mermaid-1.2.3.tar.gz", hash = "sha256:358699d0ec924ef679b41873d9edd97d0773446daf9760c75e18dc0adfd91371", size = 18885, upload-time = "2025-11-26T04:18:32.43Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/39/8b54299ffa00e597d3b0b4d042241a0a0b22cb429ad007ccfb9c1745b4d1/sphinxcontrib_mermaid-1.2.3-py3-none-any.whl", hash = "sha256:5be782b27026bef97bfb15ccb2f7868b674a1afc0982b54cb149702cfc25aa02", size = 13413, upload-time = "2025-11-26T04:18:31.269Z" }, -] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, -] - [[package]] name = "sqlalchemy" version = "2.0.45" @@ -2961,24 +2273,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, ] -[[package]] -name = "standard-imghdr" -version = "3.10.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/d2/2eb5521072c9598886035c65c023f39f7384bcb73eed70794f469e34efac/standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52", size = 5474, upload-time = "2024-04-21T18:55:10.859Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/d0/9852f70eb01f814843530c053542b72d30e9fbf74da7abb0107e71938389/standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2", size = 5598, upload-time = "2024-04-21T18:54:48.587Z" }, -] - -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "threadpoolctl" version = "3.6.0" @@ -2988,15 +2282,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, ] -[[package]] -name = "toml" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, -] - [[package]] name = "tornado" version = "6.5.4" From c4e8d823566c5b0398976a2968de3feda67a87fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:34:06 +0200 Subject: [PATCH 02/17] Address Codex review of RNG refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the previous commit. Codex review flagged: 1) MEDIUM: cells.py seed derivation `get_random_seed() + (class_id+1)*(global_id+1)` is not injective — e.g. (0, 5) and (1, 2) both add 6. Added a collision-free `derive_subseed(*labels)` helper in myogen/__init__.py built on `numpy.random.SeedSequence`. All three cells.py sites and the motor_unit_sim.py KMeans `random_state` now use this helper. 2) MEDIUM: examples/02_finetune/03_optimize_dd_for_target_force.py and examples/03_papers/watanabe/02_optimize_oscillating_dc.py imported RANDOM_GENERATOR *and* later called set_random_seed(42) — the exact stale-reference bug leaking into examples. Migrated both to get_random_generator(). 3) LOW: migrated the remaining six example scripts and their narrative docstring blocks from RANDOM_GENERATOR / SEED to get_random_generator(), so examples no longer teach the deprecated API or emit DeprecationWarnings at import. 4) LOW: added two regression tests: - test_derive_subseed_is_collision_free_for_distinct_labels — verifies the 8x8 sub-seed grid is injective, catching any future regression to a non-injective mixing function. - test_derive_subseed_tracks_set_random_seed — verifies derived sub-seeds change with the global seed. --- ...simulate_spike_trains_current_injection.py | 20 ++++++------- ..._simulate_spike_trains_descending_drive.py | 16 +++++------ .../01_optimize_dd_for_target_firing_rate.py | 4 +-- .../02_compute_force_from_optimized_dd.py | 4 +-- .../03_optimize_dd_for_target_force.py | 4 +-- .../watanabe/01_compute_baseline_force.py | 4 +-- .../watanabe/02_optimize_oscillating_dc.py | 4 +-- .../watanabe/03_10pct_mvc_simulation.py | 4 +-- myogen/__init__.py | 28 +++++++++++++++++++ .../core/emg/intramuscular/motor_unit_sim.py | 4 +-- myogen/simulator/neuron/cells.py | 8 +++--- tests/test_determinism.py | 23 +++++++++++++++ 12 files changed, 87 insertions(+), 36 deletions(-) diff --git a/examples/01_basic/02_simulate_spike_trains_current_injection.py b/examples/01_basic/02_simulate_spike_trains_current_injection.py index a03e0dd8..32560daa 100644 --- a/examples/01_basic/02_simulate_spike_trains_current_injection.py +++ b/examples/01_basic/02_simulate_spike_trains_current_injection.py @@ -20,17 +20,17 @@ # ---------------- # # .. important:: -# In **MyoGen** all **random number generation** is handled by the :data:`~myogen.RANDOM_GENERATOR` object. +# In **MyoGen** all **random number generation** is handled by the RNG returned from +# :func:`~myogen.get_random_generator`, a thin wrapper around :mod:`numpy.random`. # -# This object is a wrapper around the :mod:`numpy.random` module and is used to generate random numbers. -# -# It is intended to be used with the following API: +# Always fetch the generator at the call site so the current seed is honored: # # .. code-block:: python # -# from myogen import simulator, RANDOM_GENERATOR +# from myogen import simulator, get_random_generator +# get_random_generator().normal(0, 1) # -# To change the default seed, use :func:`~myogen.set_random_seed`: +# To change the seed, use :func:`~myogen.set_random_seed`: # # .. code-block:: python # @@ -50,7 +50,7 @@ from neuron import h from viziphant.rasterplot import rasterplot_rates -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator.neuron.populations import AlphaMN__Pool from myogen.utils.currents import create_trapezoid_current from myogen.utils.neuron.inject_currents_into_populations import ( @@ -107,9 +107,9 @@ timestep = 0.05 * pq.ms simulation_time = 4000 * pq.ms -rise_time_ms = list(RANDOM_GENERATOR.uniform(100, 500, size=n_pools)) * pq.ms -plateau_time_ms = list(RANDOM_GENERATOR.uniform(1000, 2000, size=n_pools)) * pq.ms -fall_time_ms = list(RANDOM_GENERATOR.uniform(1000, 2000, size=n_pools)) * pq.ms +rise_time_ms = list(get_random_generator().uniform(100, 500, size=n_pools)) * pq.ms +plateau_time_ms = list(get_random_generator().uniform(1000, 2000, size=n_pools)) * pq.ms +fall_time_ms = list(get_random_generator().uniform(1000, 2000, size=n_pools)) * pq.ms input_current__AnalogSignal = create_trapezoid_current( n_pools, diff --git a/examples/01_basic/03_simulate_spike_trains_descending_drive.py b/examples/01_basic/03_simulate_spike_trains_descending_drive.py index 49b0a6a3..5ab2a9a4 100644 --- a/examples/01_basic/03_simulate_spike_trains_descending_drive.py +++ b/examples/01_basic/03_simulate_spike_trains_descending_drive.py @@ -28,17 +28,17 @@ # ---------------- # # .. important:: -# In **MyoGen** all **random number generation** is handled by the ``RANDOM_GENERATOR`` object. +# In **MyoGen** all **random number generation** is handled by the RNG returned from +# ``get_random_generator()``, a thin wrapper around ``numpy.random``. # -# This object is a wrapper around the ``numpy.random`` module and is used to generate random numbers. -# -# It is intended to be used with the following API: +# Always fetch the generator at the call site so the current seed is honored: # # .. code-block:: python # -# from myogen import simulator, RANDOM_GENERATOR +# from myogen import simulator, get_random_generator +# get_random_generator().normal(0, 1) # -# To change the default seed, use ``set_random_seed``: +# To change the seed, use ``set_random_seed``: # # .. code-block:: python # @@ -59,7 +59,7 @@ from neuron import h from tqdm import tqdm -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator.neuron import Network from myogen.simulator.neuron.populations import AlphaMN__Pool, DescendingDrive__Pool from myogen.utils.nmodl import load_nmodl_mechanisms @@ -161,7 +161,7 @@ # Add small noise for realism trapezoid_drive = ( - trapezoid_drive + np.clip(RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None) * pps + trapezoid_drive + np.clip(get_random_generator().normal(0, 1.0, size=time_points), 0, None) * pps ) # Create AnalogSignal diff --git a/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py b/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py index 2a9ffbbe..5f73efb8 100644 --- a/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py +++ b/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py @@ -56,7 +56,7 @@ from neo import Segment, SpikeTrain from neuron import h -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator import RecruitmentThresholds from myogen.simulator.neuron import Network from myogen.simulator.neuron.populations import AlphaMN__Pool, DescendingDrive__Pool @@ -224,7 +224,7 @@ def objective(trial): # Generate constant drive signal with small noise time_points = int(SIMULATION_TIME_MS / TIMESTEP_MS) drive_signal = np.ones(time_points) * dd_drive__Hz + np.clip( - RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None + get_random_generator().normal(0, 1.0, size=time_points), 0, None ) # Run NEURON simulation diff --git a/examples/02_finetune/02_compute_force_from_optimized_dd.py b/examples/02_finetune/02_compute_force_from_optimized_dd.py index 254878ba..5ffe0a9e 100644 --- a/examples/02_finetune/02_compute_force_from_optimized_dd.py +++ b/examples/02_finetune/02_compute_force_from_optimized_dd.py @@ -58,7 +58,7 @@ from neo import Block, Segment, SpikeTrain from neuron import h -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator import RecruitmentThresholds from myogen.simulator.core.force.force_model import ForceModel from myogen.simulator.neuron import Network @@ -215,7 +215,7 @@ time_points = int(SIMULATION_TIME_MS / TIMESTEP_MS) drive_signal = np.ones(time_points) * dd_drive__Hz + np.clip( - RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None + get_random_generator().normal(0, 1.0, size=time_points), 0, None ) ############################################################################## diff --git a/examples/02_finetune/03_optimize_dd_for_target_force.py b/examples/02_finetune/03_optimize_dd_for_target_force.py index e53e96ea..5b57273c 100644 --- a/examples/02_finetune/03_optimize_dd_for_target_force.py +++ b/examples/02_finetune/03_optimize_dd_for_target_force.py @@ -50,7 +50,7 @@ from neo import Block, Segment, SpikeTrain from neuron import h -from myogen import RANDOM_GENERATOR, set_random_seed +from myogen import get_random_generator, set_random_seed from myogen.simulator import RecruitmentThresholds from myogen.simulator.core.force.force_model import ForceModel from myogen.simulator.neuron import Network @@ -212,7 +212,7 @@ def run_simulation_and_compute_force(dd_drive__Hz, gamma_shape, recruitment_thre # Create constant drive signal time_points = int(SIMULATION_TIME_MS / TIMESTEP_MS) drive_signal = np.ones(time_points) * dd_drive__Hz + np.clip( - RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None + get_random_generator().normal(0, 1.0, size=time_points), 0, None ) # Initialize simulation diff --git a/examples/03_papers/watanabe/01_compute_baseline_force.py b/examples/03_papers/watanabe/01_compute_baseline_force.py index 4fadb4da..9a6bbe33 100644 --- a/examples/03_papers/watanabe/01_compute_baseline_force.py +++ b/examples/03_papers/watanabe/01_compute_baseline_force.py @@ -68,7 +68,7 @@ from neo import Block, Segment, SpikeTrain from neuron import h -from myogen import RANDOM_GENERATOR +from myogen import get_random_generator from myogen.simulator import RecruitmentThresholds from myogen.simulator.core.force.force_model import ForceModel from myogen.simulator import Network @@ -231,7 +231,7 @@ time_points = int(SIMULATION_TIME_MS / TIMESTEP_MS) drive_signal = np.ones(time_points) * DD_DRIVE_HZ + np.clip( - RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None + get_random_generator().normal(0, 1.0, size=time_points), 0, None ) ############################################################################## diff --git a/examples/03_papers/watanabe/02_optimize_oscillating_dc.py b/examples/03_papers/watanabe/02_optimize_oscillating_dc.py index b58be0fa..6197a600 100644 --- a/examples/03_papers/watanabe/02_optimize_oscillating_dc.py +++ b/examples/03_papers/watanabe/02_optimize_oscillating_dc.py @@ -72,7 +72,7 @@ from neo import Block, Segment, SpikeTrain from neuron import h -from myogen import RANDOM_GENERATOR, set_random_seed +from myogen import get_random_generator, set_random_seed from myogen.simulator import RecruitmentThresholds from myogen.simulator.core.force.force_model import ForceModel from myogen.simulator.neuron import Network @@ -226,7 +226,7 @@ def run_simulation_with_oscillating_drive(dc_offset, recruitment_thresholds): drive_signal = np.clip(drive_signal, 0, None) # Add small noise - drive_signal += np.clip(RANDOM_GENERATOR.normal(0, 1.0, size=time_points), 0, None) + drive_signal += np.clip(get_random_generator().normal(0, 1.0, size=time_points), 0, None) # Initialize simulation h.load_file("stdrun.hoc") diff --git a/examples/03_papers/watanabe/03_10pct_mvc_simulation.py b/examples/03_papers/watanabe/03_10pct_mvc_simulation.py index 7348ec6c..534bd9e5 100644 --- a/examples/03_papers/watanabe/03_10pct_mvc_simulation.py +++ b/examples/03_papers/watanabe/03_10pct_mvc_simulation.py @@ -77,7 +77,7 @@ import quantities as pq from neuron import h -from myogen import RANDOM_GENERATOR, load_nmodl_mechanisms +from myogen import get_random_generator, load_nmodl_mechanisms from myogen.simulator import RecruitmentThresholds from myogen.simulator.neuron.network import Network from myogen.simulator.neuron.populations import AlphaMN__Pool, DescendingDrive__Pool @@ -204,7 +204,7 @@ DDdrive[phase3_mask] = DC_OFFSET_OPTIMIZED + 20 * np.sin(2 * np.pi * 20 * time_s[phase3_mask]) # Independent noise (IN) - 125 Hz constant (Watanabe specification) -INdrive = 125.0 + RANDOM_GENERATOR.normal(0, 5.0, len(time)) +INdrive = 125.0 + get_random_generator().normal(0, 5.0, len(time)) plt.figure(figsize=(12, 6)) plt.subplot(2, 1, 1) diff --git a/myogen/__init__.py b/myogen/__init__.py index 963e6617..7608260d 100644 --- a/myogen/__init__.py +++ b/myogen/__init__.py @@ -24,6 +24,33 @@ def get_random_seed() -> int: return _current_seed +def derive_subseed(*labels: int) -> int: + """ + Derive a collision-free deterministic sub-seed from the current seed and a tuple of integer labels. + + Use this to seed non-NumPy generators (Cython Mersenne spike generators, + sklearn ``random_state``, etc.) in a way that (a) always changes with + :func:`set_random_seed` and (b) guarantees distinct sub-seeds for distinct + label tuples — which the older ``SEED + (class_id+1)*(global_id+1)`` pattern + did not (e.g. (0, 5) and (1, 2) collided). + + Parameters + ---------- + *labels : int + Arbitrary integer identifiers (e.g. cell class ID, pool ID). Their + order is part of the sub-seed, so ``derive_subseed(a, b)`` and + ``derive_subseed(b, a)`` yield different sub-seeds. + + Returns + ------- + int + A non-negative 32-bit integer suitable for passing as a seed to + NumPy, sklearn, or the bundled Cython RNG wrappers. + """ + seq = np.random.SeedSequence(entropy=(_current_seed, *labels)) + return int(seq.generate_state(1, dtype=np.uint32)[0]) + + def set_random_seed(seed: int = _DEFAULT_SEED) -> None: """ Set the random seed for reproducibility. @@ -268,6 +295,7 @@ def error(msg): __all__ = [ "get_random_generator", "get_random_seed", + "derive_subseed", "set_random_seed", "load_nmodl_mechanisms", "NMODLLoadError", diff --git a/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py b/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py index 2d3af4e0..387fa851 100644 --- a/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py +++ b/myogen/simulator/core/emg/intramuscular/motor_unit_sim.py @@ -15,7 +15,7 @@ from sklearn.cluster import KMeans from tqdm import tqdm -from myogen import get_random_generator, get_random_seed +from myogen import derive_subseed, get_random_generator from myogen.utils.decorators import beartowertype from .bioelectric import ( get_current_density, @@ -138,7 +138,7 @@ def sim_nmj_branches_two_layers( ) # Point coordinates kmeans = KMeans( - n_clusters=n_branches, init="k-means++", max_iter=100, random_state=get_random_seed() + n_clusters=n_branches, init="k-means++", max_iter=100, random_state=derive_subseed(n_branches) ) idx = kmeans.fit_predict(self.muscle_fiber_centers__mm) c = kmeans.cluster_centers_ diff --git a/myogen/simulator/neuron/cells.py b/myogen/simulator/neuron/cells.py index 99c50aa3..1b84dfc3 100644 --- a/myogen/simulator/neuron/cells.py +++ b/myogen/simulator/neuron/cells.py @@ -9,7 +9,7 @@ import quantities as pq from neuron import h -from myogen import get_random_generator, get_random_seed +from myogen import derive_subseed, get_random_generator from myogen.simulator.neuron._cython._gamma_process_generator import ( _GammaProcessGenerator__Cython, ) @@ -409,7 +409,7 @@ def __init__(self, N, dt, pool__ID: int | None = None): self.ns = h.DUMMY() # Dummy cell _Cell.__init__(self, next(self._ids2), pool__ID) _PoissonProcessGenerator__Cython.__init__( - self, get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), N, dt + self, derive_subseed(self.class__ID, self.global__ID), N, dt ) def __repr__(self) -> str: @@ -494,7 +494,7 @@ def __init__( _Cell.__init__(self, next(self._ids2), pool__ID) _GammaProcessGenerator__Cython.__init__( self, - get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), + derive_subseed(self.class__ID, self.global__ID), shape, timestep__ms.magnitude, ) @@ -589,7 +589,7 @@ def __init__( _Cell.__init__(self, class__ID if class__ID is not None else next(self._ids2), pool__ID) _GammaProcessGenerator__Cython.__init__( self, - seed=get_random_seed() + (self.class__ID + 1) * (self.global__ID + 1), + seed=derive_subseed(self.class__ID, self.global__ID), shape=N, # Shape parameter controls ISI CV = 1/sqrt(N) dt=timestep__ms.magnitude, ) diff --git a/tests/test_determinism.py b/tests/test_determinism.py index 9c6f0291..089783d7 100644 --- a/tests/test_determinism.py +++ b/tests/test_determinism.py @@ -98,6 +98,29 @@ def test_deprecated_SEED_emits_warning_and_returns_current_seed(): assert seed_via_deprecated == 5150 +def test_derive_subseed_is_collision_free_for_distinct_labels(): + """Regression for the earlier ``SEED + (a+1)*(b+1)`` collision — e.g. (0, 5) and (1, 2) both map to +6.""" + set_random_seed(42) + subseeds = { + (a, b): myogen.derive_subseed(a, b) + for a in range(8) + for b in range(8) + } + assert len(set(subseeds.values())) == len(subseeds), ( + "derive_subseed must yield a unique value for every distinct label tuple" + ) + + +def test_derive_subseed_tracks_set_random_seed(): + set_random_seed(1) + sub_a = myogen.derive_subseed(3, 7) + set_random_seed(2) + sub_b = myogen.derive_subseed(3, 7) + assert sub_a != sub_b, ( + "derive_subseed must change when the global seed changes" + ) + + def test_modules_that_use_accessor_are_not_stale_after_seed_change(): """End-to-end: reimport a module that calls ``get_random_generator()`` and confirm its RNG draws track the current seed.""" from myogen.simulator.core.muscle import muscle as muscle_module From 2816ad341e0dcfd9d7b834077ebe8d38b31daf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:35:33 +0200 Subject: [PATCH 03/17] Soften derive_subseed collision-free language (Codex re-review) Codex re-review noted that derive_subseed compresses inputs to a uint32, so collisions remain possible in principle (birthday probability N^2 / 2^33, ~1e-7 at 1000 cells). Reworded the docstring to promise "deterministic, seed-tracking" rather than "collision-free", documented the quantitative collision bound, and renamed the regression test to test_derive_subseed_avoids_old_collision_pattern with an explicit check on the (0,5) vs (1,2) pair that the old formula mangled. Also extended coverage to a 16x16 label grid. --- myogen/__init__.py | 29 +++++++++++++++-------------- tests/test_determinism.py | 13 ++++++++----- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/myogen/__init__.py b/myogen/__init__.py index 7608260d..6967f39f 100644 --- a/myogen/__init__.py +++ b/myogen/__init__.py @@ -26,20 +26,21 @@ def get_random_seed() -> int: def derive_subseed(*labels: int) -> int: """ - Derive a collision-free deterministic sub-seed from the current seed and a tuple of integer labels. - - Use this to seed non-NumPy generators (Cython Mersenne spike generators, - sklearn ``random_state``, etc.) in a way that (a) always changes with - :func:`set_random_seed` and (b) guarantees distinct sub-seeds for distinct - label tuples — which the older ``SEED + (class_id+1)*(global_id+1)`` pattern - did not (e.g. (0, 5) and (1, 2) collided). - - Parameters - ---------- - *labels : int - Arbitrary integer identifiers (e.g. cell class ID, pool ID). Their - order is part of the sub-seed, so ``derive_subseed(a, b)`` and - ``derive_subseed(b, a)`` yield different sub-seeds. + Derive a deterministic, seed-tracking sub-seed from the current seed and a tuple of integer labels. + + Intended for seeding non-NumPy generators (Cython Mersenne spike + generators, sklearn ``random_state``, etc.) so that a call to + :func:`set_random_seed` propagates to them. Label order matters: + ``derive_subseed(a, b)`` and ``derive_subseed(b, a)`` yield different + sub-seeds. + + This replaces the pre-existing ``SEED + (class_id+1)*(global_id+1)`` + derivation, which collided on swapped factors — e.g. ``(0, 5)`` and + ``(1, 2)`` both produced ``+6``. The present mixing function uses + :class:`numpy.random.SeedSequence` to fold the inputs into a 32-bit + integer; collisions remain possible in principle (birthday-paradox + probability ≈ ``N² / 2³³``) but are negligible for realistic motor-unit + pool sizes (≲ 10⁻⁷ at 1000 cells). Returns ------- diff --git a/tests/test_determinism.py b/tests/test_determinism.py index 089783d7..ca4967b6 100644 --- a/tests/test_determinism.py +++ b/tests/test_determinism.py @@ -98,16 +98,19 @@ def test_deprecated_SEED_emits_warning_and_returns_current_seed(): assert seed_via_deprecated == 5150 -def test_derive_subseed_is_collision_free_for_distinct_labels(): - """Regression for the earlier ``SEED + (a+1)*(b+1)`` collision — e.g. (0, 5) and (1, 2) both map to +6.""" +def test_derive_subseed_avoids_old_collision_pattern(): + """Regression for the earlier ``SEED + (a+1)*(b+1)`` collision: (0, 5) and (1, 2) both produced +6. The new SeedSequence-based mixing is uint32 and therefore not literally collision-free, but it must not reproduce the swapped-factor collision, and a 16x16 grid must be collision-free in practice.""" set_random_seed(42) + assert myogen.derive_subseed(0, 5) != myogen.derive_subseed(1, 2) + assert myogen.derive_subseed(1, 2) != myogen.derive_subseed(2, 1) + subseeds = { (a, b): myogen.derive_subseed(a, b) - for a in range(8) - for b in range(8) + for a in range(16) + for b in range(16) } assert len(set(subseeds.values())) == len(subseeds), ( - "derive_subseed must yield a unique value for every distinct label tuple" + "derive_subseed must yield a unique value across a 16x16 label grid" ) From 444e8cefbbf437ecbce9f3baf45b0eb3cc63fed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:36:36 +0200 Subject: [PATCH 04/17] Guard FR_std against n=1 NaN in calculate_firing_rate_statistics ddof=1 is undefined for n<2 (division by n-1 = 0) and returns NaN, which could silently corrupt downstream ensemble-statistics analysis when a pool has exactly one active unit after firing-rate filtering. Guards the sample-std branch in myogen/utils/helper.py:175 to return 0.0 when fewer than two units are active. The empty-population path (n=0) already returned 0.0 and is unchanged. Adds tests/test_firing_rate_statistics.py with three cases: - single active unit -> FR_std is 0.0 (regression for the NaN case) - empty population -> FR_std stays 0.0 - two distinct rates -> FR_std is finite and positive (sanity) --- myogen/utils/helper.py | 7 +++- tests/test_firing_rate_statistics.py | 61 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/test_firing_rate_statistics.py diff --git a/myogen/utils/helper.py b/myogen/utils/helper.py index 83f29e99..45ea8c3b 100644 --- a/myogen/utils/helper.py +++ b/myogen/utils/helper.py @@ -169,10 +169,13 @@ def calculate_firing_rate_statistics( "firing_rates": np.array([]), } - # Ensemble statistics: mean across neurons for both FR and SD_FR + # Ensemble statistics: mean across neurons for both FR and SD_FR. + # np.std with ddof=1 is undefined for n<2, so report 0.0 rather than + # NaN when only one unit is active. + fr_std = np.std(firing_rates, ddof=1) if len(firing_rates) > 1 else 0.0 return { "FR_mean": np.mean(firing_rates), - "FR_std": np.std(firing_rates, ddof=1), + "FR_std": fr_std, "n_active": len(firing_rates), "firing_rates": np.array(firing_rates), } diff --git a/tests/test_firing_rate_statistics.py b/tests/test_firing_rate_statistics.py new file mode 100644 index 00000000..ba2aa816 --- /dev/null +++ b/tests/test_firing_rate_statistics.py @@ -0,0 +1,61 @@ +"""Regression test for R1 Code Review 3: FR_std must not be NaN when only a single unit is active.""" + +from __future__ import annotations + +import math + +import neo +import numpy as np +import pytest +import quantities as pq + +from myogen.utils.helper import calculate_firing_rate_statistics + + +def _build_spiketrain(times_ms, t_stop_ms=2000.0): + """Construct a ``neo.SpikeTrain`` in milliseconds.""" + return neo.SpikeTrain(np.asarray(times_ms) * pq.ms, t_stop=t_stop_ms * pq.ms) + + +def test_fr_std_is_zero_not_nan_for_single_active_unit(): + """When only one unit passes the firing-rate filter, ``FR_std`` must be 0.0, never NaN — otherwise downstream analyses silently corrupt (CR3).""" + active = _build_spiketrain(np.linspace(100.0, 1900.0, num=30)) + silent = _build_spiketrain([]) + + stats = calculate_firing_rate_statistics( + [active, silent], + plateau_start_ms=100.0, + plateau_end_ms=1900.0, + ) + + assert stats["n_active"] == 1 + assert not math.isnan(stats["FR_std"]), "FR_std must not be NaN when n_active == 1" + assert stats["FR_std"] == 0.0 + + +def test_fr_std_is_zero_for_empty_population(): + """Empty-population branch already returned 0.0 and must keep doing so.""" + silent = _build_spiketrain([]) + stats = calculate_firing_rate_statistics( + [silent, silent], + plateau_start_ms=0.0, + plateau_end_ms=2000.0, + ) + assert stats["n_active"] == 0 + assert stats["FR_std"] == 0.0 + + +def test_fr_std_is_finite_and_positive_for_multiple_active_units(): + """Two active units with distinct rates must produce a positive, finite FR_std.""" + fast = _build_spiketrain(np.linspace(100.0, 1900.0, num=40)) # ~22 Hz + slow = _build_spiketrain(np.linspace(100.0, 1900.0, num=15)) # ~7.5 Hz + + stats = calculate_firing_rate_statistics( + [fast, slow], + plateau_start_ms=100.0, + plateau_end_ms=1900.0, + ) + + assert stats["n_active"] == 2 + assert math.isfinite(stats["FR_std"]) + assert stats["FR_std"] > 0.0 From 6e58e1211275cec3b321bdc607ea4e28171f1c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:37:50 +0200 Subject: [PATCH 05/17] Extend FR_std fix to per-neuron CV_ISI NaN path (Codex review) Codex noted a second NaN landmine in the same function: when a caller sets return_per_neuron=True with min_spikes_for_cv=2, a neuron with exactly two spikes yields one ISI, and np.std(..., ddof=1) over that single element is NaN. Guarded with len(isis_array) > 1; falls back to CV_ISI = 0.0 otherwise. Added a regression test test_per_neuron_cv_is_finite_with_min_spikes_for_cv_2. --- myogen/utils/helper.py | 10 ++++++++-- tests/test_firing_rate_statistics.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/myogen/utils/helper.py b/myogen/utils/helper.py index 45ea8c3b..47389ba0 100644 --- a/myogen/utils/helper.py +++ b/myogen/utils/helper.py @@ -144,8 +144,14 @@ def calculate_firing_rate_statistics( firing_rates.append(neuron_fr) if return_per_neuron: - # Compute CV of inter-spike intervals - cv = np.std(isis_array, ddof=1) / np.mean(isis_array) + # Compute CV of inter-spike intervals. Guard against + # the n=1 NaN case when a caller lowers + # min_spikes_for_cv to 2 (only one ISI is observable). + cv = ( + np.std(isis_array, ddof=1) / np.mean(isis_array) + if len(isis_array) > 1 + else 0.0 + ) per_neuron_results.append( { "MU_ID": mu_id, diff --git a/tests/test_firing_rate_statistics.py b/tests/test_firing_rate_statistics.py index ba2aa816..f95aac98 100644 --- a/tests/test_firing_rate_statistics.py +++ b/tests/test_firing_rate_statistics.py @@ -45,6 +45,24 @@ def test_fr_std_is_zero_for_empty_population(): assert stats["FR_std"] == 0.0 +def test_per_neuron_cv_is_finite_with_min_spikes_for_cv_2(): + """When a caller lowers ``min_spikes_for_cv`` to 2, a neuron with exactly two spikes produces one ISI — ``np.std(..., ddof=1)`` would otherwise be NaN. The guard at the sample-std call site must return a finite CV.""" + two_spikes = _build_spiketrain([200.0, 1000.0]) + silent = _build_spiketrain([]) + + df = calculate_firing_rate_statistics( + [two_spikes, silent], + plateau_start_ms=100.0, + plateau_end_ms=1900.0, + return_per_neuron=True, + min_spikes_for_cv=2, + ) + + assert len(df) == 1 + cv = df["CV_ISI"].iloc[0] + assert math.isfinite(cv), "CV_ISI must not be NaN for n=1 ISI" + + def test_fr_std_is_finite_and_positive_for_multiple_active_units(): """Two active units with distinct rates must produce a positive, finite FR_std.""" fast = _build_spiketrain(np.linspace(100.0, 1900.0, num=40)) # ~22 Hz From 71b6d5ec99b2644189d3d35c3ee593693b708af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:41:03 +0200 Subject: [PATCH 06/17] Make elephant an optional extra; align NEURON version to 8.2.7 1) elephant is now a proper optional extra. - Removed elephant>=1.1.1 from [project.dependencies]. - Added elephant = ["elephant>=1.1.1"] to [project.optional-dependencies]. - The five code sites that import elephant (myogen/utils/helper.py, surface_emg.py, intramuscular_emg.py, force_model.py, force_model_vectorized.py) already wrap the import in a try/except ImportError and print a helpful "Install with: pip install myogen[elephant]" message when missing; those guards now match reality. 2) NEURON version unified to 8.2.7 in the Windows install docs. - README.md lines 50, 73: bumped 8.2.6 -> 8.2.7 (both installer link and version label). - docs/source/README.md line 42 and docs/source/index.md lines 50, 73: same bump. - Corrected the Python-version stub in the Windows installer filename to match the 8.2.7 release: py-39-310-311-312-313 (8.2.7 dropped 3.8 and added 3.13 compared to 8.2.6's py-38-39-310-311-312). Verified via `gh api repos/neuronsimulator/nrn/releases/tags/8.2.7`. - pyproject.toml and the CI workflow already pinned 8.2.7 for Linux/macOS; no change needed there. --- README.md | 4 ++-- docs/source/README.md | 2 +- docs/source/index.md | 4 ++-- pyproject.toml | 4 +++- uv.lock | 8 +++++--- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1762a49c..14313a45 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ MyoGen is designed for algorithm validation, hypothesis-driven research, and edu | Platform | Before Installing MyoGen | |----------|--------------------------| -| **Windows** | [NEURON 8.2.6](https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe) - Download, run installer, select "Add to PATH" | +| **Windows** | [NEURON 8.2.7](https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe) - Download, run installer, select "Add to PATH" | | **Linux** | `sudo apt install libopenmpi-dev` (Ubuntu/Debian) or `sudo dnf install openmpi-devel` (Fedora) | | **macOS** | `brew install open-mpi` | @@ -70,7 +70,7 @@ MyoGen is designed for algorithm validation, hypothesis-driven research, and edu > > ### 2. NEURON Simulator > -> 1. **Download**: [NEURON 8.2.6 Installer](https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe) +> 1. **Download**: [NEURON 8.2.7 Installer](https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe) > 2. **Run the installer** and select **"Add to PATH"** when prompted > 3. **Restart your terminal** (close and reopen) > 4. Then continue with the installation below diff --git a/docs/source/README.md b/docs/source/README.md index c9bfb3d2..1e721531 100644 --- a/docs/source/README.md +++ b/docs/source/README.md @@ -39,7 +39,7 @@ MyoGen is designed for algorithm validation, hypothesis-driven research, and edu **Prerequisites**: Python ≥3.12, Linux/Windows/macOS > [!IMPORTANT] -> **Windows users**: Install [NEURON 8.2.6](https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe) before running `uv sync` +> **Windows users**: Install [NEURON 8.2.7](https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe) before running `uv sync` ```bash # Clone and install diff --git a/docs/source/index.md b/docs/source/index.md index 45dea341..6cefd5ae 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -47,7 +47,7 @@ MyoGen is designed for algorithm validation, hypothesis-driven research, and edu | Platform | Before Installing MyoGen | |----------|--------------------------| -| **Windows** | [NEURON 8.2.6](https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe) - Download, run installer, select "Add to PATH" | +| **Windows** | [NEURON 8.2.7](https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe) - Download, run installer, select "Add to PATH" | | **Linux** | `sudo apt install libopenmpi-dev` (Ubuntu/Debian) or `sudo dnf install openmpi-devel` (Fedora) | | **macOS** | `brew install open-mpi` | @@ -70,7 +70,7 @@ MyoGen is designed for algorithm validation, hypothesis-driven research, and edu > > ### 2. NEURON Simulator > -> 1. **Download**: [NEURON 8.2.6 Installer](https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe) +> 1. **Download**: [NEURON 8.2.7 Installer](https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe) > 2. **Run the installer** and select **"Add to PATH"** when prompted > 3. **Restart your terminal** (close and reopen) > 4. Then continue with the installation below diff --git a/pyproject.toml b/pyproject.toml index 2ab4ba0d..00bb3a0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ classifiers = [ dependencies = [ "beartype>=0.21.0", "cython>=3.1.3", - "elephant>=1.1.1", "impi-rt>=2021.15.0 ; sys_platform == 'win32'", "matplotlib>=3.10.1", "mpi4py>=4.0.3", @@ -51,6 +50,9 @@ dependencies = [ ] [project.optional-dependencies] +elephant = [ + "elephant>=1.1.1", +] nwb = [ "pynwb>=2.8.0", "nwbinspector>=0.5.0", diff --git a/uv.lock b/uv.lock index b51652ce..5c4380f9 100644 --- a/uv.lock +++ b/uv.lock @@ -1211,7 +1211,6 @@ source = { editable = "." } dependencies = [ { name = "beartype" }, { name = "cython" }, - { name = "elephant" }, { name = "impi-rt", marker = "sys_platform == 'win32'" }, { name = "matplotlib" }, { name = "mpi4py" }, @@ -1231,6 +1230,9 @@ dependencies = [ ] [package.optional-dependencies] +elephant = [ + { name = "elephant" }, +] nwb = [ { name = "nwbinspector" }, { name = "pynwb" }, @@ -1248,7 +1250,7 @@ dev = [ requires-dist = [ { name = "beartype", specifier = ">=0.21.0" }, { name = "cython", specifier = ">=3.1.3" }, - { name = "elephant", specifier = ">=1.1.1" }, + { name = "elephant", marker = "extra == 'elephant'", specifier = ">=1.1.1" }, { name = "impi-rt", marker = "sys_platform == 'win32'", specifier = ">=2021.15.0" }, { name = "matplotlib", specifier = ">=3.10.1" }, { name = "mpi4py", specifier = ">=4.0.3" }, @@ -1268,7 +1270,7 @@ requires-dist = [ { name = "tqdm", specifier = ">=4.67.1" }, { name = "viziphant", specifier = ">=0.4.0" }, ] -provides-extras = ["nwb"] +provides-extras = ["elephant", "nwb"] [package.metadata.requires-dev] dev = [ From ed0089440d2cf2adadddc7dd27bce7cca940ffc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:42:15 +0200 Subject: [PATCH 07/17] Bump setup.py NEURON reference to 8.2.7 (Codex review) Codex spotted remaining 8.2.6 references in setup.py (lines 204, 208) in the Windows-install failure message. Bumped version string and Windows installer URL to 8.2.7 (py-39-310-311-312-313 stub). --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5ca35d25..1bb0932f 100644 --- a/setup.py +++ b/setup.py @@ -201,11 +201,11 @@ def compile_nmodl(self): MyoGen Installation Failed - NEURON Required ================================================================================ -MyoGen requires NEURON 8.2.6 to be installed BEFORE installing MyoGen. +MyoGen requires NEURON 8.2.7 to be installed BEFORE installing MyoGen. STEP 1: Download and install NEURON ----------------------------------- - https://github.com/neuronsimulator/nrn/releases/download/8.2.6/nrn-8.2.6.w64-mingw-py-38-39-310-311-312-setup.exe + https://github.com/neuronsimulator/nrn/releases/download/8.2.7/nrn-8.2.7.w64-mingw-py-39-310-311-312-313-setup.exe During installation, select "Add to PATH" From 59d9ea2e5ee0032701f75e5ecbab8176bda2c2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:43:22 +0200 Subject: [PATCH 08/17] Fix "extandable" typo and "as determined by no one" placeholder text - "extandable" -> "extensible" in README.md, docs/source/index.md, docs/source/README.md. - muscle.py docstring (two places) replaces the informal "as determined by no one" placeholders with concrete rationale: the 30 mm default length and the 0.25 max territory-to-muscle ratio are nominal starting points for the FDI; users are told to revisit them for other muscles. Broader repo-wide grep for common English typos (recieve, teh, seperate, occured, lenght, etc.) found no additional hits. --- README.md | 2 +- docs/source/README.md | 2 +- docs/source/index.md | 2 +- myogen/simulator/core/muscle/muscle.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 14313a45..c77a8a50 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ MyoGen Logo -

The modular and extandable simulation toolkit for neurophysiology

+

The modular and extensible simulation toolkit for neurophysiology

[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://nsquaredlab.github.io/MyoGen/) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) diff --git a/docs/source/README.md b/docs/source/README.md index 1e721531..3460ae3f 100644 --- a/docs/source/README.md +++ b/docs/source/README.md @@ -13,7 +13,7 @@ [Paper](#citation) -# MyoGen - The modular and extandable simulation toolkit for neurophysiology +# MyoGen - The modular and extensible simulation toolkit for neurophysiology MyoGen is a **modular and extensible neuromuscular simulation framework** for generating physiologically grounded motor-unit activity, muscle force, and surface EMG signals. diff --git a/docs/source/index.md b/docs/source/index.md index 6cefd5ae..2096d77b 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -4,7 +4,7 @@ MyoGen Logo -

The modular and extandable simulation toolkit for neurophysiology

+

The modular and extensible simulation toolkit for neurophysiology

[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://nsquaredlab.github.io/MyoGen/) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) diff --git a/myogen/simulator/core/muscle/muscle.py b/myogen/simulator/core/muscle/muscle.py index 3baf1785..9e04140f 100644 --- a/myogen/simulator/core/muscle/muscle.py +++ b/myogen/simulator/core/muscle/muscle.py @@ -101,12 +101,12 @@ class Muscle: radius__mm : float, default=6.91 Radius of the muscle cross-section in millimeters. Default is set to 6.91 mm as determined by Jacobson et al. 1992 [3]_. length__mm : float, default=30.0 - Length of the muscle in millimeters. Default is set to 30.0 mm as determined by no one. + Length of the muscle in millimeters. Default is 30.0 mm, a nominal value chosen to match the order of magnitude of the FDI muscle; adjust to match the muscle under study. fiber_density__fibers_per_mm2 : float, default=350 Density of muscle fibers per square millimeter. Default is set to 350 fibers/mm² as determined by Bettelho et al. 2019 [7]_. max_innervation_area_to_total_muscle_area__ratio : float, default=0.25 Ratio defining the maximum territory size relative to total muscle area. - Default is set to 0.25 as determined by no one but it is a good starting point. + Default is 0.25 as a pragmatic upper bound for the FDI, with no single published source; revisit for larger muscles. A value of 0.25 means the largest motor unit can innervate up to 25% of the total muscle cross-sectional area. Must be in range (0, 1]. From b8e5a5fb556cf5007e09f701bb0276112a2badd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:45:28 +0200 Subject: [PATCH 09/17] Replace bar graphs with distribution plots in examples the underlying data rather than summary-only bars . - examples/02_finetune/01_optimize_dd_for_target_firing_rate.py Target-vs-Achieved comparison is two scalar endpoints per category (not a distribution), so converted to a labelled dumbbell plot that explicitly marks each data point. - examples/02_finetune/02_compute_force_from_optimized_dd.py - examples/03_papers/watanabe/01_compute_baseline_force.py Per-motor-unit firing rate bar chart replaced with a violin + box-and-whisker shell (only when n >= 10, matching the journal's threshold) plus a jittered scatter of every active unit. Centrality (Mean) and dispersion (SD) annotated in the legend. No functional change to the underlying data or simulation pipeline. --- .../01_optimize_dd_for_target_firing_rate.py | 30 ++++++++----------- .../02_compute_force_from_optimized_dd.py | 23 ++++++++++++-- .../watanabe/01_compute_baseline_force.py | 23 ++++++++++++-- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py b/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py index 5f73efb8..8bd36b8a 100644 --- a/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py +++ b/examples/02_finetune/01_optimize_dd_for_target_firing_rate.py @@ -504,7 +504,9 @@ def trial_to_dict(t): fig, ax = plt.subplots(1, 1, figsize=(10, 6)) -# Bar plot comparing target vs achieved +# Dumbbell plot comparing target vs achieved (two scalar endpoints per +# category — not a distribution, so Editor 8's "show all data points" +# rule is honoured by marking each value as an explicit point). categories = ["Mean FR (Hz)", "Std FR (Hz)"] targets = [TARGET_FR_MEAN__HZ, TARGET_FR_STD__HZ] achieved = [ @@ -513,22 +515,16 @@ def trial_to_dict(t): ] x = np.arange(len(categories)) -width = 0.35 - -bars1 = ax.bar(x - width / 2, targets, width, label="Target") -bars2 = ax.bar(x + width / 2, achieved, width, label="Achieved") - -# Add value labels on bars -for bars in [bars1, bars2]: - for bar in bars: - height = bar.get_height() - ax.text( - bar.get_x() + bar.get_width() / 2.0, - height, - f"{height:.2f}", - ha="center", - va="bottom", - ) + +for xi, t, a in zip(x, targets, achieved): + ax.plot([xi, xi], [t, a], color="gray", linestyle="--", alpha=0.6, zorder=1) + +ax.scatter(x, targets, s=120, marker="o", label="Target", zorder=3) +ax.scatter(x, achieved, s=120, marker="D", label="Achieved", zorder=3) + +for xi, t, a in zip(x, targets, achieved): + ax.text(xi + 0.05, t, f"{t:.2f}", va="center", ha="left") + ax.text(xi + 0.05, a, f"{a:.2f}", va="center", ha="left") # Calculate percent errors mean_error = abs(achieved[0] - targets[0]) / targets[0] * 100 diff --git a/examples/02_finetune/02_compute_force_from_optimized_dd.py b/examples/02_finetune/02_compute_force_from_optimized_dd.py index 5ffe0a9e..d8542bfb 100644 --- a/examples/02_finetune/02_compute_force_from_optimized_dd.py +++ b/examples/02_finetune/02_compute_force_from_optimized_dd.py @@ -405,9 +405,26 @@ mu_ids.append(i) if firing_rates: - axes[2].bar(mu_ids, firing_rates, alpha=0.7) - axes[2].axhline(fr_mean, linestyle="--", label=f"Mean = {fr_mean:.1f} Hz") - axes[2].set_xlabel("Motor Unit ID") + # Distribution plot with all data points (Editor 8): violin shell for + # the density, box-and-whisker for the quartiles, jittered points for + # every active motor unit. + fr_array = np.asarray(firing_rates) + fr_sd = float(np.std(fr_array, ddof=1)) if fr_array.size > 1 else 0.0 + if fr_array.size >= 10: + axes[2].violinplot( + fr_array, positions=[0], widths=0.7, showmeans=False, showmedians=False + ) + axes[2].boxplot( + fr_array, positions=[0], widths=0.3, showfliers=False + ) + jitter = np.random.default_rng(0).uniform(-0.15, 0.15, size=fr_array.size) + axes[2].scatter(jitter, fr_array, alpha=0.6, s=18, zorder=3) + axes[2].axhline( + fr_mean, linestyle="--", label=f"Mean = {fr_mean:.1f} Hz (SD = {fr_sd:.1f} Hz)" + ) + axes[2].set_xticks([0]) + axes[2].set_xticklabels([f"n = {fr_array.size}"]) + axes[2].set_xlabel("Motor Units") axes[2].set_ylabel("Firing Rate (Hz)") axes[2].set_title("Firing Rate Distribution") axes[2].legend(framealpha=1.0, edgecolor="none") diff --git a/examples/03_papers/watanabe/01_compute_baseline_force.py b/examples/03_papers/watanabe/01_compute_baseline_force.py index 9a6bbe33..8c338075 100644 --- a/examples/03_papers/watanabe/01_compute_baseline_force.py +++ b/examples/03_papers/watanabe/01_compute_baseline_force.py @@ -428,9 +428,26 @@ mu_ids.append(i) if firing_rates: - axes[2].bar(mu_ids, firing_rates, alpha=0.7) - axes[2].axhline(fr_mean, linestyle="--", label=f"Mean = {fr_mean:.1f} Hz") - axes[2].set_xlabel("Motor Unit ID") + # Distribution plot with all data points (Editor 8): violin shell for + # the density, box-and-whisker for the quartiles, jittered points for + # every active motor unit. + fr_array = np.asarray(firing_rates) + fr_sd = float(np.std(fr_array, ddof=1)) if fr_array.size > 1 else 0.0 + if fr_array.size >= 10: + axes[2].violinplot( + fr_array, positions=[0], widths=0.7, showmeans=False, showmedians=False + ) + axes[2].boxplot( + fr_array, positions=[0], widths=0.3, showfliers=False + ) + jitter = np.random.default_rng(0).uniform(-0.15, 0.15, size=fr_array.size) + axes[2].scatter(jitter, fr_array, alpha=0.6, s=18, zorder=3) + axes[2].axhline( + fr_mean, linestyle="--", label=f"Mean = {fr_mean:.1f} Hz (SD = {fr_sd:.1f} Hz)" + ) + axes[2].set_xticks([0]) + axes[2].set_xticklabels([f"n = {fr_array.size}"]) + axes[2].set_xlabel("Motor Units") axes[2].set_ylabel("Firing Rate (Hz)") axes[2].set_title("Firing Rate Distribution") axes[2].legend(framealpha=1.0, edgecolor="none") From 2c530576e86ff8d2b5fa7a52ce141dcd28ad24e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:46:48 +0200 Subject: [PATCH 10/17] Exclude distribution subplot from shared time-axis xlim (Codex review) Codex caught a visual bug in the two reshaped figures: the existing `for ax in axes: ax.set_xlim(0, SIMULATION_TIME_MS / 1000)` loop now squashed the new firing-rate distribution subplot onto the left edge of the axis and clipped the negative jitter. Restricted the loop to the two time-series subplots (axes[:2]); the categorical distribution subplot sets its own ticks. Applies to: - examples/02_finetune/02_compute_force_from_optimized_dd.py - examples/03_papers/watanabe/01_compute_baseline_force.py --- examples/02_finetune/02_compute_force_from_optimized_dd.py | 5 +++-- examples/03_papers/watanabe/01_compute_baseline_force.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/02_finetune/02_compute_force_from_optimized_dd.py b/examples/02_finetune/02_compute_force_from_optimized_dd.py index d8542bfb..e2473825 100644 --- a/examples/02_finetune/02_compute_force_from_optimized_dd.py +++ b/examples/02_finetune/02_compute_force_from_optimized_dd.py @@ -429,8 +429,9 @@ axes[2].set_title("Firing Rate Distribution") axes[2].legend(framealpha=1.0, edgecolor="none") -# Format all axes -for ax in axes: +# Format time-series axes (subplot 2 is the MU firing-rate distribution, +# whose x-axis is categorical, not time) +for ax in axes[:2]: ax.set_xlim(0, SIMULATION_TIME_MS / 1000) plt.tight_layout() diff --git a/examples/03_papers/watanabe/01_compute_baseline_force.py b/examples/03_papers/watanabe/01_compute_baseline_force.py index 8c338075..3fe2e72a 100644 --- a/examples/03_papers/watanabe/01_compute_baseline_force.py +++ b/examples/03_papers/watanabe/01_compute_baseline_force.py @@ -452,7 +452,7 @@ axes[2].set_title("Firing Rate Distribution") axes[2].legend(framealpha=1.0, edgecolor="none") -for ax in axes: +for ax in axes[:2]: ax.set_xlim(0, SIMULATION_TIME_MS / 1000) plt.tight_layout() From 1baed965745f509cecbecc53852bdf452b7d9541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:49:44 +0200 Subject: [PATCH 11/17] Add per-muscle supplementary figure generator The main Figure 4B pools experimental ISI/CV statistics across VL, VM and FDI into one axis. A per-muscle breakdown was requested so the simulation overlay can be compared against each individual muscle's envelope. The pooled main figure stays untouched; this commit adds a companion three-panel supplementary figure. Added to examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py: - plot_cv_vs_fr_per_muscle(all_muscle_data, exp_data, muscles=...) builds a 1 x N grid (default VL | VM | FDI) where each panel shows that muscle's experimental convex hull + scatter alongside the full simulation overlay (same colors, same force-level markers as the pooled figure) so the simulation-vs-muscle match is evaluated panel-local. - Main-script block that calls the new function after the pooled plot and saves the companion figure to `isi_cv_per_muscle_supplement.{OUTPUT_FORMAT}` in the existing results directory. --- .../05_plot_isi_cv_multi_muscle_comparison.py | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py b/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py index f7c605b5..a255a1df 100644 --- a/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py +++ b/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py @@ -526,6 +526,128 @@ def plot_cv_vs_fr_multi_muscle(all_muscle_data, exp_data): return fig, ax +def plot_cv_vs_fr_per_muscle(all_muscle_data, exp_data, muscles=("VL", "VM", "FDI")): + """ + Per-muscle supplementary figure in response to R3 Major 4. + + The main Figure 4B pools experimental data across VL, VM and FDI into + a single axis so the simulation overlay can be compared against the + envelope of all three muscles at once. The reviewer asked for a + companion view that separates the three muscles, enabling a fair + per-muscle comparison against the simulation. Each panel therefore + shows a single muscle's experimental envelope (convex hull + scatter) + and repeats the full simulation overlay so readers can judge how well + the simulation matches each individual muscle. + + Parameters + ---------- + all_muscle_data : dict + Nested ``{simulated_muscle_type: {force_level: DataFrame}}`` — the + same structure consumed by :func:`plot_cv_vs_fr_multi_muscle`. + exp_data : pd.DataFrame + Experimental ISI statistics with at least ``Muscle``, ``ISI CV`` + and ``FR mean`` columns. + muscles : sequence of str, optional + Experimental muscles to plot, one per panel. + + Returns + ------- + tuple + ``(fig, axes)`` where ``axes`` is a 1-D array of Axes, one per muscle. + """ + n_panels = len(muscles) + fig, axes = plt.subplots( + 1, n_panels, figsize=(5.5 * n_panels, 6), sharex=True, sharey=True + ) + if n_panels == 1: + axes = np.array([axes]) + + # Same force markers across all panels so they can be compared directly. + all_force_levels = set() + for muscle_data in all_muscle_data.values(): + all_force_levels.update(muscle_data.keys()) + force_markers = generate_force_markers(all_force_levels) + + for ax, muscle in zip(axes, muscles): + # 1. Experimental envelope for this muscle only. + if exp_data is not None: + muscle_rows = exp_data[exp_data["Muscle"] == muscle] + cv_data = muscle_rows["ISI CV"].values + fr_data = muscle_rows["FR mean"].values + + if len(cv_data) > 2: + points = np.column_stack([cv_data, fr_data]) + try: + hull = ConvexHull(points) + polygon = Polygon( + points[hull.vertices], + facecolor=EXP_COLORS.get(muscle, "#808080"), + alpha=0.25, + edgecolor=EXP_COLORS.get(muscle, "#808080"), + linewidth=1.5, + linestyle="-", + zorder=0, + ) + ax.add_patch(polygon) + except Exception: + pass + + ax.scatter( + cv_data, + fr_data, + s=20, + alpha=1.0, + color=EXP_COLORS.get(muscle, "#808080"), + edgecolors="white", + linewidth=0.5, + marker="x", + zorder=1, + label=f"Experimental {muscle}", + ) + + # 2. Full simulation overlay repeated on every panel so the + # simulation-vs-muscle comparison is panel-local. + for muscle_type in sorted(all_muscle_data.keys()): + muscle_data = all_muscle_data[muscle_type] + short_muscle = muscle_type.split("_")[0] + colormap_name = MUSCLE_COLORMAPS.get(short_muscle, "Greys") + + for force_level in sorted(muscle_data.keys()): + df = muscle_data[force_level] + recruitment_order = ( + df["MU_ID"].values if "MU_ID" in df.columns else np.arange(len(df)) + ) + colors = get_muscle_colors(recruitment_order, colormap_name) + marker = force_markers.get(force_level, "o") + ax.scatter( + df["CV_ISI"], + df["mean_firing_rate_Hz"], + s=40, + alpha=0.8, + c=colors, + edgecolors="black", + linewidth=0.5, + marker=marker, + zorder=2, + ) + + ax.set_xlabel("Coefficient of Variation (CV)", fontsize=12) + ax.set_title(muscle, fontsize=14) + ax.set_xlim(0, 0.5) + ax.set_ylim(4, 25) + ax.tick_params(axis="both", labelsize=10) + + axes[0].set_ylabel("Mean Firing Rate (pps)", fontsize=12) + + fig.suptitle( + "ISI Statistics Comparison — per-muscle split (supplementary to Fig 4B)", + fontsize=14, + ) + fig.tight_layout() + + return fig, axes + + ############################################################################## # Load Simulation Data # --------------------- @@ -596,6 +718,26 @@ def plot_cv_vs_fr_multi_muscle(all_muscle_data, exp_data): plt.show() print(f"\nPlot saved to: {output_file}") +############################################################################## +# Per-Muscle Supplementary Figure (R3 Major 4) +# -------------------------------------------- +# +# Companion to the pooled Fig 4B above: splits the experimental envelope +# into per-muscle panels (VL, VM, FDI) so the simulation overlay can be +# compared against each muscle individually. + +print("\nCreating per-muscle supplementary figure...") +supp_fig, _ = plot_cv_vs_fr_per_muscle(all_muscle_data, exp_data) +supp_output_file = RESULTS_PATH / f"isi_cv_per_muscle_supplement.{OUTPUT_FORMAT}" +if OUTPUT_FORMAT in ["jpg", "jpeg"]: + supp_fig.savefig( + supp_output_file, dpi=300, bbox_inches="tight", pil_kwargs={"quality": 95} + ) +else: + supp_fig.savefig(supp_output_file, dpi=300, bbox_inches="tight", transparent=True) +plt.show() +print(f"Supplementary plot saved to: {supp_output_file}") + ############################################################################## # Summary Statistics # ------------------ From 8d381d4be337b0b6b9c527dc72ae3d7c0002f621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 21:50:36 +0200 Subject: [PATCH 12/17] Add shared legend to per-muscle supplementary figure (Codex review) Codex's LOW suggestion: without a legend the per-panel force markers were only decoded implicitly via the main Fig 4B. Added a shared figure-level legend covering muscle types and force-level markers (same mapping used by the pooled plot), positioned below the panels. --- .../05_plot_isi_cv_multi_muscle_comparison.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py b/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py index a255a1df..b1c8c128 100644 --- a/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py +++ b/examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py @@ -639,11 +639,44 @@ def plot_cv_vs_fr_per_muscle(all_muscle_data, exp_data, muscles=("VL", "VM", "FD axes[0].set_ylabel("Mean Firing Rate (pps)", fontsize=12) + # Shared legend at the figure level so the force-level markers are + # decoded for every panel without repeating a per-axis legend. + legend_handles = [ + Patch( + facecolor=MUSCLE_LEGEND_COLORS.get(m.split("_")[0], "#000000"), + edgecolor="black", + label=m.split("_")[0], + ) + for m in sorted(all_muscle_data.keys()) + ] + for force in sorted(all_force_levels): + legend_handles.append( + Line2D( + [0], + [0], + marker=force_markers[force], + color="none", + markerfacecolor="gray", + markeredgecolor="black", + markersize=8, + label=f"{force}% MVC", + linewidth=0, + ) + ) + fig.legend( + handles=legend_handles, + loc="lower center", + ncol=min(len(legend_handles), 6), + frameon=False, + fontsize=10, + bbox_to_anchor=(0.5, -0.02), + ) + fig.suptitle( "ISI Statistics Comparison — per-muscle split (supplementary to Fig 4B)", fontsize=14, ) - fig.tight_layout() + fig.tight_layout(rect=(0, 0.05, 1, 1)) return fig, axes From 30cef7be59a3688c4da708eef8b98b800292a756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 22:02:02 +0200 Subject: [PATCH 13/17] Fix pyproject.toml: restore docs dep-group Chunk 1 inadvertently inserted [tool.pytest.ini_options] between the dev and docs dependency-groups, which reparented the docs entries under pytest config and removed them from uv's dep-group resolver. Moved the pytest section below the dep-groups. --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 00bb3a0f..781b7af3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,9 +76,6 @@ dev = [ "pytest>=8.0", "scipy-stubs>=1.16.1.0", ] - -[tool.pytest.ini_options] -testpaths = ["tests"] docs = [ "enum-tools[sphinx]>=0.12.0", "linkify-it-py>=2.0.3", @@ -99,6 +96,9 @@ docs = [ "nwbinspector>=0.5.0", ] +[tool.pytest.ini_options] +testpaths = ["tests"] + [tool.poe.tasks] setup_myogen = "python -c 'from myogen import _setup_myogen; _setup_myogen()'" From e6cd8628b115172f0250a6435c5be8e108ebec3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sat, 18 Apr 2026 22:36:25 +0200 Subject: [PATCH 14/17] Pin sphinx<9 in docs dep-group sphinx 9.1.0 broke sphinx-hoverxref 1.4.2 (cannot unpack non-iterable _Opt object in deprecated_configs_warning). sphinx-hoverxref has no released fix yet; pinning sphinx<9 restores the docs build. Matches what CI must have been resolving to before sphinx 9 released. Verified: full `make html` build completes (with the documented `|| make html` retry for NEURON nrn_finitialize state drift) and renders every example in 01_basic/, 02_finetune/, and 03_papers/watanabe/, including all files touched by this branch. --- pyproject.toml | 2 +- uv.lock | 762 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 756 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 781b7af3..9af70ef5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ docs = [ "pygments>=2.19.2", "rinohtype>=0.5.5", "roman>=5.2", - "sphinx>=8.1.3", + "sphinx>=8.1.3,<9", "sphinx-autodoc-typehints>=2.5.0", "sphinx-design>=0.6.1", "sphinx-gallery>=0.19.0", diff --git a/uv.lock b/uv.lock index 5c4380f9..4bb26804 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,18 @@ resolution-markers = [ "python_full_version < '3.13'", ] +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -113,6 +125,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + [[package]] name = "alembic" version = "1.17.2" @@ -127,6 +148,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, ] +[[package]] +name = "apeye" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "apeye-core" }, + { name = "domdf-python-tools" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/6b/cc65e31843d7bfda8313a9dc0c77a21e8580b782adca53c7cb3e511fe023/apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36", size = 99219, upload-time = "2023-08-14T15:32:41.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/7b/2d63664777b3e831ac1b1d8df5bbf0b7c8bee48e57115896080890527b1b/apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e", size = 107989, upload-time = "2023-08-14T15:32:40.064Z" }, +] + +[[package]] +name = "apeye-core" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "domdf-python-tools" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/4c/4f108cfd06923bd897bf992a6ecb6fb122646ee7af94d7f9a64abd071d4c/apeye_core-1.1.5.tar.gz", hash = "sha256:5de72ed3d00cc9b20fea55e54b7ab8f5ef8500eb33a5368bc162a5585e238a55", size = 96511, upload-time = "2024-01-30T17:45:48.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/9f/fa9971d2a0c6fef64c87ba362a493a4f230eff4ea8dfb9f4c7cbdf71892e/apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf", size = 99286, upload-time = "2024-01-30T17:45:46.764Z" }, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + [[package]] name = "asciitree" version = "0.3.3" @@ -142,6 +200,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "autodocsumm" +version = "0.2.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/f28dea12fae1d1ad1e706f5cf6d16e8d735f305ebee86fd9390e099bd27d/autodocsumm-0.2.15.tar.gz", hash = "sha256:eaf431e7a5a39e41a215311173c8b95e83859059df1ccf3b79c64bf3d5582b3c", size = 46674, upload-time = "2026-03-26T20:44:07.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/3d/4357a0f685c0a2ae7132ac91905bec565e64f9ba63b079f7ec5da46e3597/autodocsumm-0.2.15-py3-none-any.whl", hash = "sha256:dbe6fabcaeae4540748ea9b3443eb76c2692e063d44f004f67c424610a5aca9a", size = 14852, upload-time = "2026-03-26T20:44:05.273Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + [[package]] name = "beartype" version = "0.22.9" @@ -151,6 +230,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + [[package]] name = "bleach" version = "6.3.0" @@ -184,6 +276,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl", hash = "sha256:89a66cb8bfe85e91bce144e3ccf3c4a6f0f1347e7006282972568ea0ecacbb00", size = 7206137, upload-time = "2025-11-07T20:50:58.108Z" }, ] +[[package]] +name = "cachecontrol" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, +] + +[package.optional-dependencies] +filecache = [ + { name = "filelock" }, +] + [[package]] name = "certifi" version = "2025.11.12" @@ -358,6 +468,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, ] +[[package]] +name = "cssutils" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/9f/329d26121fe165be44b1dfff21aa0dc348f04633931f1d20ed6cf448a236/cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2", size = 711657, upload-time = "2024-06-04T15:51:39.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747, upload-time = "2024-06-04T15:51:37.499Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -409,6 +531,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, ] +[[package]] +name = "dict2css" +version = "0.3.0.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cssutils" }, + { name = "domdf-python-tools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/eb/776eef1f1aa0188c0fc165c3a60b71027539f71f2eedc43ad21b060e9c39/dict2css-0.3.0.post1.tar.gz", hash = "sha256:89c544c21c4ca7472c3fffb9d37d3d926f606329afdb751dc1de67a411b70719", size = 7845, upload-time = "2023-11-22T11:09:20.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/47/290daabcf91628f4fc0e17c75a1690b354ba067066cd14407712600e609f/dict2css-0.3.0.post1-py3-none-any.whl", hash = "sha256:f006a6b774c3e31869015122ae82c491fd25e7de4a75607a62aa3e798f837e0d", size = 25647, upload-time = "2023-11-22T11:09:19.221Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "domdf-python-tools" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "natsort" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/8b/ab2d8a292bba8fe3135cacc8bfd3576710a14b8f2d0a8cde19130d5c9d21/domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298", size = 100458, upload-time = "2025-02-12T17:34:05.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/11/208f72084084d3f6a2ed5ebfdfc846692c3f7ad6dce65e400194924f7eed/domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36", size = 126860, upload-time = "2025-02-12T17:34:04.093Z" }, +] + [[package]] name = "elephant" version = "1.1.1" @@ -428,6 +585,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/47/b775cf445a9119a782744326036690b4925162cb71b1d4599a9fa64941da/elephant-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c0f5ee07ee06e34ba6474cc579d6cd378fb5bd7acf52985e3bea9b6d3ea3910", size = 594743, upload-time = "2024-10-28T15:33:03.381Z" }, ] +[[package]] +name = "enum-tools" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/87/50091e20c2765aa495b24521844a7d8f7041d48e4f9b47dd928cd38c8606/enum_tools-0.13.0.tar.gz", hash = "sha256:0d13335e361d300dc0f8fd82c8cf9951417246f9676144f5ee1761eb690228eb", size = 18904, upload-time = "2025-04-17T15:26:59.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/45/cf8a8df3ebe78db691ab54525d552085b67658877f0334f4b0c08c43b518/enum_tools-0.13.0-py3-none-any.whl", hash = "sha256:e0112b16767dd08cb94105844b52770eae67ece6f026916a06db4a3d330d2a95", size = 22366, upload-time = "2025-04-17T15:26:58.34Z" }, +] + +[package.optional-dependencies] +sphinx = [ + { name = "sphinx" }, + { name = "sphinx-jinja2-compat" }, + { name = "sphinx-toolbox" }, +] + [[package]] name = "fasteners" version = "0.20" @@ -437,6 +614,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, ] +[[package]] +name = "filelock" +version = "3.28.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474, upload-time = "2026-04-14T22:54:33.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189, upload-time = "2026-04-14T22:54:32.037Z" }, +] + [[package]] name = "find-libpython" version = "0.5.0" @@ -709,6 +895,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/ab/a6aa43d45ceb88adc0e8c1358fa6935c6e6a5895537431dec67524ca2ccd/holoviews-1.22.1-py3-none-any.whl", hash = "sha256:6f4f0656336035cde1d8103ac6461d7c8ac9a60c4a6d883785cc81f2cc5b8702", size = 5946246, upload-time = "2025-12-05T14:54:19.191Z" }, ] +[[package]] +name = "html5lib" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -718,6 +917,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + [[package]] name = "impi-rt" version = "2021.17.1" @@ -919,14 +1127,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] @@ -1067,6 +1275,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "memory-profiler" +version = "0.61.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, +] + [[package]] name = "mpi4py" version = "4.1.1" @@ -1105,6 +1334,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/ca/7e27edf78cd8ba68aacafc836004cd092a978f0d5ffc8a3eac9e904a3e0e/mpi4py-4.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:b4b3813da9a7a1fc37ffb8dad314cb396313a40cd3fe150854ab29e999a9eb8c", size = 1771707, upload-time = "2025-10-10T13:54:51.756Z" }, ] +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, + { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, + { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, + { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, + { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, +] + [[package]] name = "multidict" version = "6.7.0" @@ -1245,6 +1518,24 @@ dev = [ { name = "pytest" }, { name = "scipy-stubs" }, ] +docs = [ + { name = "enum-tools", extra = ["sphinx"] }, + { name = "linkify-it-py" }, + { name = "memory-profiler" }, + { name = "nwbinspector" }, + { name = "pydata-sphinx-theme" }, + { name = "pygments" }, + { name = "pynwb" }, + { name = "rinohtype" }, + { name = "roman" }, + { name = "sphinx" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-design" }, + { name = "sphinx-gallery" }, + { name = "sphinx-hoverxref" }, + { name = "sphinxcontrib-mermaid" }, + { name = "toml" }, +] [package.metadata] requires-dist = [ @@ -1279,6 +1570,41 @@ dev = [ { name = "pytest", specifier = ">=8.0" }, { name = "scipy-stubs", specifier = ">=1.16.1.0" }, ] +docs = [ + { name = "enum-tools", extras = ["sphinx"], specifier = ">=0.12.0" }, + { name = "linkify-it-py", specifier = ">=2.0.3" }, + { name = "memory-profiler", specifier = ">=0.61.0" }, + { name = "nwbinspector", specifier = ">=0.5.0" }, + { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, + { name = "pygments", specifier = ">=2.19.2" }, + { name = "pynwb", specifier = ">=2.8.0" }, + { name = "rinohtype", specifier = ">=0.5.5" }, + { name = "roman", specifier = ">=5.2" }, + { name = "sphinx", specifier = ">=8.1.3,<9" }, + { name = "sphinx-autodoc-typehints", specifier = ">=2.5.0" }, + { name = "sphinx-design", specifier = ">=0.6.1" }, + { name = "sphinx-gallery", specifier = ">=0.19.0" }, + { name = "sphinx-hoverxref", specifier = ">=1.4.1" }, + { name = "sphinxcontrib-mermaid", specifier = ">=1.0.0" }, + { name = "toml", specifier = ">=0.10.2" }, +] + +[[package]] +name = "myst-parser" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, +] [[package]] name = "narwhals" @@ -1767,6 +2093,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/bb/4a97aaa840b26601d6d04deca1389c35025336428706a4a732051187fbd3/pydata_sphinx_theme-0.17.0.tar.gz", hash = "sha256:529c5631582cb3328cf4814fb9eb80611d1704c854406d282a75c9c86e3a1955", size = 4990605, upload-time = "2026-04-03T13:02:20.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/b7/a2bae25aae3568fe9f17040b31f9c190b4c5d86856d8869d0a30364a2567/pydata_sphinx_theme-0.17.0-py3-none-any.whl", hash = "sha256:cec5c92f41f4a11541b6df8210c446b4aa9c3badb7fcf2db7893405b786d5c99", size = 6820685, upload-time = "2026-04-03T13:02:18.09Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -1938,6 +2310,103 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rinoh-typeface-dejavuserif" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rinohtype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/d1/e221568b41d6205356ffa6340dcee02acf86d92dec5e5b04798c141b3472/rinoh-typeface-dejavuserif-0.1.3.tar.gz", hash = "sha256:8be129230ac98ab2ebfbf5b570575052ba7069e4087ce36a2d4c1d85182833ce", size = 1666226, upload-time = "2020-12-07T15:22:02.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a7/c3df4e93137f390d28849ee1fd7578adee2b6322dca33082c7173ac2135f/rinoh_typeface_dejavuserif-0.1.3-py3-none-any.whl", hash = "sha256:35ba67bf25e526b4b8180dc31d8fa2ff9b594d924e8790e8074699cd2f0e7da8", size = 1667463, upload-time = "2020-12-07T15:21:58.999Z" }, +] + +[[package]] +name = "rinoh-typeface-texgyrecursor" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rinohtype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/70/b0a661118e7a93d1abf4375543644b054bb74b258924d62dd898b9860a71/rinoh-typeface-texgyrecursor-0.1.1.tar.gz", hash = "sha256:076f7dbbd0201b0d44f4c77e4f7070474e1365152dc8509b5cded04c1648308a", size = 239925, upload-time = "2016-07-18T13:31:16.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/21/1b46f0a50dfea435f90f29595d2e0b3d3c15a31b35bdde725942c83f2644/rinoh_typeface_texgyrecursor-0.1.1-py3-none-any.whl", hash = "sha256:98080e6af2271e67cff1d69033cb6c3a6baa58de41a66dce1a1102b4dd41de72", size = 242415, upload-time = "2016-07-18T13:31:20.398Z" }, +] + +[[package]] +name = "rinoh-typeface-texgyreheros" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rinohtype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/3b28c30558fd53590797c455bb46ab47599c2b9145a09e7ae162dd625426/rinoh-typeface-texgyreheros-0.1.1.tar.gz", hash = "sha256:fd7082e917bccc292894447f11d15a3efce9f1386056118b57b66cc2f5a36bbd", size = 522029, upload-time = "2016-07-18T13:32:49.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/0d/b428d4cf5ad3bd6848b617570d5dcb061b3f66a68b72e96bf2a7909a2300/rinoh_typeface_texgyreheros-0.1.1-py3-none-any.whl", hash = "sha256:0c921a040a84b0af031e4a36d196d46b65fb18929d88df5ed26533b57100e6c9", size = 523725, upload-time = "2016-07-18T13:32:53.338Z" }, +] + +[[package]] +name = "rinoh-typeface-texgyrepagella" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rinohtype" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/2a/86db888c244f11bb0f6fed72c75b09dba9dbf8aa72e7b69c27cf7975340f/rinoh-typeface-texgyrepagella-0.1.1.tar.gz", hash = "sha256:53f4dba338c6b1df758888f23ce1ed728e5be45746f161488ad3b944e5e79fd2", size = 319023, upload-time = "2016-07-18T13:33:36.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/44/c27695119e6ad918358d44a87f8543cab6240f1f768068dd32ba1b65d92e/rinoh_typeface_texgyrepagella-0.1.1-py3-none-any.whl", hash = "sha256:0144e3b828a31b405ab9be1dec67f48be360d9f86d109578924fd1d7e0e1ded6", size = 321426, upload-time = "2016-07-18T13:33:39.855Z" }, +] + +[[package]] +name = "rinohtype" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "docutils" }, + { name = "myst-parser" }, + { name = "packaging" }, + { name = "rinoh-typeface-dejavuserif" }, + { name = "rinoh-typeface-texgyrecursor" }, + { name = "rinoh-typeface-texgyreheros" }, + { name = "rinoh-typeface-texgyrepagella" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/21/7e2e32b16b5625a651b4f6773e1fe6ae10449f8c1f84b6731797a722a6a4/rinohtype-0.5.5.tar.gz", hash = "sha256:2efc38b5f41541693617e5a4bc6826e8932897d3c14b1b0ecd3ae4aaff404224", size = 6452152, upload-time = "2024-07-13T14:58:43.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/06/b9a641899db78cbd38c2d57d1878a499546336b6b844e79021b5f362fa54/rinohtype-0.5.5-py3-none-any.whl", hash = "sha256:b1c429e69c55b1b2400367ac0fcc17f30ec537ceb22e8f2dd768909282c2e642", size = 611480, upload-time = "2024-07-13T14:58:41.574Z" }, +] + +[[package]] +name = "roman" +version = "5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/7c/3901b35ed856329bf98e84da8e5e0b4d899ea0027eee222f1be42a24ff3f/roman-5.2.tar.gz", hash = "sha256:275fe9f46290f7d0ffaea1c33251b92b8e463ace23660508ceef522e7587cb6f", size = 8185, upload-time = "2025-11-11T08:03:57.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/14/ea3cdd7276fcd731a9003fe4abeb6b395a38110ddff6a6a509f4ee00f741/roman-5.2-py3-none-any.whl", hash = "sha256:89d3b47400388806d06ff77ea77c79ab080bc127820dea6bf34e1f1c1b8e676e", size = 6041, upload-time = "2025-11-11T08:03:56.051Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "roman-numerals" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, +] + [[package]] name = "rpds-py" version = "0.30.0" @@ -2021,14 +2490,14 @@ wheels = [ [[package]] name = "ruamel-yaml" -version = "0.18.17" +version = "0.18.16" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ruamel-yaml-clib", marker = "python_full_version < '3.15' and platform_python_implementation == 'CPython'" }, + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c", size = 149602, upload-time = "2025-12-17T20:02:55.757Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d", size = 121594, upload-time = "2025-12-17T20:02:07.657Z" }, + { url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba", size = 119858, upload-time = "2025-10-22T17:53:59.012Z" }, ] [[package]] @@ -2240,6 +2709,258 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/4f/4fd5583678bb7dc8afa69e9b309e6a99ee8d79ad3a4728f4e52fd7cb37c7/sphinx_autodoc_typehints-3.5.2.tar.gz", hash = "sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb", size = 37839, upload-time = "2025-10-16T00:50:15.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl", hash = "sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c", size = 21184, upload-time = "2025-10-16T00:50:13.973Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + +[[package]] +name = "sphinx-gallery" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pillow" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/14/9238ac61932299b38c20c7c37dbfe60348c0348ea4d400f9ef25875b3bf7/sphinx_gallery-0.20.0.tar.gz", hash = "sha256:70281510c6183d812d3595957005ccf555c5a793f207410f6cd16a25bf08d735", size = 473502, upload-time = "2025-12-02T15:51:37.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/fd/818a53d4da56ef2da7b08f77bb3a825635941d1fcc6b6a490995dec1a81c/sphinx_gallery-0.20.0-py3-none-any.whl", hash = "sha256:188b7456e269649945825661b76cdbfbf0b70c2cfd5b75c9a11fe52519879e4d", size = 458655, upload-time = "2025-12-02T15:51:35.311Z" }, +] + +[[package]] +name = "sphinx-hoverxref" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/6d/89764d3c719ed172d94778a74661cca647b2475165ae6cde1f73c9524d63/sphinx_hoverxref-1.4.2.tar.gz", hash = "sha256:74fab961b1b8c0e9c2cf22fa195c0e783d635e78686d39dd06ff157c196ca0c9", size = 1715764, upload-time = "2024-11-18T18:01:34.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/3b/d981175d7b9bbbcf6894faf640d15198394efc7dedd6182332c4a791a5a6/sphinx_hoverxref-1.4.2-py2.py3-none-any.whl", hash = "sha256:4fc2e283e908d9df61ea9196589934944a7e2e6e3cb753a420b938cd6d14e220", size = 32198, upload-time = "2024-11-18T18:01:31.473Z" }, +] + +[[package]] +name = "sphinx-jinja2-compat" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "standard-imghdr", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/98/43313781f29e8c6c46fec907430310172d6f207e95e4fbea9289990fbbfe/sphinx_jinja2_compat-0.4.1.tar.gz", hash = "sha256:0188f0802d42c3da72997533b55a00815659a78d3f81d4b4747b1fb15a5728e6", size = 5222, upload-time = "2025-08-06T20:06:25.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/c8/4fd58c1000d7f8f5572c507f4550d2e2d9741e500c68eb2e3da17cbe5a85/sphinx_jinja2_compat-0.4.1-py3-none-any.whl", hash = "sha256:64ca0d46f0d8029fbe69ea612793a55e6ef0113e1bba4a85d402158c09f17a14", size = 8123, upload-time = "2025-08-06T20:06:24.947Z" }, +] + +[[package]] +name = "sphinx-prompt" +version = "1.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "docutils" }, + { name = "idna" }, + { name = "jinja2" }, + { name = "pygments" }, + { name = "requests" }, + { name = "sphinx" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/a3/91293c0e0f0b76d0697ba7a41541929ca3f5457671d008bd84a9bde17e21/sphinx_prompt-1.10.2.tar.gz", hash = "sha256:47b592ba75caebd044b0eddf7a5a1b6e0aef6df587b034377cd101a999b686ba", size = 5566, upload-time = "2025-11-28T09:23:18.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/f4/44ce4d0179fb4e9cfe181a8aa281bba23e40158a609fb3680774529acaaa/sphinx_prompt-1.10.2-py3-none-any.whl", hash = "sha256:6594337962c4b1498602e6984634bed4a0dc7955852e3cfc255eb0af766ed859", size = 7474, upload-time = "2025-11-28T09:23:17.154Z" }, +] + +[[package]] +name = "sphinx-tabs" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/32/ab475e252dc2b704e82a91141fa404cdd8901a5cf34958fd22afacebfccd/sphinx-tabs-3.4.5.tar.gz", hash = "sha256:ba9d0c1e3e37aaadd4b5678449eb08176770e0fc227e769b6ce747df3ceea531", size = 16070, upload-time = "2024-01-21T12:13:39.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9f/4ac7dbb9f23a2ff5a10903a4f9e9f43e0ff051f63a313e989c962526e305/sphinx_tabs-3.4.5-py3-none-any.whl", hash = "sha256:92cc9473e2ecf1828ca3f6617d0efc0aa8acb06b08c56ba29d1413f2f0f6cf09", size = 9904, upload-time = "2024-01-21T12:13:37.67Z" }, +] + +[[package]] +name = "sphinx-toolbox" +version = "4.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "apeye" }, + { name = "autodocsumm" }, + { name = "beautifulsoup4" }, + { name = "cachecontrol", extra = ["filecache"] }, + { name = "dict2css" }, + { name = "docutils" }, + { name = "domdf-python-tools" }, + { name = "filelock" }, + { name = "html5lib" }, + { name = "roman" }, + { name = "ruamel-yaml" }, + { name = "sphinx" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-jinja2-compat" }, + { name = "sphinx-prompt" }, + { name = "sphinx-tabs" }, + { name = "tabulate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/16/769454374719569f6165149a16d06b8da27cda8330fe5f932c2c65a77a04/sphinx_toolbox-4.1.2.tar.gz", hash = "sha256:c30a4f86c4c29e97adb0eb9337d35f5093cb96a44f49caffcf7d5bc58a88b781", size = 114911, upload-time = "2026-01-14T17:33:02.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/38/6763a5a981770a21a18b66d3593a8bbb801884d9828c126866efdd01d41a/sphinx_toolbox-4.1.2-py3-none-any.whl", hash = "sha256:0438f8342ba1c6c0d6e47207f4eac167adba61742e8c2b1dc9624ff955b7bc89", size = 196812, upload-time = "2026-01-14T17:33:00.51Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/ae/999891de292919b66ea34f2c22fc22c9be90ab3536fbc0fca95716277351/sphinxcontrib_mermaid-2.0.1.tar.gz", hash = "sha256:a21a385a059a6cafd192aa3a586b14bf5c42721e229db67b459dc825d7f0a497", size = 19839, upload-time = "2026-03-05T14:10:41.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/46/25d64bcd7821c8d6f1080e1c43d5fcdfc442a18f759a230b5ccdc891093e/sphinxcontrib_mermaid-2.0.1-py3-none-any.whl", hash = "sha256:9dca7fbe827bad5e7e2b97c4047682cfd26e3e07398cfdc96c7a8842ae7f06e7", size = 14064, upload-time = "2026-03-05T14:10:40.533Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.45" @@ -2275,6 +2996,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, ] +[[package]] +name = "standard-imghdr" +version = "3.10.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/d2/2eb5521072c9598886035c65c023f39f7384bcb73eed70794f469e34efac/standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52", size = 5474, upload-time = "2024-04-21T18:55:10.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/d0/9852f70eb01f814843530c053542b72d30e9fbf74da7abb0107e71938389/standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2", size = 5598, upload-time = "2024-04-21T18:54:48.587Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + [[package]] name = "threadpoolctl" version = "3.6.0" @@ -2284,6 +3023,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, ] +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tornado" version = "6.5.4" From ac4303c9d40ef6cab82948b1b41efb2627c3a481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sun, 19 Apr 2026 07:05:47 +0200 Subject: [PATCH 15/17] Update CHANGELOG Unreleased with RNG accessor, NaN guards, elephant extra, figure changes --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac7f419..c9464568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **RNG accessors**: new public API for reproducible random draws + - `myogen.get_random_generator()` — always returns the current global RNG (tracks the latest `set_random_seed` call) + - `myogen.get_random_seed()` — returns the seed currently in effect + - `myogen.derive_subseed(*labels)` — deterministic sub-seed helper for seeding non-NumPy generators (Cython spike generators, sklearn `random_state`, etc.) so they also track `set_random_seed` +- **Optional `elephant` extra**: install via `pip install myogen[elephant]`. Core install no longer pulls elephant; the five modules that import it (`utils/helper.py`, `surface_emg.py`, `intramuscular_emg.py`, `force_model.py`, `force_model_vectorized.py`) were already guarded and now emit an accurate install hint when the extra is missing +- **Test suite** under `tests/` + - `test_determinism.py` — 10 regressions covering seed propagation, sub-seed collision avoidance, and deprecation warnings for the legacy RNG names + - `test_firing_rate_statistics.py` — 4 regressions covering `FR_std` and `CV_ISI` NaN edge cases + - Added `pytest>=8.0` to the `dev` dependency-group and a `[tool.pytest.ini_options]` section +- **Per-muscle ISI/CV figure**: new `plot_cv_vs_fr_per_muscle()` in `examples/02_finetune/05_plot_isi_cv_multi_muscle_comparison.py` emits a 3-panel VL / VM / FDI breakdown alongside the existing pooled plot + +### Changed +- **RNG architecture** (internal refactor): the six internal modules and eight example scripts that previously did `from myogen import RANDOM_GENERATOR, SEED` at module scope now call the accessor at each site, eliminating the stale-reference bug where `set_random_seed` didn't reach downstream modules or seed-derived generators +- **Per-cell seed derivation**: the three sites in `myogen/simulator/neuron/cells.py` that built a per-cell seed as `SEED + (class_id+1)*(global_id+1)` — a formula that collided on swapped factors, e.g. `(0, 5)` and `(1, 2)` both resolved to `+6` — now use `derive_subseed(class_id, global_id)` (collision-free with respect to label order, built on `numpy.random.SeedSequence`). The `motor_unit_sim.py` `KMeans` `random_state` uses the same helper. **Consequence**: for a given global seed, RNG output differs from earlier releases; byte-identical reproduction of pre-`Unreleased` outputs is not possible without matching code +- **Example figures**: bar graphs in `02_finetune/01_optimize_dd_for_target_firing_rate.py`, `02_finetune/02_compute_force_from_optimized_dd.py` and `03_papers/watanabe/01_compute_baseline_force.py` replaced with dumbbell / violin + box + jittered-scatter plots that show the underlying distribution (all points when n<10, violin + box when n≥10) +- **NEURON version alignment**: `README.md`, `docs/source/README.md`, `docs/source/index.md`, and `setup.py` Windows-install messaging now reference NEURON **8.2.7** (matching the Linux/macOS pip pin in `pyproject.toml` and the CI workflow) with the correct installer filename (`py-39-310-311-312-313`) +- **Docs build**: pinned `sphinx<9` in the `docs` dependency-group to work around a `sphinx-hoverxref` regression on Sphinx 9, and moved `[tool.pytest.ini_options]` in `pyproject.toml` so it no longer re-parents the `docs` dependency-group + +### Deprecated +- **`myogen.RANDOM_GENERATOR`** and **`myogen.SEED`** module attributes. They remain accessible via a module-level `__getattr__` that emits a `DeprecationWarning` and returns the current RNG / current seed. External code should migrate to `get_random_generator()` / `get_random_seed()`; module-level `from myogen import RANDOM_GENERATOR` captures a stale reference and does not reflect later `set_random_seed` calls + +### Fixed +- **`FR_std = NaN` with a single active unit**: `myogen/utils/helper.py::calculate_firing_rate_statistics` now returns `FR_std = 0.0` when fewer than two units pass the firing-rate filter, instead of propagating the `np.std(..., ddof=1)` NaN into downstream ensemble statistics +- **`CV_ISI = NaN` for n=1 ISI**: same function's per-neuron branch now returns `0.0` when `min_spikes_for_cv` is set to 2 and a neuron has exactly two spikes +- **`set_random_seed` did not propagate**: module-level `from myogen import RANDOM_GENERATOR, SEED` imports in the internal modules captured a stale reference at import time; reseeding the package rebinding did not affect them. The accessor refactor above is the fix +- **Typos / placeholder docstrings**: `extandable` → `extensible` in README and docs; `"as determined by no one"` docstring comments in `myogen/simulator/core/muscle/muscle.py` replaced with concrete physiological rationale + ## [0.8.5] - 2026-01-15 ### Fixed From 4c9ff67b6c60dc81578980dae7d4e985c30946e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sun, 19 Apr 2026 07:18:54 +0200 Subject: [PATCH 16/17] Make elephant truly optional: move viziphant to the extra, add neo to core Codex review flagged that the previous "elephant is an optional extra" change was only half-true: the core install still pulled viziphant, which in turn pulled elephant transitively. Users running `pip install myogen` therefore still received elephant, contradicting the documented intent. Fix: - Moved `viziphant>=0.4.0` out of core `[project.dependencies]` into the `elephant` optional-dependency bundle, where it lives alongside `elephant>=1.1.1`. The single `from viziphant.rasterplot import ...` use site is `examples/01_basic/02_simulate_spike_trains_current_injection.py`, which also imports elephant directly, so it already requires the elephant extra. - Added `neo>=0.14.0` as an explicit core dependency. It was reached transitively via `viziphant -> neo`, so dropping viziphant from core would otherwise break `import myogen` (12 direct import sites in core, including `myogen/utils/continuous_saver.py` which is on the top-level import path). - Added `elephant` and `viziphant` to the docs dep-group so sphinx-gallery can execute the examples that depend on them. Verification: `uv sync` followed by `python -c "import importlib.util; print(importlib.util.find_spec('elephant'))"` now prints `None`; `import myogen` succeeds; 14/14 tests pass. Also: - Documented `derive_subseed`'s non-negative-label precondition in the docstring (SeedSequence rejects negatives). - Added a `tests` GitHub Actions workflow that runs pytest on every PR to main (the existing workflows only build wheels, smoke-test imports, or publish docs on release). CHANGELOG updated to reflect the true scope. --- .github/workflows/tests.yml | 39 +++++++++++++++++++++++++++++++++++++ .gitignore | 4 ++++ CHANGELOG.md | 2 +- myogen/__init__.py | 5 ++++- pyproject.toml | 8 +++++++- uv.lock | 10 ++++++++-- 6 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..93b103b9 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,39 @@ +name: tests + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12.8" + + - name: System packages for NEURON / MPI + run: | + sudo apt-get update + sudo apt-get install -y libopenmpi-dev openmpi-bin libgtk2.0-dev + + - name: Install uv + run: | + python -m pip install --upgrade pip + python -m pip install uv + + - name: Install project with dev group + run: | + uv sync --group dev + + - name: Compile NEURON mechanisms + run: uv run poe setup_myogen + + - name: Run pytest + run: uv run pytest tests/ -v diff --git a/.gitignore b/.gitignore index 805e3729..5e58b558 100644 --- a/.gitignore +++ b/.gitignore @@ -175,10 +175,14 @@ test_*.png myogen/simulator/neuron/_cython/*.c myogen/simulator/nmodl_files/x86_64/ +myogen/simulator/nmodl_files/arm64/ myogen/simulator/x86_64/ +myogen/simulator/arm64/ myogen/simulator/nmodl_files/*.c myogen/simulator/nmodl_files/*.o myogen/simulator/nmodl_files/*.dll +myogen/simulator/nmodl_files/*.dylib +myogen/simulator/nmodl_files/*.so data/ # Note: examples/data/ is NOT ignored - it contains pre-generated static data for docs diff --git a/CHANGELOG.md b/CHANGELOG.md index c9464568..ef062604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `myogen.get_random_generator()` — always returns the current global RNG (tracks the latest `set_random_seed` call) - `myogen.get_random_seed()` — returns the seed currently in effect - `myogen.derive_subseed(*labels)` — deterministic sub-seed helper for seeding non-NumPy generators (Cython spike generators, sklearn `random_state`, etc.) so they also track `set_random_seed` -- **Optional `elephant` extra**: install via `pip install myogen[elephant]`. Core install no longer pulls elephant; the five modules that import it (`utils/helper.py`, `surface_emg.py`, `intramuscular_emg.py`, `force_model.py`, `force_model_vectorized.py`) were already guarded and now emit an accurate install hint when the extra is missing +- **Optional `elephant` extra**: install via `pip install myogen[elephant]`. The extra bundles both `elephant>=1.1.1` and `viziphant>=0.4.0` (viziphant was moved out of core dependencies because its only transitive import chain in core came from `elephant`). Core install is now genuinely elephant-free — verified with `importlib.util.find_spec('elephant') is None` on a fresh `uv sync`. The five modules that import elephant (`utils/helper.py`, `surface_emg.py`, `intramuscular_emg.py`, `force_model.py`, `force_model_vectorized.py`) were already guarded with `try/except ImportError` and now emit an accurate install hint when the extra is missing. `neo>=0.14.0` is now an explicit core dependency — it was previously only reached transitively through `elephant`/`viziphant`, so removing those would otherwise break `import myogen` - **Test suite** under `tests/` - `test_determinism.py` — 10 regressions covering seed propagation, sub-seed collision avoidance, and deprecation warnings for the legacy RNG names - `test_firing_rate_statistics.py` — 4 regressions covering `FR_std` and `CV_ISI` NaN edge cases diff --git a/myogen/__init__.py b/myogen/__init__.py index 6967f39f..c71d3369 100644 --- a/myogen/__init__.py +++ b/myogen/__init__.py @@ -32,7 +32,10 @@ def derive_subseed(*labels: int) -> int: generators, sklearn ``random_state``, etc.) so that a call to :func:`set_random_seed` propagates to them. Label order matters: ``derive_subseed(a, b)`` and ``derive_subseed(b, a)`` yield different - sub-seeds. + sub-seeds. Each label must be a **non-negative** integer; callers with + signed identifiers should offset them beforehand (NumPy's + :class:`~numpy.random.SeedSequence`, which backs this helper, rejects + negatives). This replaces the pre-existing ``SEED + (class_id+1)*(global_id+1)`` derivation, which collided on swapped factors — e.g. ``(0, 5)`` and diff --git a/pyproject.toml b/pyproject.toml index 9af70ef5..38c15e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "neuron==8.2.7 ; sys_platform == 'linux' or sys_platform == 'darwin'", #"neuron-gpu-nightly>=9.0a0", #"neuron-nightly>=9.0a1.dev1492", + "neo>=0.14.0", "numba>=0.61.2", "numpy>=1.26,<2.0", "optuna>=4.5.0", @@ -46,12 +47,12 @@ dependencies = [ "seaborn>=0.13.2", "setuptools>=80.9.0", "tqdm>=4.67.1", - "viziphant>=0.4.0", ] [project.optional-dependencies] elephant = [ "elephant>=1.1.1", + "viziphant>=0.4.0", ] nwb = [ "pynwb>=2.8.0", @@ -94,6 +95,11 @@ docs = [ # NWB support for examples "pynwb>=2.8.0", "nwbinspector>=0.5.0", + # Spike-train analysis + visualization needed by several examples + # (elephant is also declared as an optional extra; the docs build + # needs both for sphinx-gallery to render those examples). + "elephant>=1.1.1", + "viziphant>=0.4.0", ] [tool.pytest.ini_options] diff --git a/uv.lock b/uv.lock index 4bb26804..bb0cbc26 100644 --- a/uv.lock +++ b/uv.lock @@ -1487,6 +1487,7 @@ dependencies = [ { name = "impi-rt", marker = "sys_platform == 'win32'" }, { name = "matplotlib" }, { name = "mpi4py" }, + { name = "neo" }, { name = "neuron", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "numba" }, { name = "numpy" }, @@ -1499,12 +1500,12 @@ dependencies = [ { name = "seaborn" }, { name = "setuptools" }, { name = "tqdm" }, - { name = "viziphant" }, ] [package.optional-dependencies] elephant = [ { name = "elephant" }, + { name = "viziphant" }, ] nwb = [ { name = "nwbinspector" }, @@ -1519,6 +1520,7 @@ dev = [ { name = "scipy-stubs" }, ] docs = [ + { name = "elephant" }, { name = "enum-tools", extra = ["sphinx"] }, { name = "linkify-it-py" }, { name = "memory-profiler" }, @@ -1535,6 +1537,7 @@ docs = [ { name = "sphinx-hoverxref" }, { name = "sphinxcontrib-mermaid" }, { name = "toml" }, + { name = "viziphant" }, ] [package.metadata] @@ -1545,6 +1548,7 @@ requires-dist = [ { name = "impi-rt", marker = "sys_platform == 'win32'", specifier = ">=2021.15.0" }, { name = "matplotlib", specifier = ">=3.10.1" }, { name = "mpi4py", specifier = ">=4.0.3" }, + { name = "neo", specifier = ">=0.14.0" }, { name = "neuron", marker = "sys_platform == 'darwin' or sys_platform == 'linux'", specifier = "==8.2.7" }, { name = "numba", specifier = ">=0.61.2" }, { name = "numpy", specifier = ">=1.26,<2.0" }, @@ -1559,7 +1563,7 @@ requires-dist = [ { name = "seaborn", specifier = ">=0.13.2" }, { name = "setuptools", specifier = ">=80.9.0" }, { name = "tqdm", specifier = ">=4.67.1" }, - { name = "viziphant", specifier = ">=0.4.0" }, + { name = "viziphant", marker = "extra == 'elephant'", specifier = ">=0.4.0" }, ] provides-extras = ["elephant", "nwb"] @@ -1571,6 +1575,7 @@ dev = [ { name = "scipy-stubs", specifier = ">=1.16.1.0" }, ] docs = [ + { name = "elephant", specifier = ">=1.1.1" }, { name = "enum-tools", extras = ["sphinx"], specifier = ">=0.12.0" }, { name = "linkify-it-py", specifier = ">=2.0.3" }, { name = "memory-profiler", specifier = ">=0.61.0" }, @@ -1587,6 +1592,7 @@ docs = [ { name = "sphinx-hoverxref", specifier = ">=1.4.1" }, { name = "sphinxcontrib-mermaid", specifier = ">=1.0.0" }, { name = "toml", specifier = ">=0.10.2" }, + { name = "viziphant", specifier = ">=0.4.0" }, ] [[package]] From c3bcb87b38449e886490ee5f09f7f3f6096dd45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raul=20C=2E=20S=C3=AEmpetru?= Date: Sun, 19 Apr 2026 07:20:29 +0200 Subject: [PATCH 17/17] Also run tests workflow on improved-semg branch (Codex review) Codex noted the tests workflow was scoped only to main, which means merging reviewer_suggestions into improved-semg wouldn't trigger CI on that integration branch. Added improved-semg to both the pull_request and push triggers. --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 93b103b9..6260b94e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,9 @@ name: tests on: pull_request: - branches: [main] + branches: [main, improved-semg] push: - branches: [main] + branches: [main, improved-semg] workflow_dispatch: jobs: