Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
3f81754
feat(finetune): port the multilevel DD optimiser and FDI force mappin…
DRohlf Nov 1, 2026
249f709
fix(neuron): enable Gfluctdv conductance noise when it is inserted
DRohlf Nov 1, 2026
a179c1f
docs(pr): checklist of changes and recommendations for the upstream PR
DRohlf Nov 1, 2026
3c2f4c2
fix(muscle): fibre CV from the diameter line in mm; uncapped circular…
DRohlf Nov 1, 2026
288155c
docs(pr): record full-suite result
DRohlf Nov 1, 2026
d1d9f1c
docs(pr): skin sigma is an effective cylinder parameter; FE model inp…
DRohlf Nov 1, 2026
16eee41
bench(semg): FDI bipolar and Delsys arms on main-calibrated 25 % MVC …
DRohlf Nov 1, 2026
620bca8
docs(pr): ForceModel twitch parameters against Milner-Brown 1973 FDI …
DRohlf Nov 1, 2026
96b91ab
docs(pr): extra-trap option is already ported
DRohlf Nov 1, 2026
e434dd5
docs(pr): Delsys retest on main-calibrated trains and IAP/CV MDF item
DRohlf Nov 1, 2026
00cbc41
docs(pr): Delsys IAP length and CV probe results
DRohlf Nov 1, 2026
190d57b
docs(pr): qualify λ 0.7 match to adjacent-pin channel
DRohlf Nov 1, 2026
d385bcd
docs(pr): IAP length parameter added, default kept; hashes after rebase
DRohlf Nov 1, 2026
33c44ac
docs(pr): full suite on rebased calibration branch
DRohlf Nov 1, 2026
8207d18
docs: CHANGELOG for ported fixes and optimiser; PR doc threshold chec…
DRohlf Nov 1, 2026
1224eb2
chore(finetune): drop machine-specific launch scripts; widen Gfluctdv…
DRohlf Nov 1, 2026
f006a01
fix(finetune): equal DD fan-in, size-compensated input weights, recru…
DRohlf Nov 1, 2026
893d150
docs: literature register for the sEMG defaults and FDI calibration
DRohlf Nov 1, 2026
f4cc0f6
docs(literature): mark the Akhmadeev 2020 citation fix
DRohlf Nov 1, 2026
87b310e
docs(pr): record the Akhmadeev citation fix
DRohlf Nov 1, 2026
c65e58f
docs(pr): calib branch rebased onto the citation fix
DRohlf Nov 1, 2026
15c8f62
feat(dd-opt): score the recruitment ceiling in % MVC
DRohlf Nov 1, 2026
91d7644
docs(pr): record the 55 % MVC ceiling recalibration result
DRohlf Nov 2, 2026
68cba71
docs(literature): add human hand motor-unit force-frequency sources
DRohlf Nov 2, 2026
01f9618
docs: force-frequency refit explains most of the FDI recruitment-ceil…
DRohlf Nov 2, 2026
39b1a17
feat(force,optimiser): human-unit force-frequency option, ranked ceil…
DRohlf Nov 2, 2026
8af2ec3
docs(pr): human59 recalibration result
DRohlf Nov 2, 2026
850c432
docs(literature): link the human-unit force curve and De Luca high-fo…
DRohlf Nov 2, 2026
be990a4
docs(pr): spinal diagnostics for the high-force rate deficit; two NER…
DRohlf Nov 2, 2026
2107873
docs(literature): motoneuron PIC and ΔF sources for the spinal diagno…
DRohlf Nov 2, 2026
5eb0ad2
docs(pr): input-weighting and Powers2017 probes for the high-force ra…
DRohlf Nov 2, 2026
15d0745
feat(optimiser): fit the DD input weight exponent (--fit-weight-expon…
DRohlf Nov 2, 2026
0f1eb54
docs(code): cite sourced constants by literature-register key with pa…
DRohlf Nov 2, 2026
b5d23ff
docs(literature): organise the register by topic; document the in-cod…
DRohlf Nov 2, 2026
eb55830
docs(literature): build the register from scripts/build_literature.py…
DRohlf Nov 2, 2026
c4686fe
docs(code): cite sourced values by literature-register key
DRohlf Nov 2, 2026
5851b02
docs(code): cite the iEMG code by register key; resolve Polgar, Power…
DRohlf Nov 2, 2026
c2a9e58
docs(pr): weight-exponent recalibration result (_human59_wexp)
DRohlf Nov 2, 2026
42a56ca
fix(iemg)!: SFAP units, IAP length scale, wave polarity and dz integr…
DRohlf Nov 2, 2026
334eb66
feat(semg,muscle)!: FDI fibre diameters from Polgar et al. (1973)
DRohlf Nov 2, 2026
46df924
docs: changelog and literature register for the iEMG unit fixes and P…
DRohlf Nov 2, 2026
937bc95
test(semg): exact-cylinder reference uses the 57.9 um default diamete…
DRohlf Nov 2, 2026
30d3b38
bench(semg): IAP length-scale and mean-CV harness keys; lambda x CV D…
DRohlf Nov 2, 2026
8b26538
docs(cite): MU CV vs threshold is Del Vecchio et al. 2018 (Acta Physi…
DRohlf Nov 2, 2026
33ad5c4
feat(semg): per-unit fibre CV option; recruitment-force CV arm
DRohlf Nov 2, 2026
60a6b1e
bench(semg): recruitment-force CV arms at lambda 0.7 and 0.5 (Botelho…
DRohlf Nov 2, 2026
630268b
docs(lit): Nandedkar 1983 and Polgar 1973 read in full; add Blijham 2006
DRohlf Nov 2, 2026
1136f58
docs(lit): Blijham 2006 read in full
DRohlf Nov 2, 2026
92ccd87
feat(muscle,semg)!: Polgar fibre-type diameter mixture; normalise d^2…
DRohlf Nov 2, 2026
77ac3ac
feat(muscle,semg)!: anchor fibre CV to the muscle mean; truncate diam…
DRohlf Nov 2, 2026
c6f095c
feat(muscle)!: fibre CV 3.89 +/- 0.50 m/s from Del Vecchio 2018
DRohlf Nov 2, 2026
8b5c8f7
docs(spec): fibre typing by motor unit (Draft)
DRohlf Nov 2, 2026
5e904a6
docs: record decisions on calibration, Gfluctdv, force-frequency, lam…
DRohlf Nov 2, 2026
c1331ab
feat(utils): recruitment force (% MVC) and CV from an MVC ramp
DRohlf Nov 2, 2026
c869ef2
docs(spec): fibre typing, builder-check calls
DRohlf Nov 2, 2026
c07acc5
docs(spec): fibre typing uses one diameter ladder across the pool (op…
DRohlf Nov 2, 2026
06fe3a7
docs(spec): fibre typing, combined tail truncation, Garnett limits, C…
DRohlf Nov 2, 2026
e7f3a79
docs(plans): fibre typing by motor unit; spec approved, CV anchor 4.3…
DRohlf Nov 2, 2026
a3dc1d7
feat(muscle): fibre-type ladder across the motor-unit pool
DRohlf Nov 2, 2026
69f1237
feat(muscle)!: type fibres by motor unit; fibre CV 4.32 +/- 0.71 m/s
DRohlf Nov 2, 2026
14af84e
docs: fibre typing by motor unit (changelog, PR doc, register)
DRohlf Nov 2, 2026
9624491
fix(muscle): fibre-type ladder stores its offset; input guards
DRohlf Nov 2, 2026
c9f39c0
fix(muscle): fibre-typing review minors
DRohlf Nov 2, 2026
3bbde23
docs: running backlog (named profiles, GitHub issues plan, open items)
DRohlf Nov 2, 2026
121546f
feat(semg): fibre property source option for surface dispersion
DRohlf Nov 2, 2026
d9dd47e
feat(semg): realise dispersion from Muscle fibre CVs and diameters
DRohlf Nov 2, 2026
130ee92
feat(semg): SurfaceEMG reads Muscle fibres for dispersion
DRohlf Nov 2, 2026
d3db695
bench(semg): Muscle-fibre arms for the lambda refit; docs
DRohlf Nov 2, 2026
c7bf633
docs(spec): settings inventory for named profiles
DRohlf Nov 2, 2026
3f119c5
docs(backlog): defects and duplicated authorities from the settings i…
DRohlf Nov 2, 2026
1ba5b18
fix(semg): review fixes for Muscle-fibre surface dispersion
DRohlf Nov 2, 2026
a30ff0a
docs(semg): lambda refit with Muscle fibres on the Botelho FDI benchmark
DRohlf Nov 3, 2026
3265550
bench(semg): within-unit CV CoV ablation arms for Muscle fibres
DRohlf Nov 3, 2026
fff5919
docs(literature): correct the Stalberg & Karlsson 2001 citation
DRohlf Nov 3, 2026
6b0ec67
docs(backlog): within-unit CV spread explains the low surface RMS
DRohlf Nov 3, 2026
8cc0bc9
docs(literature): Martin 1988, Andreassen 1987, Burke & Tsairis 1973
DRohlf Nov 3, 2026
6a2719c
feat(muscle): within-unit fibre diameter spread per type; CV SD over …
DRohlf Nov 3, 2026
d82a03e
feat(muscle): per-unit fibre CVs; fallback mean and SD over unit means
DRohlf Nov 3, 2026
84870f4
docs: register rebuild for the new Muscle citations; spec decision an…
DRohlf Nov 3, 2026
6c3cc91
docs: lambda refit with Martin within-unit spread and per-unit CVs (~…
DRohlf Nov 3, 2026
54458d1
feat(utils,muscle): Del Vecchio residual scatter on per-unit CVs; war…
DRohlf Nov 3, 2026
bad0a65
docs: CV scatter and bound warning; old-vs-new comparison
DRohlf Nov 3, 2026
5879036
docs(backlog): no further Botelho fitting (Devon)
DRohlf Nov 3, 2026
6129cbb
docs(pr): draft GitHub issues for the 2026-10-30 to 11-03 changes
DRohlf Nov 3, 2026
0ec4f1e
fix(muscle): per-unit CVs accept plain arrays (m/s) and any velocity …
DRohlf Nov 3, 2026
1aebf98
docs(pr): issues opened; PR routing along the branch history
DRohlf Nov 3, 2026
0cbe48f
docs(backlog): issues #29-#39 and PRs #40, #41 opened
DRohlf Nov 3, 2026
4626640
docs(changelog): thank @joaohbbittar for the feedback behind the surf…
DRohlf Nov 3, 2026
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
37 changes: 33 additions & 4 deletions CHANGELOG.md

Large diffs are not rendered by default.

1,475 changes: 1,475 additions & 0 deletions benchmarks/semg_dispersion/arms.json

Large diffs are not rendered by default.

62 changes: 53 additions & 9 deletions benchmarks/semg_dispersion/gen_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@
8-35 Hz rate coding with ISI CV 0.2, 2048 Hz. An empty configuration ({}) is the
undispersed baseline. Harness-only keys set the outer layers:
``_skin_thickness__mm`` (default 1.29), ``_fat_thickness__mm`` (limb 4, FDI 0.3),
``_skin_conductivity__S_per_m`` (default 1) and ``_fiber_diameter__um`` (default 50).
``_skin_conductivity__S_per_m`` (default 1) and ``_fiber_diameter__um`` (default: the ``SurfaceEMG`` default).
FDI only: ``_muscle_radius__mm`` (default 6.91) and ``_fiber_density__per_mm2`` (default 350).
``_spike_file`` (npz with ``spikes`` and ``thresholds``) replaces the generated spike trains.
``_iap_lambda__per_mm`` (default 1) sets the Rosenfalck IAP length scale (``SurfaceEMG.iap_lambda__per_mm``);
``_mean_cv__m_s`` (default: the ``Muscle`` default) sets the mean muscle-fibre conduction velocity.
``_fiber_cv_per_unit_file`` (json with ``cv__m_s``, one value per unit) sets each unit's mean fibre CV
(``SurfaceFiberDispersion.fiber_cv_per_unit__m_s``).
``fiber_properties: "muscle"`` (a ``SurfaceFiberDispersion`` field) takes every fibre's CV and diameter from the
``Muscle``: its fibre properties are generated after the assignment, with the global generator's state restored
afterwards so every other draw matches the earlier arms; their hash is saved as ``property_hash``.
``_iap_kernel_length__mm`` (FDI default 150) sets ``SurfaceEMG.iap_kernel_length__mm``; slow fibres at small
lambda need a longer window (the surface window check says so).
``_muscle_cv_within_cov`` (ablation, with ``fiber_properties: "muscle"``) rescales each unit's ``Muscle`` fibre CVs
about the unit mean to this coefficient of variation; unit means and diameters stay as generated.
``_muscle_unit_cv_file`` (json with ``cv__m_s``, one value per unit) sets each unit's mean fibre CV in the ``Muscle``
(``Muscle.motor_unit_conduction_velocities__m_per_s``).
FDI only: ``_fs__Hz`` (default 2048) sets the output and internal sampling rate. ``_delsys5`` replaces the
bipolar pair with Delsys 5-pin sensors (0.5 mm pins at the corners and centre of a 5 x 5 mm square, as in
Pereira Botelho et al. 2019): ``{"z__mm": [...], "rotation__deg": [...], "theta__deg": [...]}`` places one
Expand All @@ -41,7 +54,7 @@
from neo import Block, Segment, SpikeTrain

import myogen
from myogen import set_random_seed
from myogen import get_random_generator, set_random_seed
from myogen.simulator import Muscle, RecruitmentThresholds, SurfaceElectrodeArray, SurfaceEMG
from myogen.simulator.core.emg.surface.fiber_dispersion import SurfaceFiberDispersion
from myogen.utils.neo import signal_to_grid
Expand All @@ -53,13 +66,24 @@
skin = config.pop("_skin_thickness__mm", 1.29)
fat = config.pop("_fat_thickness__mm", 4.0 if scenario == "limb" else 0.3)
skin_sigma = config.pop("_skin_conductivity__S_per_m", 1.0)
diameter = config.pop("_fiber_diameter__um", 50.0)
diameter = config.pop("_fiber_diameter__um", None)
rate_model = config.pop("_rate_model", None)
spike_file = config.pop("_spike_file", None)
fdi_radius = config.pop("_muscle_radius__mm", 6.91)
fdi_density = config.pop("_fiber_density__per_mm2", 350.0)
fs_out = config.pop("_fs__Hz", 2048.0)
delsys5 = config.pop("_delsys5", None)
iap_lambda = config.pop("_iap_lambda__per_mm", 1.0)
kernel_override = config.pop("_iap_kernel_length__mm", None)
within_cov = config.pop("_muscle_cv_within_cov", None)
mean_cv = config.pop("_mean_cv__m_s", None)
cv_kw = {} if mean_cv is None else {"mean_conduction_velocity__m_per_s": mean_cv * pq.m / pq.s}
unit_cv_file = config.pop("_muscle_unit_cv_file", None)
if unit_cv_file is not None:
cv_kw["motor_unit_conduction_velocities__m_per_s"] = np.asarray(json.load(open(unit_cv_file))["cv__m_s"]) * pq.m / pq.s
cv_file = config.pop("_fiber_cv_per_unit_file", None)
if cv_file is not None:
config["fiber_cv_per_unit__m_s"] = tuple(json.load(open(cv_file))["cv__m_s"])
if "nerve_cvs__m_per_s" in config:
config["nerve_cvs__m_per_s"] = tuple(config["nerve_cvs__m_per_s"])
dispersion = SurfaceFiberDispersion(**config)
Expand All @@ -74,16 +98,31 @@
muscle = Muscle(recruitment_thresholds=thresholds, radius__mm=15 * pq.mm, length__mm=80 * pq.mm,
fiber_density__fibers_per_mm2=350 * pq.mm**-2, mean_fiber_length__mm=70 * pq.mm,
var_fiber_length__mm=5 * pq.mm, radius_bone__mm=3 * pq.mm, fat_thickness__mm=fat * pq.mm,
skin_thickness__mm=skin * pq.mm, skin_conductivity__S_per_m=skin_sigma * pq.S / pq.m)
skin_thickness__mm=skin * pq.mm, skin_conductivity__S_per_m=skin_sigma * pq.S / pq.m, **cv_kw)
radius = 15.0
else:
muscle = Muscle(recruitment_thresholds=thresholds, radius__mm=fdi_radius * pq.mm,
fiber_density__fibers_per_mm2=fdi_density * pq.mm**-2, skin_thickness__mm=skin * pq.mm,
fat_thickness__mm=fat * pq.mm, skin_conductivity__S_per_m=skin_sigma * pq.S / pq.m)
fat_thickness__mm=fat * pq.mm, skin_conductivity__S_per_m=skin_sigma * pq.S / pq.m, **cv_kw)
radius = fdi_radius
muscle.distribute_innervation_centers()
muscle.generate_muscle_fiber_centers(verbose=False)
muscle.assign_mfs2mns(verbose=False, n_jobs=n_jobs)
property_hash = ""
if dispersion.fiber_properties == "muscle":
state = get_random_generator().bit_generator.state
muscle._generate_fiber_properties()
get_random_generator().bit_generator.state = state
if within_cov is not None:
cv = np.asarray(muscle.muscle_fiber_conduction_velocities__mm_per_s.magnitude, float).copy()
for mu_idx in range(len(thresholds)):
sel = np.asarray(muscle.assignment) == mu_idx
if sel.sum() > 1 and cv[sel].std() > 0:
cv[sel] = cv[sel].mean() + (cv[sel] - cv[sel].mean()) * within_cov * cv[sel].mean() / cv[sel].std()
muscle._muscle_fiber_conduction_velocities__mm_per_s = cv * pq.mm / pq.s
property_hash = hashlib.sha1(
np.ascontiguousarray(muscle.muscle_fiber_diameters__mm.magnitude).tobytes()
+ np.ascontiguousarray(muscle.muscle_fiber_conduction_velocities__mm_per_s.magnitude).tobytes()).hexdigest()
counts = np.asarray(muscle.resulting_number_of_innervated_fibers)
fibre_hash = hashlib.sha1(b"".join(
np.ascontiguousarray(muscle.resulting_fiber_assignment(i)).tobytes() for i in range(N_MU))).hexdigest()
Expand Down Expand Up @@ -143,6 +182,8 @@ def excitation(t):
bending_radius__mm=(radius + fat + skin) * pq.mm,
differentiation_mode="bipolar_longitudinal")
kernel = 150.0
if kernel_override is not None:
kernel = float(kernel_override)
sensors = []
if delsys5 is not None:
sensors = [(z, rot, th) for z in delsys5["z__mm"] for rot in delsys5["rotation__deg"] for th in delsys5["theta__deg"]]
Expand All @@ -154,7 +195,10 @@ def excitation(t):
set_random_seed(7)
semg = SurfaceEMG(muscle, arrays, sampling_frequency__Hz=FS * pq.Hz, MUs_to_simulate=mus,
internal_sampling_frequency__Hz=FS * pq.Hz, fiber_dispersion=dispersion,
iap_kernel_length__mm=kernel, fiber_diameter__um=diameter)
iap_kernel_length__mm=kernel, iap_lambda__per_mm=iap_lambda,
**({} if diameter is None else {"fiber_diameter__um": diameter}))
diameter = semg.fiber_diameter__um
mean_cv = float(semg._mean_conduction_velocity__m_s)
t0 = time.time()
muaps = semg.simulate_muaps(n_jobs=n_jobs, verbose=False, use_gpu=False)
sig0 = muaps.groups[0].segments[mus[0]].analogsignals[0]
Expand All @@ -169,13 +213,13 @@ def excitation(t):
for m in mus])
mono = np.zeros((0,))
report = semg.fiber_dispersion_report or {}
np.savez(out, label=label, scenario=scenario, myogen=myogen.__file__, config=json.dumps(config), skin=skin, fat=fat, skin_sigma=skin_sigma, diameter=diameter, radius=radius, density=fdi_density, rate_model=json.dumps(rate_model), spike_file=str(spike_file), mus=mus, counts=counts,
fibre_hash=fibre_hash, spike_hash=spike_hash, ramp=RAMP, hold=HOLD, level=LEVEL,
np.savez(out, label=label, scenario=scenario, myogen=myogen.__file__, config=json.dumps(config), skin=skin, fat=fat, skin_sigma=skin_sigma, diameter=diameter, iap_lambda=iap_lambda, mean_cv=mean_cv, radius=radius, density=fdi_density, rate_model=json.dumps(rate_model), spike_file=str(spike_file), cv_file=str(cv_file), mus=mus, counts=counts,
fibre_hash=fibre_hash, spike_hash=spike_hash, property_hash=property_hash, kernel=np.nan if kernel is None else kernel, ramp=RAMP, hold=HOLD, level=LEVEL,
bip_muaps=templates, pair_muap_fs=float(sig0.sampling_rate.rescale(pq.Hz).magnitude),
bip_emg=np.asarray(signal_to_grid(emg.rescale(pq.mV))), mono_emg=mono, sensors=np.array(sensors),
sensor_pos_z=np.array([a.pos_z for a in arrays]) if delsys5 is not None else np.zeros(0),
sensor_pos_theta=np.array([a.pos_theta for a in arrays]) if delsys5 is not None else np.zeros(0), fs=float(emg.sampling_rate.rescale(pq.Hz).magnitude),
report=np.array({k: v for k, v in report.items() if k != "config"}, dtype=object),
seconds=time.time() - t0, allow_pickle=True)
print(label, "fibres", int(counts[mus].sum()), "secs", round(time.time() - t0), "fibre", fibre_hash[:10],
"spikes", spike_hash[:10], flush=True)
"spikes", spike_hash[:10], "properties", property_hash[:10], flush=True)
58 changes: 58 additions & 0 deletions docs/backlog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# MyoGen backlog (running task list)

