Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

This is a breaking release. It corrects the surface-EMG pipeline against Farina, Mesin, Martina & Merletti (2004). The problems were found through feedback from @joaohbbittar; thank you. Both the default route and `execution=` are affected.

It also ports three fixes that never reached `main` (Gfluctdv noise, fibre conduction velocities, large fibre layouts) and corrects the units of the intramuscular SFAP.
It also ports three fixes that never reached `main` (Gfluctdv noise, fibre conduction velocities, large fibre layouts), corrects the units of the intramuscular SFAP and adds the multilevel descending-drive optimiser used to calibrate an FDI pool.

### Breaking changes

Expand Down Expand Up @@ -62,6 +62,17 @@ This release changes default outputs; regenerate anything calibrated against ear

The realised spreads are reported per motor unit in `SurfaceEMG.fiber_dispersion_report`. The default is off and leaves outputs bit-identical. `SurfaceFiberDispersion.literature_candidate()` gives a preset whose values come from the literature or are marked as assumptions. The `execution=` route and `use_unified=True` refuse an active configuration.
- `IntramuscularEMG(nmj_coefficients=...)` selects the end-plate spreads (see Changed).
- `ForceModel(force_frequency_shape=(a, c))`: shape of the force-frequency saturation S(r) = 1 − exp(−a r^c) in the Fuglevand gain. The default (2, 3) is Fuglevand et al. 1993 and leaves outputs unchanged. `force_utils.HUMAN_UNIT_FORCE_FREQUENCY` (0.93, 1.29) is fitted to human single motor units stimulated through their axons (Macefield et al. 1996, Fuglevand et al. 1999, McNulty et al. 2000): units keep gaining force up to ~30–50 Hz, where Fuglevand's curve saturates by ~20 Hz for slow units. It also gives the measured twitch:tetanus ratio (0.23; Fuglevand 0.11).
- **Multilevel descending-drive optimiser.** `examples/02_finetune/06_optimize_dd_multilevel.py` fits the descending-drive network (and the Gfluctdv noise scale) to rate-coding targets over a trapezoidal ramp, sequentially or with parallel Optuna workers. Supporting modules: `myogen.utils.fr_optimization_targets` (Fuglevand, De Luca and experimental targets), `myogen.utils.ramp_analysis` and `myogen.utils.parallel_budget` (worker count from free RAM and CPUs).
- Every motor neuron receives the same number of descending-drive inputs, and each input's weight is scaled by the neuron's dendrite-to-soma current transfer, so one input delivers the same current to a clamped soma whatever the neuron's size and recruitment follows rheobase. With random input counts and one weight, large neurons received ~2× less current per input and the largest units were recruited late and out of order. Both are modelling assumptions (equal effective synaptic current per input, the equal-share case of Heckman & Binder 1991).
- `--last-recruit-mvc PCT`: each trial also runs an MVC ramp (0.5 s rest, excitation 0 → 1 at 0.1/s, 3 s hold; force from `ForceModel`, MVC = mean of the last 2 s) and penalises the distance between the force (% MVC) at which the last unit is recruited and PCT. The calibration ramp stops at 30 % MVC, so this ceiling was previously unconstrained. Off by default. `07_fdi_spikes_force.py` uses the same MVC ramp.
- `--ceiling-rank K` scores the unit with K units recruited after it instead of the last unit (less sensitive to one unit's noise). `--force-frequency human` uses the fitted force-frequency curve below for the MVC ramp. `--high-force-rates` also scores the MVC-ramp firing rates at 40 and 80 % MVC against De Luca et al. 1982 (25.3 and 41.4 Hz); the calibration ramp never reaches them. All off by default.
- `--fit-weight-exponent` also fits p in [0, 1] for the DD input weights `w·(k_max/k_i)^p`; it is recorded as `network.weight_exponent`. The default p = 1 (equal current per input at rest) is unchanged, and files without the key use it. With p = 1, large units fire as fast as small ones at high force (no onion skin). Lower p gives large units less current per input.
- Parameter files record the wiring in a `network` block; files without it are refused.
- `examples/02_finetune/07_fdi_spikes_force.py` maps excitation to % MVC with `ForceModel` on an MVC ramp and writes spike trains for force-matched trapezoids.
- `myogen.utils.ramp_analysis`: recruitment and firing rates at % MVC checkpoints of a ramp (`extract_ramp_recruitment`), the force (% MVC) at which each unit is recruited on an MVC ramp (`recruitment_force_pct_mvc`), and unit conduction velocities from recruitment force (`cv_from_recruitment_force`, Del Vecchio et al. 2018; with the residual scatter of their fit by default).
- `Network.connect_fan_in(source, target, n_inputs, weights__uS)` gives every target neuron exactly `n_inputs` excitatory inputs, drawn without replacement, with one weight per target.
- `docs/literature.md`: register of the sources behind these defaults and calibrations, each linked to the code that uses it, with verification status (full text, abstract, secondary) and corrections. It is generated by `scripts/build_literature.py`; sourced constants cite it in the code as `[key locator]`, e.g. `[deluca1982 p. 118 Table 1]`, and `tests/test_literature_register.py` fails when the register is stale or the code cites an unknown key.
- `benchmarks/semg_dispersion/`: paired sweeps of these options on a limb-like cylinder and on the default (FDI) muscle.

### Changed
Expand Down
8 changes: 5 additions & 3 deletions docs/gallery_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ def reset_neuron(gallery_conf, fname):
# ``__file__`` (undefined under mkdocs-gallery's exec()) to locate a results
# directory and load each other's ``.pkl`` outputs, so they cannot execute
# cleanly in the gallery — they render source-only. The adaptive CPU CLI
# also requires arguments and user-provided input files, so render its
# source without executing it. Keep scripts/build_docs.py in sync.
# also requires arguments and user-provided input files, and the multilevel
# descending-drive optimiser and its spike/force script use ``__file__`` and
# long runs (07 takes arguments), so render their source without executing
# them. Keep SOURCE_ONLY in scripts/build_docs.py in sync.
# When MKDOCS_GALLERY_PLOT is false, everything is source-only regardless.
"filename_pattern": r"/(01_basic|02_finetune)/(?!16_adaptive_cpu_execution\.py$)",
"filename_pattern": r"/(01_basic|02_finetune)/(?!16_adaptive_cpu_execution\.py$|06_optimize_dd_multilevel\.py$|07_fdi_spikes_force\.py$)",
"ignore_pattern": r"(14_calibrate_noise_from_real|_oscillating_dc_helpers|_optimize_dc_worker|_pic_protocols)\.py",
"within_subsection_order": FileNameSortKey,
"image_scrapers": ("matplotlib",),
Expand Down
Loading
Loading