feat[event]: Add MultiPopulationRecurrentLIF for per-population time constants - #1
Draft
dreliq9 wants to merge 1 commit into
Draft
Conversation
…constants Connected recurrent graphs in jaxsnn currently share a single LIFParameters across all populations (RecurrentLIF). Biology-faithful multi-cell-type circuits — e.g. cerebellum granule + Purkinje — need different time constants per population within a connected graph. This PR adds: - lif_exponential_flow_vec (flow.py): builds per-neuron [N,2,2] kernels via vmap. Complements the existing scalar-params lif_exponential_flow; existing function unchanged. - MultiPopulationRecurrentLIF (modules/leaky_integrate_and_fire.py): factory that accepts a list of LIFParameters, one per population in `layers`. Validates layer integrity (non-empty, positive int), per-population tau positivity, tau_mem/tau_syn ratio (1 or 2 within 1e-6, inherited from the analytical TTFS solver's domain), and v_th=1.0 (jaxsnn's step.py hard-codes V>=1.0). - tests/sw/event/test_multi_population_recurrent_lif.py: 6 tests covering construction, all five validation rules, the load-bearing heterogeneous-dynamics behaviour (pop B between fast and slow baselines), and backward-compat. Scope (v1): - Per-population homogeneity (one params per population, not per-neuron) - Tau ratios constrained to 1 or 2 (analytical TTFS limit) - Per-neuron heterogeneity within a population and arbitrary tau ratios (Newton-based solver) deferred to a follow-up. Backward-compat: all existing factories untouched. The 12 existing tests in test_lif, test_flow, test_lif_vs_recurrenteventproplif, test_lif_vs_eventproplif still pass unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RecurrentLIFand the EventProp variants currently share a singleLIFParametersacross all populations in a connected recurrent graph. Heterogeneous τ across populations — fast PV inhibition coupled to slower pyramidal cells, multi-cell-type biology-faithful circuits, trainable per-population time constants for surrogate-gradient SNN training — is broadly useful but currently inexpressible without bypassing the event-based engine.This PR adds
MultiPopulationRecurrentLIF(oneLIFParametersper population) and a backinglif_exponential_flow_vechelper. Submitted as draft for early API feedback; the EventProp adjoint variant follows once this surface is approved.Motivation
Heterogeneous τ across populations underpins several research lines that map cleanly to jaxsnn / BSS-2:
The gap is jaxsnn-specific (verified empirically):
RecurrentLIFover five populations with five distinct τ fails insidelif_exponential_flow(flow.py:25) withValueError: All input arrays must have the same shape. The kernel is closed over scalarparams.tau_memandvmap'd within_axes=(0, None)(params broadcast, not indexed);ttfs_solveris partial'd with scalartau_memat trace time.tauas a declared parameter, not a closed-over scalar). The gap is specific to monolithic-kernel event-based libraries.What this PR adds
lif_exponential_flow_vec(src/pyjaxsnn/jaxsnn/event/flow.py)Builds a per-neuron
[N, 2, 2]kernel viajax.vmapand appliesexpmper neuron. Drop-in for callers maintaining the per-neuron-batchedLIFStateshape; no outervmaprequired. Existing scalar-paramslif_exponential_flowis unchanged.MultiPopulationRecurrentLIF(src/pyjaxsnn/jaxsnn/event/modules/leaky_integrate_and_fire.py)Internally concatenates per-population params into per-neuron arrays of shape
(sum(layers),), threads them throughlif_exponential_flow_vec(dynamics) and a per-neuron-vmappedttfs_solver(spike timing).transition_with_recurrenceis reused viapartial; broadcasting handles thev_resetfield correctly.Validation rules (all raise
ValueErrorat construction)layersnon-emptyninlayersis a positive integer (isinstance(n, (int, np.integer)))len(params_per_population) == len(layers)tau_mem > 0andtau_syn > 0tau_mem / tau_synratio is≈ 1.0or≈ 2.0within1e-6rel-tol — analytical TTFS solver constraint inherited fromevent/root/ttfs.pyv_th == 1.0within1e-6rel-tol —step.py:85hard-codesevolved_neuron_state.V >= 1.and the TTFS solver'sv_thcannot override it; off-1.0 silently desynchronises the two layers(Rule 6 may also apply to the existing
LIF/RecurrentLIFfactories — happy to surface as a separate cleanup PR if useful.)Tests
tests/sw/event/test_multi_population_recurrent_lif.py— 6 tests, all passing locally:Construction with per-population params (happy path)
Length-mismatch validation
v_th-not-1.0 validation
Non-integer-layer validation
Heterogeneous-dynamics observable (load-bearing). Two populations (τ = 5 ms fast + τ = 20 ms slow) compared against
RecurrentLIFbaselines:Pop B in HETERO sits strictly between all-fast (4.07 ms) and all-slow (11.55 ms) — slow τ integrating an early kick from fast pop A. The test asserts both inequalities; catches a class of regression where per-population τ silently flattens.
Backward-compat smoke for unchanged
RecurrentLIFBackward compatibility
Zero regression. The 12 existing tests in
test_lif,test_flow,test_lif_vs_recurrenteventproplif,test_lif_vs_eventproplifall pass unchanged. No existing factory (LIF,RecurrentLIF,EventPropLIF,RecurrentEventPropLIF,HardwareLIF,HardwareRecurrentLIF) is touched.Scope and follow-ups
This PR (v1):
LIFParametersper population, not per-neuron).Natural follow-ups:
MultiPopulationRecurrentEventPropLIF— the EventProp adjoint variant. Mechanically parallel to the existingRecurrentEventPropLIFpattern; threadsadjoint_lif_exponential_flowthrough the same per-population concatenation. The EventProp adjoint is linear in1/tau_mem, so per-population rescales cleanly under heterogeneous τ. Happy to draft this in a follow-up PR once the v1 surface is approved.ttfs_solver) to Newton —newton_solveralready exists inevent/root/newton.pybut isn't currently wired into the factories. The combination unlocks the trainable-per-neuron-τ research line cited above.I'd appreciate a sanity check on the v2 direction (Newton solver substitution + the EventProp adjoint variant) before starting on it.
Incidental finding
While bootstrapping a dev environment I hit
ImportError: No module named 'nir.data_ir'fromsrc/pyjaxsnn/jaxsnn/event/from_nir_data.pyagainstnir 1.0.7from PyPI. Looks like upstream HEAD references an unreleased nir API. Worked around locally; happy to file as a separate issue if not already known internally.Happy to iterate on naming, API surface, or v2 design.