Started 2026-11-03 (Devon). Future work we must not lose sight of. Newest decisions in the linked docs; this list says what is open and why. Move items to "Done" with the commit or issue that closed them.

## Priority: future work

### 1. Named simulation profiles

**Why.** MyoGen has many interconnected settings; signal-quality ones are easy to miss because they are off by default and buried (Gfluctdv noise was silently off; the human force-frequency curve, surface fibre dispersion, Muscle-fibre surface CVs and λ are all opt-in). A user who does not know they exist gets a worse signal without warning.

**Idea (to be designed).** A small set of named profiles, each filling every interconnected setting with consistent defaults across the neuron pool, `Muscle`, `ForceModel`, surface EMG and intramuscular EMG. Explicit arguments always override the profile.
- `"physiological"`: every validated mechanism on, with literature values (Gfluctdv on and calibrated, human force-frequency curve, fibre typing by unit, surface EMG reading `Muscle` fibres with the FDI λ ≈ 0.63 fit, two-layer end-plates with nerve delays, tendon-end stagger, …). λ is fitted per muscle and per fibre source (0.7 with surface CVs, ≈ 0.63 with `Muscle` fibres and per-unit CVs from recruitment force), so the profile must carry it with the fibre source.
- `"published"`: each component exactly as its source paper describes it (e.g. Fuglevand force-frequency, Rosenfalck λ = 1, no dispersion).
- `"manual"`: nothing filled in; the user sets everything.

**Open questions for the design.** Which profile is the default (a default change: needs Devon's yes). Per-muscle values (FDI vs VL calibrations) inside a profile. How the active profile and every setting it filled are recorded in outputs (manifest), so a reader can see what was on. Whether to warn when a quality-relevant setting is off under `"physiological"`.

**Input.** The settings inventory (overnight 2026-11-03, `docs/superpowers/specs/2026-11-03-settings-inventory.md`): every quality-relevant setting, where it lives, default, physiological value with source, interactions, off-by-default flag.

### 2. GitHub issues for the recent changes

**Why.** Trackable history for future sanity checks and backtracking: each key change since ~2026-10-30 gets an issue, and the fixes reach `main` through those issues.

**Done 2026-11-03**: issues #29–#39 opened (texts in `docs/pr/2026-11-03-github-issues.md`); PR #40 (`fix/semg-farina2004`, closes #29, #32) and PR #41 (`calib/fdi-dd-main`, stacked on #40, closes the rest); old `fix/semg-amplitude-scale` deleted (all 22 commits patch-equivalent in #40). Original plan: One issue per major change, each listing its commits on `calib/fdi-dd-main` (and earlier branches), the evidence, and the decision record (PR doc / design notes). Candidates:
- Surface EMG corrections against Farina 2004 (B(k_z) complex part, MUAP time axis, Rosenfalck λ / `z/2`, fibre semi-lengths, fibre timing, mixed arrays, absolute amplitude, skin/fat defaults, failed units raise).
- Intramuscular SFAP units and polarity; end-plate spreads (Akhmadeev published coefficients).
- Gfluctdv noise silent; large circular fibre layouts; fibre CV unit bug.
- Surface fibre dispersion (end-plates, nerve delays, CV, d² source normalisation, tendon ends).
- FDI fibre diameters (Polgar), fibre typing by motor unit, fibre CV 4.32 ± 0.71 (Del Vecchio 2018).
- IAP λ option and the FDI fit to Botelho 2019.
- Descending-drive optimiser and FDI calibration (`_human59_wexp`), human force-frequency option, % MVC / CV helpers.
- Literature register and inline citation convention.
Then decide one PR or several, routed through the issues. Source of truth for the list: `docs/pr/2026-11-01-upstream-changes.md`, `CHANGELOG.md`.

## Other open items

- **Non-zero Gfluctdv default**: separate PR; generic current-injection calibration; recheck the primary source of the 15–22 % ISI CV target. Fits into profiles (item 1).
- **Human force-frequency curve as `ForceModel` default**: proposed for the next release (item 1).
- **`HillModel` type counts from `Muscle`** (fibre-typing follow-up).
- **Type I / IIa interleaving at low thresholds** (Garnett 1979), if a source gives the overlap width.
- **6 m/s fibre CV bound**: Del Vecchio's max is 6.21. Kept for now (Devon 2026-11-02). Since the 2026-11-03 change the `Muscle` fallback (4.32 ± 0.71 over unit means) puts ~15 % of FDI fibres there; per-unit CVs from recruitment force put 1 %. Revisit if the fallback is used for results.
- **`--max-recruitment` alignment** with the human force-frequency curve at the next recalibration.
- **Library findings out of scope so far**: NERLab rheobase spans 3.6× for a 48× threshold range; NERLab rests at ~0 mV but the yaml initialises −67 mV; inhibitory synapses at −75 mV absolute are far too strong for NERLab.
- **Defects found by the settings inventory** (2026-11-03, `docs/superpowers/specs/2026-11-03-settings-inventory.md` §5; not yet fixed, each needs a test first): passive mode string `"pas"` vs `"passive"`; GTO `"generic"` raises NameError; inhibitory-synapse fallback; optimiser default threshold mode raises (`deluca__slope` never passed); docstring/default mismatches (bone radius, `peak_hz`, axon units); FDI calibration file is gitignored.
- **Duplicated authorities** (same inventory): muscle conductivity sEMG 0.09/0.4 vs iEMG 0.063/0.33 S/m; fibre length 30 / 31.7 / 38.9 mm; fibre CV (sEMG 4.2, `Muscle` 4.32 ± 0.71, dispersion slope) — the last is now resolvable with `fiber_properties="muscle"`. iEMG output normalised to peak 1 (no physical amplitude); iEMG hard-codes λ; iEMG `execution=` still runs the pre-0.13.0 kernel. Inputs for item 1 and item 2.
- **Surface RMS below Botelho with `Muscle` fibres**: at the λ fit the corner–centre RMS is ~0.17 mV, under the IQR 0.19–0.26 mV (adjacent pins inside). Reported, not tuned. Cause found 2026-11-03 (ablation `delsys5r_wexp_muscle_lam0.7_wcov*`, same fibres and spikes): the within-unit fibre CV spread alone. At λ 0.7, `Muscle` fibres with within-unit CoV 0 / 0.05 / 0.11 (as generated) give cc RMS 0.199 / 0.193 / 0.173 mV, adj 0.236 / 0.230 / 0.215 mV, mean MDF 171.9 / 170.0 / 163.1 Hz; the surface-CV control (CoV 0.05) gives 0.191 / 0.227 mV, 171.9 Hz, so diameters and unit-mean CVs barely matter. The within-unit CoV is not set anywhere: it emerges from the per-type diameter log-SD 0.2 and 0.2 m/s scatter (both assumptions), and the per-fibre rescale to Del Vecchio's 0.71 m/s, which is a between-unit SD (realised between-unit SD of unit means 0.32 m/s, within-unit 0.41–0.45 m/s). No human within-unit measurement found; cat TA fibre CSA CoV 0.25 within units (Martin et al. 1988, abstract only) gives CV CoV ≈ 0.09 (0.07–0.12). λ and within-unit CoV both lower MDF, so fix the CoV from the literature before refitting λ. Proposed: make within-unit CV CoV and between-unit CV SD separate `Muscle` parameters (default change: needs Devon). **Done 2026-11-03** (Devon approved): within-unit spread from Martin et al. 1988 (full text read), unit-mean CVs from recruitment force; refit λ ≈ 0.63, RMS 0.19 / 0.23 mV, inside Botelho's IQR.
- **Per-unit CVs have little spread within a recruitment band** (0.12 m/s vs Del Vecchio's 0.50 at RT 0–30 %), because `cv_from_recruitment_force` is deterministic. **Done 2026-11-03** (Devon approved): residual scatter 0.39 m/s (`54458d14d`). At λ 0.63 it lowers mean MDF 165 → 160 Hz (one draw, seed 20261103); RMS unchanged. Not refitted (Devon 2026-11-03): mean MDF 160 Hz is inside Botelho's IQR, and their model (one subject, no code) cannot be reproduced closely enough to chase; the PSD shape also differs and is left as is. λ stays ≈ 0.63.
- **6 m/s bound with the `Muscle` fallback**: ~16 % of default FDI fibres clipped. Devon 2026-11-03: keep the model, warn above 5 % (`54458d14d`).
- **Review minors, `Muscle` CV work** (fresh review 2026-11-03, deferred): type-mean test tolerance 0.5 µm cannot catch a missing per-type factor (check the ladder analytically to 1e-6); confirm the `None`-path hash against `a30ff0a84` and note it in the test; validate `within_unit_fiber_diameter_log_sd` in `__init__` (now fails after `assign_mfs2mns`) and accept any 2-sequence; `cv_from_recruitment_force` should raise on NaN / negative `residual_sd__m_s` and say it advances the global RNG; no count of fibres clipped at 2 m/s; tests for NaN per-unit CVs and per-unit CVs with `None`.
- **Intramuscular EMG**: deferred (Devon 2026-11-02), kept open.
- **Future data**: possible HD-iEMG + HD-sEMG FDI dataset for a CV comparison paper (not available yet).

## Done

- 2026-11-03: GitHub issues #29–#39, PRs #40 and #41 (backlog item 2).
- 2026-11-03: surface EMG reads `Muscle` fibres (`121546f55`, `d9dd47e7b`, `130ee929b`, `d3db69520`, `1ba5b18bb`); λ refit on Botelho: ≈ 0.72 (see PR doc).
- 2026-11-03: within-unit fibre spread from Martin 1988 and per-unit `Muscle` CVs (`6a2719c4a`, `d82a03e5d`, `84870f42c`); λ refit ≈ 0.63 with RMS inside Botelho's range.
- 2026-11-02: fibre typing by motor unit (`a3dc1d7`…`c9f39c04d`); % MVC / CV helpers (`c1331ab76`); decisions on calibration, Gfluctdv, force-frequency, λ, CV clip (`5e904a67f`).
Loading
Loading