Release Notes#
Version 0.2.6#
A compatibility release: braintrace now works with JAX / jaxlib 0.11,
which merged ClosedJaxpr into Jaxpr and, in doing so, silently changed
what a compiler-built transition jaxpr reports about its own inputs. On 0.11.1
that broke 690 of the 2902 tests in the suite; all of them pass again.
Support for JAX 0.8 through 0.10 is unchanged — the fix derives the information JAX stopped storing rather than branching on a version, so there is one code path across every supported release. No public API changes.
Fixes#
Restored compatibility with JAX / jaxlib 0.11. JAX 0.11 merged
ClosedJaxprintoJaxpr: a jaxpr now holds a single positional input list and derives theconstvars/invarsboundary from how many constant values are attached, instead of storing it alongside the symbols. The ETP compiler builds transition jaxprs — programs whoseinvarsare the differentiated inputs (a hidden state att-1, or an ETP primitive’s outputy) and whoseconstvarsare surrounding intermediates bound from the forward pass at execution time. Those carry symbols but no attached values, so on 0.11 they began reportingconstvars == []and folding the constvars intoinvars.Every consumer that recovered the split by reading
jaxpr.constvarsback therefore passed too few values tojax.core.eval_jaxpr, which failed arity checking withValueError: foreach() argument 2 is shorter than argument 1the first time any algorithm evaluated a transition. Since that is the shared entry point for hidden→hidden and hidden→weight Jacobians, it took down essentially every gradient path: D-RTRL, ES-D-RTRL / pp_prop, EProp, OSTL, SnAp, UORO, DNI and all of the BPTT oracle cross-checks, across every ETP primitive family (dense, LoRA, sparse, convolutional, element-wise, embedding).The split is now derived from the invar count — which every caller already knows, since the invars are what it is about to feed in — via three helpers in
braintrace._compatible_imports:jaxpr_all_invars,split_jaxpr_invarsandjaxpr_constvars.split_jaxpr_invarsrange-checks its argument, so a miscounted call raises at the compiler boundary rather than producing a misaligned argument list. The full analysis is indocs/specs/2026-08-18-jax-011-jaxpr-merge-compat.md.Fixed SnAp-n position analysis under JAX 0.11.
build_snap_patternletanalyze_position_adjacencyseed its reachability walk from the transition jaxpr’s owninvars. Under the merged representation that set silently widened to include every constvar, so the derived neighbourhood was inflated — typically all the way to the conservative all-positions-couple fallback, which is correct but costs orders of magnitude in trace size. The group’shidden_invarsare now passed explicitly.
Internal#
HiddenGroup.transition_jaxpr_constvarsis populated from the constvars the builder actually used rather than read back off the constructed jaxpr, matching howHidden2GroupTransition.other_invarshas always been handled. Const-var collection in the graph executor, the compiled-graph output registration and structured scan descent go through the new helpers.The
jax-versionCI matrix pins0.11.0alongside0.8.0/0.9.0/0.10.0andlatest, so the merged-Jaxprrepresentation stays covered oncelatestmoves past it.New regression coverage for the split itself: helper-level tests in
braintrace/_compatible_imports_test.py(round-trip, zero- and all-constvar edges, out-of-range rejection) and compiler-level tests asserting that a compiled group’s recorded constvars match its jaxpr’s leading inputs and that the transition still evaluates.
Version 0.2.5#
This patch release removes public API. Despite the patch version number,
OTTT,OSTTP,OTPEandPresynapticTraceare gone andIODimVjpAlgorithm.decayis now read-only. A pin ofbraintrace>=0.2,<0.3will pick these changes up, so read the Breaking changes section below before upgrading.
It is also the largest feature release since 0.2.0: ten new public symbols,
headlined by the sequence-driver API (etrace_grad / etrace_evolve),
which removes the hand-written scan-and-accumulate loop from every call site,
and by five new learning rules (SnAp, UORO, ThreeFactor, DNI,
RandomProjectionVjpAlgorithm) expressed as coordinates in the new
ETraceConfig axis space rather than as bespoke implementations.
Closing the release, a nine-item hardening pass (E-01 … E-09) worked through
the backlog opened by the pre-release package audit. Its theme is that a
failure should be visible: four paths that could hang the compiler, mis-attribute
a gradient, or refuse a constructor argument several transforms too late now
raise where the mistake is, jax is a declared dependency instead of a borrowed
one, and the mypy typing gate covers the last two packages that were outside
it. Those items appear under Correctness and robustness, Improvements,
Documentation and Internal below.
Breaking changes#
Removed
OTTT,OSTTPandOTPE.braintraceis a framework for online learning in brain simulation, and a framework should ship general mechanisms. These three rules were not model-agnostic: all of them whitelisted dense-matmul primitives (_SUPPORTED_PRIMITIVES = {etp_mm_p, etp_mv_p}) and raisedNotImplementedErrorfor lora / sparse / convolutional / element-wise relations, and all of them were single-step only.OTPEadditionally assumed a single global time constant, was feed-forward only, was gradient-exact for one hidden layer, and rejectednum_state > 1outright — ruling out ALIF and any adaptation variable.OSTTPboundB_listto the HiddenGroup count and threadedy_targetthrough a bespoke path.Their coordinates remain reachable: the planned axis decomposition (
trace_factorization×temporal_recursion×learning_signal×trace_filter×update_schedule) expresses each of them as a configuration that works for every ETP primitive, not just dense matmul. Seedocs/specs/2026-07-25-algorithm-axes-roadmap.md.Also removed with them:
PresynapticTrace(used only byOTTT) and the internalextract_y_targethelper (used only byOSTTP).KappaFilterandFixedRandomFeedbackare unaffected. The'ottt','osttp'and'otpe'names no longer resolve inbraintrace.compile.Migration:
OTTT→pp_prop(sameio_factorizedtrace, keeps the temporal term instead of dropping it);OTPE→D_RTRLorpp_prop;OSTTP→EProp(feedback='random'), which is random feedback on the error rather than on the target.IODimVjpAlgorithm.decayis now a read-only property. The x-side and f-side decays became independent (seeETraceConfigbelow), so a singledecayattribute is only meaningful when the two agree. Reading it when they differ raisesAttributeErrornamingdecay_x/decay_f; assigning to it is no longer possible.decay_or_rank=0.9remains element-wise identical to the new(0.9, 0.9).decay_or_rank=0.0is now accepted byIODimVjpAlgorithm(the bound relaxed from0 < decay < 1to0 <= decay < 1). Zero is the coordinate for “no temporal accumulation on this side” and canonicalises totemporal_recursion='none'; it was previously rejected as invalid input.Renamed the private module
braintrace._state_managmenttobraintrace._state_management(the old name was missing ane). No deprecation shim and noDeprecationWarningwere left behind: the module is private, none of its helpers (assign_dict_state_values,assign_state_values_v2,sequence_split_state_values,split_dict_states_v2) is re-exported frombraintrace, and a shim would keep the misspelling importable — and therefore greppable and copy-pasteable — indefinitely. Public API is unaffected; only code that imported the private path directly needs to change:# before from braintrace._state_managment import assign_state_values_v2 # after from braintrace._state_management import assign_state_values_v2
Resolves #162; see
docs/specs/2026-08-07-e07-state-management-rename.md.
New features#
Sequence drivers:
etrace_gradandetrace_evolve. Every algorithm now carries two methods (via the newbraintrace.SequenceDriverMixin) that drive a whole sequence, so the scan-and-accumulate loop that every call site used to hand-write is gone.etrace_gradaccumulates online gradients over a sequence;etrace_evolveadvances hidden states and eligibility traces without computing any gradient.# before -- hand-written, repeated in 19 example files grads = jax.tree.map(jnp.zeros_like, {k: v.value for k, v in weights.items()}) def body(carry, xs_t): x, y = xs_t g, loss = brainstate.transform.grad(step, weights, return_value=True)(x, y) return jax.tree.map(jnp.add, carry, g), loss grads, losses = brainstate.transform.scan(body, grads, (xs, ys)) # after grads, losses = learner.etrace_grad(xs, ys, step_fn=step, return_value=True)
step_fnis keyword-only, so any number of sequences can be passed positionally and are sliced in lockstep. Supporting options:mask(weights the loss per step while still evolving the trace on masked steps),chunk_size(windowed drive),weights,reduction('mean'over unmasked steps by default, or'sum'),loss_output('per_step'/'masked'/'scalar'),has_auxandreturn_value.chunk_sizeandvjp_methodare independent axes: chunking sets how many steps each window covers, the VJP method sets how the window is differentiated.braintrace.compile(..., vmap=True)now returns abraintrace.ETraceVmap— abrainstate.nn.Vmapsubclass carrying the same two methods — so batched and unbatched call sites are identical. It remains abrainstate.nn.Vmapforisinstancepurposes; onlytype(x) is brainstate.nn.Vmapchanges. Note that reaching through.moduleis not equivalent:learner.module.etrace_grad(...)drives the unbatched learner and silently produces per-lane-wrong results.See
docs/specs/2026-07-27-sequence-driver-api.md. All examples, tutorials and docstrings were migrated ontocompile/etrace_grad/etrace_evolve; the remaining manual loops inexamples/are BPTT baselines and benchmark instrumentation, each carrying an in-file# kept manual:rationale.SnAp— sparse n-step approximation.recurrence_scopegeneralises from the two-valued'diagonal'/'coupled'to an n-step neighbourhood:SnAp(model, n=k)keeps hidden→hidden influence out toksteps in the position graph and drops the rest, son=1is the diagonal rule and largerninterpolates toward the fully coupled one. Reachable by name as'snap', and the neighbourhood is computed from the compiled graph, so it works for every ETP primitive rather than dense matmul only. Seedocs/specs/2026-07-25-p3-snap-n.md.UORO— unbiased online recurrent optimization. A rank-1 random projection of the influence matrix, giving an unbiased (but higher-variance) gradient estimate at O(P) memory instead of D-RTRL’s O(P·H). Built on the newRandomProjectionVjpAlgorithmengine, which is also public. Reachable as'uoro'.ThreeFactor— modulated learning.learning_signal='modulatory'replaces the backpropagated error with an externally supplied scalar (or per-group) modulator, the neuromodulation-style third factor. Requiresvjp_method='single-step', which is enforced at construction rather than discovered at run time. Reachable as'three_factor'.DNI— decoupled neural interfaces / synthetic gradients.DNIlearns a synthesiserM(h)that predicts the future loss gradientdL_{>=t}/dh_t, removing the dependence on a full backward pass. Ships withSyntheticGradient(the synthesiser module, sized from the compiled graph viaalgo.group_signal_shapes()) andtrain_synthetic_gradient(a training helper driven by the learner’s own returned hidden cotangent, so the regression target is exact rather than bootstrapped). Reachable as'dni'. Seedocs/specs/2026-07-25-p4-uoro-modulatory-dni.md.ETraceConfig— learning rules as explicit axis coordinates. The newbraintrace.ETraceConfigdescribes a learning rule as a point in a six-axis space (trace_factorization,temporal_recursion,recurrence_scope,learning_signal,trace_filter,update_schedule) plus the coefficients those axes need (decay,kappa,feedback_scale,sparsity). Illegal combinations are rejected at construction with an error naming the legal pairings, and coordinates that mean the same rule are canonicalised to one form (e.g. a zero decay collapses totemporal_recursion='none').braintrace.compileaccepts a config wherever it accepts an algorithm name, so a rule with no name is as constructible as one with a name:# an x-side leak with an instantaneous f-side learner = braintrace.compile( model, braintrace.ETraceConfig(trace_factorization='io_factorized', temporal_recursion=('scalar_leak', 'none'), decay=(0.9, 0.0)), x0, )
The named algorithms are now thin factories over coordinates rather than separate implementations:
D_RTRL,pp_prop,EProp,OSTLRecurrentandOSTLFeedforwardall construct a config and delegate. Their gradients are unchanged — the migration is pinned by 24 frozen golden gradients spanning all three trace paths (chunked, fused multi-step, single-step).temporal_recursionworks for every ETP primitive. The recursion is realised by substituting the executor’s per-hidden-group hidden→hidden Jacobian (λ·Iforscalar_leak, zeros fornone) rather than by special-casing operators, so it applies to dense, conv, sparse, lora and element-wise relations alike. The removedOTTT’s coordinate — x-side leak, f-side instantaneous — is reachable again this way, and is now primitive-generic, which the deleted implementation never was.Random feedback and the κ-filter are no longer
EProp-only.learning_signal='random_feedback'andtrace_filter='kappa'moved onto the base engine, so random feedback now composes with the O(I+O)io_factorizedtrace as well as the O(P·H) one. A configuration that cannot be honoured (random feedback requested but no feedback matrices allocated) now raises rather than silently computing the symmetric rule.recurrence_scopeis a public axis. What was the private_include_recurrent_mixingclass attribute is nowrecurrence_scope, and asking for a scope wider than the model supports — e.g.'coupled'on a model whose compilation descends into ascan— raises instead of silently degrading to'diagonal'. Beyond the original two values it accepts an integer n-step neighbourhood; seeSnApabove.New algorithm names in
braintrace.compile.'snap','uoro','three_factor'and'dni'now resolve, alongside the existing'd_rtrl','pp_prop','e_prop'and the OSTL names. ('ottt','osttp'and'otpe'no longer do — see Breaking changes.)braintrace.nn.CFNCellis exported. The Chaos-Free Network cell (arXiv:1612.06212) was implemented but never added tobraintrace.nn’s__all__, so the call its own docstring advertised raisedAttributeError. Exporting it surfaced a second defect, fixed here: the constructor sized the input projectionout_size -> out_size, butupdate()feeds it the input, so every forward pass within_size != out_sizeraised adot_generalshape error. It is now sizedin_size -> out_size, matching the paper’sh_t = theta_t * phi(h_{t-1}) + eta_t * phi(W x_t).braintrace.nn.__dir__now advertises the ~50 names that__getattr__forwards tobrainstate.nn/brainpy.state. They always resolved, but were invisible todir()and tab-completion, unlike the top-levelbraintracenamespace which already had this.
Correctness and robustness#
The hidden↔gradient correspondence is checked, not asserted (#165). Three
assertstatements in_algorithm/vjp_base.pywere meant to guarantee that the backward pass’s cotangents line up with the compiled hidden states. They failed at that twice over:python -Ostrips anassert, so on an optimised interpreter nothing checked anything; and even enabled, they compared cardinalities and one key set, never that cotangent i belongs to hidden state i. A misattributed cotangent yields a wrong gradient, not an error.Every guard is now an explicit
raise.vjp_base._check_hidden_gradient_correspondenceverifies totality, absence of strays, and per-index shape/dtype agreement, and is called from both branches.HiddenGroup.concat_hidden— whosezipagainstself.hidden_statessilently truncated a short value list into a too-narrow slab that surfaced later in unrelated trace math, or never — raises unless it receives exactly one value per hidden state;split_hiddengets the mirror guard on its trailing-axis width.HiddenPerturbation.perturb_data_to_hidden_group_dataraises on a length disagreement and names the group and the missing path instead of a bareKeyError. All comparisons are on Python-level metadata, so nothing is added to the traced graph. Seedocs/specs/2026-08-07-e01-hidden-gradient-correspondence.md.Control-flow canonicalization can no longer hang the compiler (#157). The three fixpoint loops in
braintrace/_compiler/canonicalize.py— cond if-conversion, inner-scan unrolling, and the joint driver — werewhile True:loops whose termination rested on an unenforced assumption that control-flow nesting is finite. A jaxpr that regenerated convertiblecond/scanequations as fast as the sweeps consumed them spun forever, with no output and no way to distinguish it from a slow compile.All three are now bounded by the new
ControlFlowPolicy.fixpoint_iteration_limit(default 64, must be a positive integer — there is no “unbounded” setting). Exhausting it raisesbraintrace.CompilationErrornaming the equations the last sweep was still rewriting (primitive, branch count or scan length, and source location) and pointing at the remedies: raise the limit if the nesting is genuine, or turn the offending pass off withControlFlowPolicy(cond='opaque')/ControlFlowPolicy(scan_unroll_limit=0).The limit bounds loop iterations, not the size of any single rewrite — that remains
scan_unroll_limit. Compiles that converged before converge identically now.braintrace.nn.Embeddingrejects its unsupported options at construction (#159).max_norm,freeze,scale_grad_by_freqandpadding_idxwere accepted by the inherited__init__and refused only byupdate()— which, underjit, is a trace arbitrarily far from the line that passed the option, so the traceback pointed at the transform rather than at the mistake.__init__now forwards every argument to the parent unchanged and then validates, so the error is raised from the constructor call; running validation aftersuper().__init__keeps the parent’s more specific diagnoses (an out-of-rangepadding_idxis still itsValueError). Theupdate()check is kept rather than deleted, because the four options are plain public attributes that can be set after construction, and the message now names only the options actually passed. The class docstring no longer inherits the parent’s text, which documented two of the four as working and demonstrated them in examples that raise.Canonicalization warning dedup no longer rests on an undocumented invariant (#158). Skip diagnostics were suppressed with
skip_warnedsets keyed onid(eqn), sound only because the enclosing jaxpr happened to keep every equation object alive across sweeps. Keying on the equation’s index is no better (each sweep rebuilds the list, so one rewrite shifts every later index), and content keys over-suppress. The key is gone instead: each sweep buffers its diagnostics and returns them, and the fixpoint driver emits only the buffer from the settling sweep — which rewrote nothing, so it visited each surviving equation exactly once and holds exactly one entry per skip.
Improvements#
JAX 0.11 compatibility. Ported the scan handling onto JAX’s flattree representation. CI now runs the suite against JAX 0.8, 0.9, 0.10 and latest.
jax>=0.8.0is now a declared dependency (#160). Every module importsjaxdirectly, but[project].dependenciesnever said so. The floor a resolver saw was not absent, it was borrowed:brainstatedeclaresjaxonly under extras, so the constraint that made installs work came frombrainevent’s metadata, and would have moved without a braintrace commit. The floor is0.8.0because that is the lowest entry in the CI matrix, and it is deliberately uncapped — a cap published today would constrain JAX releases that do not exist yet, for every artifact already on PyPI, while the daily scheduled run against unpinnedjaxsurfaces a breaking release within 24 hours. No install that works today gains a constraint; ownership of the floor simply moves to the matrix that tests it. The accelerator extras are unaffected (braintrace[cuda12]still resolves onejaxsatisfying>=0.8.0with the cuda12 extra, verified against the built wheel withpip install --dry-run --report), and they stay unversioned so the floor lives in exactly one place. The previously untestedbraintrace/_version.pygained a co-located test pinning the declared floor tomin(CI matrix), the no-cap rule, and therequirements.txtsync.Conv bias IO-dim fix, plus an axis-aware verification harness and an in-tree limitation list (
docs/specs/2026-07-25-known-limitations.md), which is now the tracked backlog of known approximation edges.train_synthetic_gradient’s window loop is compiled rather than traced once per window, viabrainstate.transform.for_loop.The wheel no longer ships the test suite. This project co-locates tests (
foo.py/foo_test.py), andsetuptools’packages.findexcludematches package names, not loose modules — so all 76*_test.pyfiles, the_algorithm/tests/and_compiler/tests/subpackages, and the fixture modules were being copied into every install: 1.63 MB of a 2.90 MB wheel, 56%. The fixture cluster (reference models, the BPTT oracle, the compiler scenario catalog) moved to a newbraintrace._testingpackage, excluded by name, and abuild_pysubclass insetup.pydrops*_test.py. The wheel is now 1.24 MB across 68 files, down from 2.90 MB across 148.This also removed a layering violation:
_algorithm/oracle_models.py(shipped code) imported layer classes frombraintrace/_etrace_model_test.py(a pytest-collected module that contained no tests). Both now live inbraintrace._testing, on the same side of the ship/no-ship line, and the mypyignore_errorsspecial case that existed only to paper over that import is gone.The sdist keeps the full test payload, because that is what downstream packagers build and run the suite from. Splitting the two artifacts is why the pruning lives in
setup.pyrather than inpackages.find: exclusion at discovery time happens before either artifact exists and so hits both. The wheel filter is applied inbuild_py.find_package_modulesand switched off forget_source_files, the listsdistbuilds its manifest from. This only holds withinclude-package-data = false(now set): left on, setuptools re-adds every file in the sdist manifest to the wheel as package data, which put the whole payload back. Verified end-to-end — the extracted sdist runs its own suite in a clean virtualenv,braintrace._testingincluded.The “every public API is typed” mypy gate now actually covers them. Twelve modules owning names in
braintrace.__all__were missing from thedisallow_untyped_defslist — including_algorithm/sequence.py, which ownsetrace_grad/etrace_evolve. They are now listed and annotated. Three dead entries naming the removedotpe/ottt/osttpmodules were deleted; mypy ignores unmatched module patterns silently, so they never errored.braintrace._ophonours its facade claim. The module docstring promised that every name exported from the underlying registries is available on the package, but seven were not — which is why_algorithm/param_dim_vjp.pyhad to reach past the facade intobraintrace._op._registries. All seven (BATCHED_COUNTERPARTS,register_batched_counterpart,get_batched_counterpart,ETP_RULES_INSTANT_DRTRL,ETP_RULES_SOLVE_DRTRL,get_instant_drtrl_rule,get_solve_drtrl_rule) are now re-exported.braintrace._compilerdeclares an__all__, the last package facade in the tree without one.Public surfaces no longer recommend
jax.random. Therandom_feedback_keyerror message, theFixedRandomFeedbackdoctest andEProp’s parameter documentation all told users to build keys withjax.random.PRNGKey, against the project’s own rule to usebrainstate.random. They now point atbrainstate.random.split_key().A bad algorithm name gives a clean error.
braintrace.compile(model, 'nosuch')raised its actionableValueErrorchained onto the internalKeyError, burying the message under “During handling of the above exception”. The chain is now suppressed.compile(..., vmap=True)validatesexample_inputs. A scalar or wrong-batch leaf previously produced a rawTypeErrorfroma[0]naming neithercompilenor the offending leaf.sparse_matmulmigrates off brainevent’s deprecated trace protocol.braintrace._op.sparsenow callsbrainevent.DataRepresentation.dt2t/.dt2t_transposeddirectly instead of the deprecated.yw_to_w/.yw_to_w_transposedaliases (brainevent renamed its own trace-propagation protocol to match braintrace’sDT_TO_Tterminology). The minimumbraineventversion is raised to 0.1.2 (the release that introducesdt2t/dt2t_transposed) inpyproject.toml/requirements.txt.Fixed a JAX-internal
linear_util.wrap_initDeprecationWarningraised by the single-step VJP residual construction invjp_graph_executor.py. The call now threads aDebugInfoobject through the ecosystem-standardbrainstate._compatible_import.wrap_initshim (re-exported asbraintrace._compatible_imports.wrap_init), matching the pattern already used elsewhere in thebrainstate/saiunitstack. No behavior change.Fixed stale API references in the documentation notebooks. Three notebooks (
docs/advanced/etp_primitives.ipynb,docs/advanced/customizing_primitive_transforms.ipynb,docs/advanced/limitations.ipynb) still referenced braintrace’s own pre-#130yw_to_w/ETP_RULES_YW_TO_Wrule naming instead of the currentdt_to_t/ETP_RULES_DT_TO_T; two executable cells inetp_primitives.ipynbraisedImportError/TypeErrorif re-run. Incidentally,etp_primitives.ipynbanddocs/quickstart/concepts.ipynbalso still calledelement_wise(weight, fn=...), predating that parameter’s rename toweight_fn. All affected notebooks were re-executed end-to-end to confirm they now run cleanly.CI runs the example suites.
examples/tests/andexamples/pp_prop/tests/both existed and both passed locally, but the CI test job ranpytest braintrace/only, so neither was ever executed — which is how the twoAttributeError/TypeErrorbugs fixed in #153 reachedmainin the first place. They now run in a dedicatedExamplesjob, kept out of the four-way JAX matrix because multiplying a 13-minute integration smoke run across four JAX versions buys nothing. The files were also renamed from thetest_*.pyprefix to the*_test.pysuffix the rest of the repo uses. The job earned its place immediately: it caught thatexamples/003-snn-memory-and-speed-evaluation-*.pyimportpsutilon the CPU backend without anything declaring it — green locally only because developers happen to have it installed. It is now inrequirements-dev.txt.
Documentation#
Tutorials reorganised into learning paths. The flat tutorial list became four hubs — Online training, Algorithm tutorials, Foundations, Compiler & runtime — with the deeper material moved from
docs/tutorials/to a newdocs/advanced/section. The hierarchy is native RST rather than synthesised.The Algorithms API reference is complete, covering every public algorithm including the five added in this release.
Every docstring is NumPy-style. The remaining Google-style docstrings were converted, so the whole public surface renders consistently.
ScaledWSLinearis documented. It was exported frombraintrace.nnbut appeared on no API page, whichnitpicky = Truewould eventually flag.README.mdnow opens with a runnable quickstart —compileplusetrace_grad— where it previously carried no code at all.The docs build is warning-free again. The convolution layers now close the bullet lists they inherit from the upstream
brainstatedocstrings structurally, rather than by matching one exact sentence of upstream wording that had since changed;docs/conf.pygained thebrainstate.random.split_keynitpick exemption the ecosystem convention already used forbrainstateclasses.The audit’s deferred findings are written down. Ten engineering-hygiene items surfaced by the pre-release audit are recorded in
docs/specs/2026-08-07-deferred-engineering-backlog.md, kept separate from the learning-rule correctness backlog indocs/specs/2026-07-25-known-limitations.md. Nine of the ten (E-01 … E-09) were resolved before this release shipped; each carries an implementation spec of its own underdocs/specs/.docs/_staticlost 599 KiB of unreferenced assets (#163). Seven files that no docs page, README, docstring, example or workflow referenced were deleted from every clone’s checkout. Two files the issue implied were dead are kept, both load-bearing:braintrace-learning-map.svgis used bydocs/index.rst, andbraintrace.pngis hotlinked by the READMEs frozen on the PyPI pages for 0.1.1 and 0.1.2 — deleting it would permanently break their header image. Three stale per-file.gitignorerules naming files that are already absent were replaced with one documented glob for the editable figure masters. Verified with a realsphinx-build -W --keep-goingover all 114 pages.
Internal#
braintrace._compilerandbraintrace._legacyare now inside the typing gate (#164). Thedisallow_untyped_defsmodule list inpyproject.tomlis what makes “every shipped def is annotated” a propertymypyenforces rather than a convention. Two packages were still outside it — the whole jaxpr-analysis layer that every algorithm depends on, and the frozen v0.1.x back-compat shim. Both are now listed and fully annotated: 93no-untyped-deferrors cleared (54 in_compiler, 39 in_legacy). No runtime behaviour changed; the only non-annotation edit is a correctedReturnsdocstring onETraceGraph.call_hidden_perturb, which claimed to return the model outputs when it returns the same four-element tuple as a normal forward call. Seedocs/specs/2026-08-07-e09-type-gate-compiler-legacy.md.The last two modules without co-located tests have them (#161).
braintrace/_typing_test.pypinsas_size_tuple’s normalisation contract across every arm of theSizeunion, its idempotence, the round-trip through abrainstatesize setter that motivates the helper, and each rejection by exception type — including two sharp edges recorded as facts rather than changed (a float inside a sequence truncates toward zero; a numeric string iterates character by character, so'12'becomes(1, 2)).braintrace/nn/__init___test.pypins the deprecation dispatcher: all 48 forwarded names,stacklevel=2attribution, non-memoisation, dispatch-table disjointness, a sorted__dir__, and theAttributeErrorfallthrough that stops an unknown name from silently evaluating toNone. Test-only; no runtime behaviour is altered.
Version 0.2.4#
This release makes eligibility-trace online learning work through JAX control
flow. A new compiler canonicalization + descent pipeline lets ETP operations
inside vmap, cond, scan / for_loop, and weight-free while bodies
participate in online learning (Phases 0–4), so recurrent cells built with
control flow no longer silently drop parameters from the trace graph. The
operator layer gains three new ETP ops — grouped_matmul, embedding, and
einsum — each with a matching braintrace.nn layer; the D-RTRL multi-step
trace update is chunk-factorized for a 2.4–4.5× speedup on multi-step windows;
and a full _op / _algorithm audit closes 24 correctness findings. The
compiler itself is now deterministic across processes and transparently inlines
user jax.jit bodies. One internal ETP rule is renamed (see Breaking changes).
Highlights#
New: grouped_matmul, embedding, and einsum ETP operators#
Three new ETP operators join the operator layer, each with hand-written ETP rules (
dt_to_t,xy_to_dw, trace initializers), a closed-form D-RTRL fast path where applicable, public exports, and single-step BPTT-oracle coverage:braintrace.grouped_matmul— a grouped matmul exposing both batched and unbatched primitives (etp_gmm/etp_gmv) and a closed-form D-RTRL fast path; D-RTRL matches BPTT element-wise andpp_propis directionally aligned. Wrapped by the newbraintrace.nn.GroupedLinearlayer.braintrace.embedding— an ETP embedding lookup with a broadcastdt_to_tand a scatter-addxy_to_dw. Because the input is integer token indices, the IO-dim (pp_prop/ES_D_RTRL) input trace cannot low-pass the raw indices; a new optional per-primitiveETP_RULES_PP_X_REPRregistry letsembeddingfilter the linear one-hot representation (y = onehot(idx) @ T) instead, andxy_to_dwdispatches on the x dtype (integer indices → gather-VJP scatter-add; float one-hot → contraction VJP). Wrapped by the newbraintrace.nn.Embeddinglayer.braintrace.einsum— an equation-parsed ETP einsum with axis classification; diagonal-class and shared-axis equations are D-RTRL BPTT-exact (maxdiff 0.0), while the genuinely lossy regime (output positions collapsing into a smaller hidden state) fails loudly at compile time with a cotangent-shape error rather than silently emitting wrong gradients.
New: structured scan descent — long ETP scans compile and learn online (Phase 4)#
A third compile path for ETP-relevant
scans above the unroll limit. Previously an ETP-relevantlax.scan/for_loopwhose static length exceededControlFlowPolicy.scan_unroll_limitwas a dead end (NotImplementedError). Under the new defaultControlFlowPolicy(scan_descent='auto'), the compiler descends such a scan: relations and hidden groups are discovered inside the scan body with the same flat finders, the equation is rewritten to emit stacked per-substep values as extra ys (leading substep axisL), and the graph executor computes stacked per-substep Jacobians by vmapping over that axis — the compiled program stays a single scan equation, so compile size is independent of the loop length (anL=100inner loop compiles in under 60 equations). ASCAN_DESCENT_APPLIEDINFO diagnostic records each descent; blocked scans getSCAN_DESCENT_SKIPPED. SetControlFlowPolicy(scan_descent='off')to restore the pre-Phase-4 error.Param-dim algorithms fold the eligibility trace over the substep axis.
D_RTRL(theParamDimVjpAlgorithmfamily) applies its trace update per substep with an innerjax.lax.scan(eps <- D_tau * eps + x_tau (x) df_tau), declaring_supports_scan_descent = True. The fold is values-only and stop-gradient’d — never differentiated, so no checkpointing is needed. The learning signal stays one-per-outer-step ((*varshape, num_state); the SNN learning-signal axis contract is unchanged). The io-dim family (pp_prop/ES_D_RTRL) and other algorithms without the flag reject descended graphs with an actionableNotImplementedErroratcompile_graph.Exactness contract (pinned by oracle tests). For diagonal-recurrence bodies (elementwise hidden-to-hidden substep path — the SNN class), descended D-RTRL is exact: whole-sequence, chunked (3-step and 1-step chunks, where the gradient depends on the folded trace at every chunk boundary), and one-step single-step gradients all match BPTT / the unrolled twin element-wise, including a two-hidden-state (
num_state == 2) group through the fold. For bodies that mix the hidden state through an ETP matmul, whole-sequence multi-step gradients remain BPTT-exact; chunked gradients approximate cross-substep credit (the same approximation class as the unroll path — documented divergence).Algorithm-level
control_flowkwarg.D_RTRL,pp_prop(IODimVjpAlgorithm),OTPE, andOTTTnow acceptcontrol_flow=ControlFlowPolicy(...)and thread it through their graph executors into compilation.v1 restrictions (each blocks descent for that scan, with a diagnostic): reverse scans, nested control flow inside the body, trainable weights scanned over as xs, and an outer ETP relation targeting a hidden state carried by a descended scan (raises with restructuring guidance).
jitbodies nested inside control-flow equations are now inlined during extraction so descent sees a flat body.Single-step readout limitation. The per-step hidden perturbation is added to a descended scan’s carry outvar; a loss that reads the hidden state through the scan’s stacked ys (e.g.
for_loop(...)[-1]) bypasses it, dropping the same-step learning signal (pinned by test; parallels the Phase 3 while-hidden limitation). Read the state after the loop (self.h.value) instead — multi-step VJP is unaffected either way.
New: while-loop policy — weight-free opaque-forward support (Phase 3)#
Weight-free
lax.while_loops that read/update hidden state now compile. Under the new default policy knobControlFlowPolicy(while_hidden='opaque-fwd'), awhilewhose inputs carry no trainable ETP weight is kept as an opaque forward node: the compiler registers relations whosey→hidden tail crosses the loop, emits aCONTROL_FLOW_OPAQUE_FWDINFO diagnostic, and extracts hidden-to-hidden Jacobians for any hidden group whose transition contains awhilein forward mode (jax.jvp-basedjacfwd_last_dim/jacfwdblock extraction) — reverse mode throughwhile_loopis structurally unsupported by JAX. SetControlFlowPolicy(while_hidden='error')to reject such loops instead.Perturbation detach keeps the VJP reverse-traceable. The hidden perturbation pass rewires every hidden-producing
whileto consumestop_gradientcopies of its inputs in the perturbed jaxpr only; theh = fresh + εadd stays outside the detach, so the single-step learning signal of the loop’s own hidden group (taken exclusively from the perturbation cotangents) is exact. Verified: D-RTRL single-step gradients on a while-settle model match its hand-composed no-whiletwin element-wise, and the twin matches the BPTT oracle. Limitation: the detach zeroes every same-step reverse path through the loop, so a parameter or other hidden group whose only same-step path to the loss crosses the loop — e.g. the weights of an upstream layer feeding a while-hidden layer — receives a zero learning signal (a WARNING-levelCONTROL_FLOW_OPAQUE_FWDdiagnostic records each detach; the zero-upstream-gradient behavior is pinned by test).vjp_method='multi-step'on awhile-hidden model still raises JAX’s reverse-through-while_loopValueError(documented limitation — use the default single-step path).A weight used inside a
whileis now a hard, actionable error (WEIGHT_IN_WHILEERROR diagnostic +NotImplementedError): move the weight application outside the loop so the loop consumes only its result (subject to the same-step limitation above), or use a fixed-length scan/for_loop(which the compiler unrolls).Breaking: ETP primitives left inside an un-flattened
scan/while/condbody now raise instead of being silently warned-and-excluded (etp_in_control_flow='error', the new default). PassControlFlowPolicy(etp_in_control_flow='exclude')to restore the old warn-and-exclude behavior.Position-mixing guard: a
while/opaque control-flow body that appliesdot_general/conv_general_dilatedto hidden-derived values (recurrent weight mixing inside the loop) cannot be expressed as a per-position Jacobian; the compiler treats it as a boundary, emits aCONTROL_FLOW_RECURRENT_MIXINGWARNING, and falls back to the zero-recurrence (e-prop-style) group.
New: inner-scan unrolling (compiler canonicalization, Phase 2)#
ETP operations inside
lax.scan/brainstate.transform.for_loopbodies now participate in online learning. A new canonicalization pass (unroll_inner_scansin_compiler/canonicalize.py) runs at extraction time and replaces every ETP-relevant, statically short scan with its unrolled body: one cloned copy per iteration with fresh variables,xssliced per step, consumedysre-stacked viabroadcast_in_dim+concatenate, andreverse=Truerespected. The unrolled program is value- and Jacobian-identical to the scan, so exact algorithms (D-RTRL, full-rank pp_prop, EProp(k=0), OSTLRecurrent) match BPTT element-wise on scan-body models — verified against hand-flattened twins and the BPTT oracle. Cond and scan canonicalization now run as a joint fixpoint (canonicalize_control_flow), so acondinside a scan body (and an eligible scan inside acondbranch) both flatten.Relation counts follow the weight→weight→hidden invariant: in an unrolled inner loop only the last sub-step’s ETP ops become relations — earlier sub-steps reach the hidden state through another trainable ETP op and are excluded (with the usual no-relation warning).
Eligibility gates: only scans whose static
lengthis ≤ControlFlowPolicy.scan_unroll_limit(default 16) and that carry no effects and contain nowhileare unrolled. An ETP-relevant scan that fails a gate emits aSCAN_UNROLL_SKIPPEDwarning and keeps today’s hard-error behavior; unrolls are recorded asSCAN_UNROLLEDINFO diagnostics onETraceGraph.diagnostics. Scans that scan over a trainable weight (weights asxs) are never unrolled (RELATION_EXCLUDED_SLICED_WEIGHTwarning) — per-slice trace lineage is deferred.Cond gate revision: a branch containing a scan no longer blocks if-conversion when that scan is itself unrollable;
scan_unroll_limit=0disables unrolling and restores the exact Phase 1 gating.Tied-weight invariant locked: one
ParamStateconsumed by several ETP call sites (which unrolling multiplies) is keyed per relation instance with per-path gradient accumulation — verified BPTT-exact and now covered by regression tests.
New: cond if-conversion (compiler canonicalization, Phase 1)#
ETP operations inside
lax.condbranches now participate in online learning. A new canonicalization pass (_compiler/canonicalize.py) runs at extraction time (after user-jitinlining) and rewrites every ETP-relevantcondequation into the inlined bodies of all branches followed by oneselect_nper output.select_n’s index semantics and JVP matchcondexactly, so for finite branches values and Jacobians — and therefore exact algorithms such as D-RTRL — are unchanged. Weights used insidecondbranches previously raisedNotImplementedError(or were silently excluded when only ETP primitives appeared inside).Semantics note: on the canonicalized graph both branches execute every step and the dead branch’s value is discarded by
select_n. Values and forward-mode derivatives are unaffected by dead-branch NaN/Inf. Reverse-mode gradients are not: if the dead branch’s local Jacobian is NaN/Inf (e.g. acondprotecting asqrtdomain), its VJP multiplies the exact-zero cotangent by that Jacobian (0 * nan = nan) and contaminates gradients of shared inputs — the classic single-wherepitfall. Keep such domain-guard conds opaque (ControlFlowPolicy(cond='opaque')) or guard the operand inside the branch.Gates: conds that touch no ETP primitive, weight, or hidden state stay opaque at zero cost. Conds with effects or containing
while/scanin a branch are never converted; an ETP-relevant one that is skipped this way emits aCOND_CONVERSION_SKIPPEDwarning and keeps today’s behavior. Conversions are recorded asCOND_IF_CONVERTEDINFO diagnostics onETraceGraph.diagnostics.Opt-out:
braintrace.ControlFlowPolicy(cond='opaque')via the newcontrol_flowkeyword oncompile_etrace_graph/extract_module_inforestores the previous behavior.
New: vmap identity preservation (operator layer)#
vmap identity preservation (operator layer):
jax.vmapover an unbatched ETP op (matmul,lora_matmul,sparse_matmulwith vector input) now re-binds the batched ETP primitive (etp_mm/etp_lora_mm/etp_sp_mm) instead of decomposing into standard JAX ops. Models that vmap per-sample ETP operations insideupdate()now compile with full eligibility-trace relations. When promotion is impossible (batched weights,etp_conv, nested vmap), the op decomposes as before but emits aUserWarninginstead of silently dropping the parameter from online learning. Note: when this warning appears from acompile(..., vmap=True)learner’s execution trace (e.g. conv models), it is expected and benign — the eligibility-trace graph was already compiled per-sample before the learner was vmapped, so no parameter is dropped.
New: user-jit inlining and a deterministic compiler#
ETP operations inside a user
jax.jitnow compile.extract_module_infoinlines userjax.jitbodies before any analysis, sojitboundaries are transparent to hidden-group discovery and relation finding. Previously a weight used inside ajitraisedNotImplementedErrorand bare ETP primitives were silently skipped (#123).Deterministic, reproducible compilation. Hidden-group discovery, transition bookkeeping, and group merging now use insertion-ordered maps and a canonical compiled-state ordering instead of
sets keyed by object identity, so group membership and ordering are stable across processes. Every built group is validated withcheck_consistent_varshape, and merges emit an INFO-levelHIDDEN_GROUP_MERGEDdiagnostic (#123).Directly-fed fan-out fix. A single ETP op feeding two independent recurrent states now registers relations to both groups — the forward BFS previously locked onto whichever hidden state it reached first and dropped the rest. Relation gating also resolves all keys before excluding a relation, so a constant-weight /
ParamState-bias matmul still registers with the bias as its trainable key (#123).Robust perturbation pass. The single-step perturbation now handles multi-output equations and read-only hidden states (synthesizing the
h^t = h^{t-1} + ppassthrough) and preserves the source jaxpr’s effect set, instead of falling through to an unexplained-hidden error (#123).
Performance#
Chunk-factorized multi-step D-RTRL trace update. Multi-step trace updates for
D_RTRLnow factor the per-step decay into suffix products and apply the trace update per chunk instead of step-by-step, giving a 2.4–4.5× speedup on multi-step windows for the dense (etp_mm/etp_mv) and elementwise (etp_elemwise) kernels. Exposed as achunked_traceknob onD_RTRL/braintrace.compile(#132).
Correctness#
ETP
_op/_algorithmaudit — 24 findings closed (4 Critical, 6 High, 6 Medium, 8 Minor). Highlights: exactconv(C1) andlora_matmul(C2) gradients under param-dim D-RTRL (per-position kernel trace + effective-weight trace, backed by new optional instant / solve D-RTRL rule registries); a fix for the batched sparse D-RTRL crash (C3) via a hashable CSR wrapper; and OSTTP’s always-zero learning signal (C4) viacustom_vjpresidual threading. Also resolved:trace_dtypegate mismatch, conv bias broadcast, EProp kappa-filter cross-state contamination and random-feedback scale invariance, OTTT / OTPE dropped bias gradients and missing guards, int/bool autodiff guards, rank guards with nn-layer axis folding, and a corrected OSTL exactness claim. Adds a cross-family single-step BPTT oracle suite and first-principles rule tests (6c7796a).
Breaking changes#
ETP rule rename:
YW_TO_W→DT_TO_T. The recurrent trace-propagation rule computesD^t * ε^{t-1}(theDᵗ-times-previous-trace term of the D-RTRL update), soDT_TO_Tnames it accurately;YW_TO_Wnever matched what the rule computes. Custom primitives that register this rule (viaregister_etp_rules/register_primitive) must use the new name. This is unrelated tobrainevent’s externalDataRepresentation.yw_to_w/yw_to_w_transposedprotocol methods, which are untouched (#130).
Internal#
mypyCI gate repaired. Cleared 40 accumulated type errors across 8 files (annotation-only, no behavior change), restoring a greentypecheck_and_buildjob (#133).Signature cleanups: a readability pass on
_etp_sp_matmul_impland removal of unused keyword arguments across several modules.
Version 0.2.3#
This release adds optional, shape-preserving parameter-transform hooks to the
eligibility-trace (ETP) operators, so a trainable weight (or bias) can be passed
through an elementwise / standardizing function before it enters the operation
while the eligibility trace and gradient remain with respect to the raw
stored parameter. These hooks are threaded through the braintrace.nn linear
layers and demonstrated in a new tutorial. The release also hardens the public
API with inline type annotations behind an enforced mypy gate, corrects the
weight_fn / bias_fn gradients on the closed-form fast path, relocates the
fast-path kernels into the operator layer, and tightens the sparse_matmul
input contract. Two public APIs are renamed and one operand type is now required
(see Breaking changes).
Highlights#
New: parameter-transform hooks on ETP operators#
Add transform hooks to the ETP ops, computing
y = x @ weight_fn(w) (+ bias_fn(b))(and per-op equivalents), with the eligibility trace and gradient kept with respect to the raw parameter:braintrace.matmul/braintrace.sparse_matmul—weight_fn,bias_fn.braintrace.conv—kernel_fn,bias_fn.braintrace.lora_matmul—b_fn,a_fn,bias_fn.braintrace.element_wise—weight_fn(see Breaking changes).
Each transform is applied inside the ETP primitive; the per-parameter Jacobian is recovered exactly once (via
jax.vjp) in the weight-gradient rule, while the trace-propagation rule is unchanged — so the forward-mode eligibility trace stays exact and is never double-counted. D-RTRL matches backprop-through-time element-wise for non-identity transforms (verified withtanh,w**2, andabs). Omitting a transform is bit-identical to the previous behavior.
New / Improved: braintrace.nn linear layers#
braintrace.nn.Linear(withw_mask),braintrace.nn.SignedWLinear, andbraintrace.nn.ScaledWSLinearnow route their weight masking / sign / standardization through the newmatmul(weight_fn=...)hook, so the masked / signed / standardized weight participates in eligibility-trace learning with the gradient kept w.r.t. the raw weight leaf. (ForScaledWSLinear,gainandbiasare applied as post-operations and are therefore non-temporal for the online trace, though still recovered exactly by the multi-step VJP oracle.)Export
braintrace.nn.ScaledWSLinear(previously importable only by its fully-qualified module path).
New: typed public API with an enforced mypy gate#
Inline type annotations now cover the public surface — ETP operators and their rule functions,
ETPPrimitive/register_primitive, thebraintrace.compileentry point and package accessors, input-data containers, thebraintrace.nnlinear / conv / recurrent cells, and the algorithm base classes, executors, and concrete algorithms. A newWeightFnalias names the transform-hook signature.An enforced
mypygate guards the public API, so type regressions fail the build (#119).
Improvements#
Correct
weight_fn/bias_fngradients on the fast path. The transform Jacobianf'(W)is now applied on the param-dim D-RTRL closed-form fast path (it lives solely inxy_to_dw;dt_to_tstays transform-free), so transformed-parameter gradients match the slow path. Also fixes anelement_wiseslow-path batched-cotangent crash (#120).Operator-layer fast-path kernels. The closed-form fast-path kernels (instant / recurrent / solve) move into the operator layer as a per-primitive
FastPathRulesbundle behind anETP_FAST_PATH_RULESregistry, and the algorithm-layer string-match gate is replaced by a per-primitiveapplicable()predicate — keeping primitive knowledge in the operator layer per the layered design (#120).
Documentation#
New tutorial: customizing primitive transforms (
docs/tutorials/customizing_primitive_transforms.ipynb), plus transform-hook docstrings on the ETP operators (#120).
Breaking changes#
braintrace.element_wise: thefnparameter is renamed toweight_fnand is now keyword-only, and the transform is applied inside the ETP primitive (previously it was applied to the weight outside the primitive). Migrateelement_wise(w, fn=g)toelement_wise(w, weight_fn=g). Forward results are unchanged; only the call signature and the internal trace-factorization point differ.braintrace.sparse_matmul: the weight parameter is renamed fromweight_datatoweightfor a cleaner, more consistent API. All in-tree call sites pass it positionally and are unaffected; update any keyword callers (#116).braintrace.sparse_matmul: the sparse operand (sparse_mat) must now be abrainevent.DataRepresentationand is enforced with a strict runtimeisinstancecheck (raisingTypeError).DataRepresentationsupplies the ETP online-learning protocol the compiler / executor require (with_data,yw_to_w,yw_to_w_transposed);brainunitsparse types (u.sparse) lack these and are no longer accepted.braineventis now a runtime dependency. Migrate sparse weights tobrainevent(e.g.brainevent.CSR) (#121).
Dependencies#
Add
braineventas a runtime dependency (pyproject.toml,requirements.txt) (#121).Bump
codecov/codecov-actionfrom 5 to 7 (#117).
Version 0.2.2#
This release introduces a unified braintrace.compile entry point for building
eligibility-trace online learners, adds a recurrent mixing mode to the
graph-construction compiler, and fixes eligibility-trace convergence under
vmap / brainstate.mixin.Batching(). It also migrates unit handling from
saiunit to brainunit, modernizes the toolchain (Python 3.14,
brainstate >= 0.5.2, Codecov), and ships broad documentation, example, and
test improvements. Internal modules were renamed for brevity; no documented
0.2.x public API is removed.
Highlights#
New: unified braintrace.compile entry point#
braintrace.compile(model, algorithm, example_input, ...)is now the canonical, single-call way to build a compiled online learner. It always initializes states, accepts aseed, applies model guardrails, and can emit a verbose compilation report — replacing the manualinit_states/learner.compile_graph(x0)triad.vmap=parameter for per-sample vmap state initialization. Withvmap=True, states are built viavmap_new_states(state_tag='new', ...)and the learner is wrapped inbrainstate.nn.Vmap(vmap_states='new'), so eligibility-trace models compose with brainstate’s per-sample vmap scheme.CompilationReport, a structured view over the eligibility-trace graph (relation/weight counts,etrace_weights,excluded_weights,report.show()with verbosity levels). It is exposed viaETraceAlgorithm.reportand now backsshow_graph.
New: recurrent mixing mode for graph construction#
Add a recurrent mixing mode to eligibility-trace graph construction, broadening the set of cell topologies the compiler can connect (#108).
Improvements#
Dependencies and toolchain#
Replace
saiunitwithbrainunitfor all unit handling across source, tests, examples, and docs.brainunitre-exportssaiunitinternally, so this is a drop-in change (#106).Raise the
brainstatefloor to >= 0.5.2, required by thecompile(vmap=True)path, and drop a duplicate dependency declaration.Update the supported Python version to 3.14 and adjust the CI JAX version matrix.
Add Codecov coverage reporting and raise source coverage to 93%, with new tests for previously-untested modules (#109).
Refactoring#
Rename internal module packages for brevity:
_etrace_op→_op,_etrace_compiler→_compiler, and_etrace_algorithms→_algorithm. These are private modules; imports were updated package-wide with word-boundary-anchored replacement (#111).Remove the unused
ParamStatefrom state management.Remove the per-step spectral-normalization path (
normalize_matrix_spectrum) from D-RTRL, E-Prop, and the OSTL trace scan; it ranjnp.linalg.eigvalson every hidden-group Jacobian, was off by default, and was costly.
Fixes#
Eligibility-trace convergence under
vmapbatching. Defer graph compilation during thevmap_new_statesdiscovery probe so the executor binds to the real batched states (fixes aBatchAxisErrorwhen writing batched values), correctly handle models that mix batched and unbatched ETP primitives in the param-dim VJP solve, and align convolution eligibility traces underbrainstate.nn.Vmap(vmap_states='new'). Restores convergence for the conv-based SNN/RNN training examples.Element-wise eligibility traces under
brainstate.mixin.Batching(). Size the trace from the (batch-aware) hidden group and sum out the leading batch axis in the solver, fixing a scan-carry type mismatch and a custom-VJP backward shape mismatch. This unblocks the default SHD batch trainer, where every LIF leak is an element-wise weight.braintrace.nn.LoRAnow routes its forward through the ETPlora_matmulprimitive, so LoRA factors participate in eligibility-trace learning (fixes the zero-relations bug) and the factor order is corrected.Resolve pre-existing
mypyerrors in the compiler’sreport.py(#112) and treatbrainunit/saiunitas untyped formypyto clear spuriousattr-definederrors from their re-export chain.Convert legacy
xfailtests to positive assertions, silence thecore.JaxprDebugInfodeprecation warning, and migrate deprecatedbrainstateAPIs (brainstate.augment→brainstate.transform,brainstate.functional→brainstate.nn) (#113).
Documentation and examples#
Make
braintrace.compilethe canonical entry point in every docstring, tutorial, notebook, and example, and fix broken examples (e.g. self-contained RNNs, consistent batch axes); each documented example is now backed by an executable test (#114).Document
CompilationReportin the API reference and migrate the onboarding guides, quickstart, and tutorials to the unified compile flow.Add a smoke-test harness and a testable
main()entry point to the standalone examples; repair all docs notebooks so they execute cleanly.
Notes#
The internal module renames (
_etrace_*→_*), the removal ofParamState, and the removal ofnormalize_matrix_spectrumtouch private/internal surfaces only; the documented 0.2.x public API is unchanged.Verified locally: the full CPU test suite is green (1604 passed, 3 skipped).
Version 0.2.1#
This is a maintenance release that restores compatibility with the latest
brain-ecosystem dependencies and toolchain. It contains no functional or
public-API changes — code written against 0.2.0 continues to work unchanged —
and exists to keep BrainTrace green against brainstate 0.5, saiunit/
brainunit 0.5.1, and pytest 9.1.
Fixes#
Dependency Compatibility#
brainstate0.5 typed API: adoptedbrainstate’s PEP 561py.typedsurface throughout the source — routedPyTreethrough BrainTrace’s existing type alias, centralized anas_size_tuple()helper in_typing, droppedFlattedDictsubscripts, and added boundary asserts/casts. This clears the 154 mypy errors newly exposed by the upstream typing, with minimal# type: ignoreonly wherebrainstate’s typing makes it unavoidable.brainstate0.5.0 convolution validation: updated convolution test expectations for the hardened validation (bareassert→ValueError) and the new one-value-per-spatial-dimension padding-tuple semantics.pytest9.1.0 collection: removed trailing commas in single-argumentparametrizeids thatpytest9.1.0 mis-parses as two values, fixing a collection-timeGraphNodeMeta has no len()error.
Notes#
All changes are BrainTrace-side. A related upstream
saiunitissue is resolved insaiunit/brainunit0.5.1 and requires no change here.Verified locally: full suite 1367 passed (2 xfailed), mypy clean across 51 files, and wheel + sdist build with
py.typedshipped (PEP 561).
Version 0.2.0#
This release is a major step for BrainTrace. It adds a family of spiking neural network (SNN) online-learning algorithms, rewrites the eligibility-trace compiler around primitive-type dispatch, generalizes every ETP primitive to support multiple trainable inputs (fixing a silent bias-gradient drop), delivers substantial performance gains for D-RTRL and multi-step rollouts, and hardens the package with PEP 561 typing and a BPTT-oracle-backed test suite.
Major Changes#
New: SNN Online-Learning Algorithms#
Added five SNN online-learning algorithms as flat
ETraceVjpAlgorithmsubclasses:EProp,OSTL(OSTLRecurrent/OSTLFeedforward),OTPE,OTTT, andOSTTP. All are exported at the top level.Added a
_compute_learning_signalhook toETraceVjpAlgorithmto support target-projection algorithms (OSTTP) without disrupting the existing D-RTRL and pp-prop paths.Added supporting trace helpers:
PresynapticTrace,KappaFilter,FixedRandomFeedback, and target-signal extraction utilities.Algorithms are cross-checked for regime equivalence and verified to decrease loss in integration smoke tests.
ETP Compiler Rewrite#
Rewrote the eligibility-trace compiler to dispatch on primitive-type identity rather than string-matching op or trace names, with structured, leveled diagnostics (
DiagnosticKind,DiagnosticLevel,CompilationRecord) replacing ad-hoc warnings.Added compile-time diagnostics that surface previously silent issues — e.g.
TRAINABLE_INVAR_NOT_PARAMSTATEflags a trainable input (such as a constant bias) that does not trace to aParamState, so users can wrap it intentionally instead of silently losing its gradient.
Multi-Trainable-Input ETP Primitives (Bias Gradients)#
Generalized every ETP primitive from a single-“weight” assumption to an arbitrary named dict of trainable inputs. This fixes a silent bias-gradient drop and a LoRA executor signature mismatch in one coherent refactor.
Migrated all built-in primitives (
elemwise, densemm/mv,conv,sparsemm/mv, andlora) to the dict-based rule API with first-class bias gradient support, each verified element-wise against a BPTT oracle.Fixed layout-aware axis handling in conv primitives (1D/2D, NHWC/NCHW, OIHW/HWIO kernel layouts) that previously corrupted gradients on non-default layouts, and fixed non-square dense weight broadcasting in
_mm_dt_to_t.Eligibility traces are now stored as per-key dicts; the transitional legacy-array adapter has been fully removed.
Performance#
D-RTRL einsum fast path (
fast_solve=True, default on): replaces nestedvmap-of-vjpand per-steplax.condoverhead with direct einsum kernels formm/mv/elemwise; conv/sparse/LoRA fall back to the legacy path.Reduced-precision trace storage (
trace_dtype, e.g. bf16/fp16) halves the dominantB*N^2trace bandwidth on GPU/TPU while keeping Jacobians, learning signals, and final gradients in fp32. DefaultNonepreserves exact behavior.Multi-step trace fusion: the per-step eligibility-trace roll for exact algorithms (D-RTRL, pp-prop) is now threaded into the graph executor’s forward scan, eliminating an
O(T × Jacobian)HBM round-trip (traced scan count drops 3 → 2). Opt-in and multi-step-only; single-step/SNN paths are unchanged.Branch-free spectrum/vector normalization to restore XLA fusion across steps.
Primitive Registration Simplification#
Removed
ETPPrimitiveSpecand the spec-based registration layer; invar/ outvar layout metadata (trainable_invars_fn,x_invar_index,y_outvar_index) now lives in internal registries populated directly throughregister_primitivekeyword arguments.
Package Restructuring#
Consolidated the eligibility-trace code into a single flat
_etrace_algorithmspackage, merging the former_etrace_vjp/,_etrace_algorithms.py,_etrace_graph_executor.py, and_snn_algorithms/modules. The top-level public API is unchanged.Split the algorithm base hierarchy into dedicated modules:
ParamDimVjpAlgorithm(D-RTRL) andIODimVjpAlgorithm(pp-prop) now live in their own files, withD_RTRL/pp_propas thin subclasses.Removed the experimental hybrid online-learning method.
Typing & Packaging#
The package is now PEP 561 compliant: ships a
py.typedmarker so downstream users receive inline type hints.Added a pragmatic
mypyconfiguration and wired type checking plus packaging verification (python -m build,py.typedpresence) into CI.
Testing#
Added a BPTT gradient oracle and a layered correctness test suite (P2–P8): per-operator rule oracles, public-API contract tests, exact-class element-wise equivalence with BPTT, approximate-class direction-alignment checks, transform/integration invariance, and per-cell compiler relation guardrails tied to the cell registry.
Documentation#
Converted all public-API docstrings to NumPy-doc style with math, references, and runnable examples.
Documentation is now self-hosted at
brainx.chaobrain.com/braintrace/, with refreshed RTD links and a WebP logo.
Dependencies & Tooling#
Replaced
brainunitwithsaiunitthroughout for unit handling.Numerous CI/CD upgrades (checkout, setup-python, artifact actions, sphinx and theme requirements); docs deploy on release publication.
Deprecations#
The entire v0.1.x class-based operator/parameter API is deprecated in favor
of the new primitive-based ETP user-API. The legacy classes still work —
they are thin back-compatibility shims that route through the new primitives —
but each emits a DeprecationWarning (once per class, per process) on first
use, and they will be removed in a future release. Migrate at your convenience.
Deprecated operator classes → new primitive functions:
Deprecated (v0.1.x) |
Use instead (v0.2.0) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
the ETP primitive functions above |
Deprecated parameter classes → brainstate.ParamState + a primitive:
Deprecated (v0.1.x) |
Use instead (v0.2.0) |
|---|---|
|
|
|
|
|
|
|
plain objects with plain JAX ops |
The stop_param_gradients context manager and the general_y2w helper are kept
as no-op compatibility shims and have no effect on the new primitive path.
Breaking Changes#
OSTL factory removed — use
OSTLRecurrentorOSTLFeedforwarddirectly instead of the formerOSTLfactory function.OTTTandOTPErequire an explicitleak— the membrane leak is no longer inferred frommodel.states()(it silently picked a wrong value on heterogeneous/multi-population models). Both now also reject hidden groups withnum_state > 1at compile time, as collapsing thenum_stateaxis has no theoretical basis for these LIF-derived rules.OTPEadditionally documents a narrower feed-forward / single-layer / global-scalar-leak regime.Unit dependency change — code relying on
brainunitinternals should migrate tosaiunit.ETPPrimitiveSpecremoved — custom primitives must register layout metadata viaregister_primitivekeyword arguments (trainable_invars_fn,x_invar_index,y_outvar_index).
Migration Guide#
OSTL#
# Old
algo = OSTL(model, ...) # factory
# New — choose the regime explicitly
algo = OSTLRecurrent(model, ...)
# or
algo = OSTLFeedforward(model, ...)
OTTT / OTPE#
# Old
algo = OTTT(model, ...) # leak inferred from model.states()
# New — pass the postsynaptic membrane leak explicitly
algo = OTTT(model, leak=0.9, ...)
Custom ETP primitives#
# Old: register_primitive_spec(ETPPrimitiveSpec(...))
# New: pass layout metadata directly
register_primitive(
prim,
trainable_invars_fn=...,
x_invar_index=...,
y_outvar_index=...,
)
Deprecated class-based API → primitive-based API#
# Old (v0.1.x): wrap the weight in an ETraceParam bound to an op
self.w = braintrace.ETraceParam({'weight': w}, braintrace.MatMulOp())
y = self.w.execute(x)
# New (v0.2.0): a plain ParamState + the ETP primitive function
self.w = brainstate.ParamState({'weight': w})
y = braintrace.matmul(x, self.w.value)
The element-wise case is analogous (ElemWiseParam/ElemWiseOp →
brainstate.ParamState + braintrace.element_wise); to keep a weight out of
the eligibility-trace graph, use a plain brainstate.ParamState with ordinary
JAX ops instead of NonTempParam / FakeETraceParam.
Version#
Bumped version from
0.1.3to0.2.0
Version 0.1.2#
Major Changes#
Import Path Migration#
Updated dependency from
brainpytobrainpy.state: Migrated all imports to use the more specificbrainpy.statemoduleUpdated
braintrace/nn/_readout.py: Changed neuron model imports frombrainpytobrainpy.stateUpdated all documentation notebooks (12 files): Concepts, RNN/SNN online learning, batching, state management, and graph visualization tutorials
Updated example scripts (4 files): COBA EI RSNN, SNN evaluation, feedforward conv SNN, and SNN models
Updated
requirements.txtandpyproject.tomlto specifybrainpy-stateas dependencyTotal: 19 files changed with improved module structure and consistency
New Algorithms#
Added PP-Prop (Pseudo-Prospective Propagation) algorithm: New eligibility trace algorithm in VJP-based methods
Added
pp_proptobraintrace/_etrace_vjp/esd_rtrl.pyUpdated
docs/apis/algorithms.rstto include PP-Prop in algorithm documentation
Python 3.14 Support#
Added Python 3.14 compatibility: Updated project metadata to officially support Python 3.14
Updated
pyproject.tomlclassifiers to include Python 3.14
Bug Fixes#
Fixed version info tuple creation: Corrected the version info structure in
braintrace/__init__.pyEnsures proper version tuple formatting for compatibility checks
CI/CD Improvements#
Updated GitHub Actions workflow: Bumped
actions/upload-artifactfrom v5 to v6Modernized CI/CD pipeline with latest GitHub Actions versions
Improved artifact upload reliability and performance
Documentation Updates#
Updated documentation links: Refreshed links in concept documentation for better navigation
Updated
docs/quickstart/concepts-en.ipynb(116 lines modified)Updated
docs/quickstart/concepts-zh.ipynb(104 lines modified)
Breaking Changes#
Dependency Change:
Dependency name change: The project now requires
brainpy-stateinstead ofbrainpyUpdate your
requirements.txtor installation commands accordingly
# Old (0.1.1)
pip install brainpy
# New (0.1.2)
pip install brainpy-state
Import path update: Update neuron model imports to use
brainpy.state
# New (0.1.2)
from brainpy.state import IF, LIF, ALIF
Migration Guide#
Update Dependencies#
Replace brainpy with brainpy-state in your project dependencies:
pip uninstall brainpy
pip install brainpy-state
Update Import Statements#
If you have custom code importing neuron models, update to use brainpy.state:
# Find and replace in your codebase
# from brainpy import → from brainpy.state import
Version#
Bumped version from
0.1.1to0.1.2
Version 0.1.1#
Major Changes#
Project Rename: BrainScale → BrainTrace#
Renamed the entire project from
brainscaletobraintrace: This change reflects the project’s focus on eligibility trace-based learning algorithmsPackage directory renamed from
brainscale/tobraintrace/All internal imports updated from
brainscaletobraintraceUpdated all 95 files including source code, tests, documentation, and examples
Updated
pyproject.tomlwith new project name and metadataUpdated README with new project branding and citation information
VJP-Based Eligibility Trace Algorithms#
Added new VJP-based eligibility trace module (
_etrace_vjp/): Comprehensive implementation of vector-Jacobian product based algorithmsbase.py: Core base classes and utilities for VJP operations (671 lines)d_rtrl.py: Diagonal Real-Time Recurrent Learning implementation (756 lines)esd_rtrl.py: Efficient Sparse Diagonal RTRL implementation (847 lines)hybrid.py: Hybrid approaches combining multiple techniques (604 lines)graph_executor.py: Graph-based execution for VJP computationsmisc.py: Miscellaneous utilities including matrix spectrum normalization
Refactored VJP algorithm structure: Migrated from monolithic
_etrace_vjp_algorithms.py(2,888 lines) to modular architectureBetter separation of concerns
Improved testability with dedicated test files (
d_rtrl_test.py,esd_rtrl_test.py,graph_executor_test.py)
Logo and Branding#
Updated logo format from JPG to PNG for consistency
Updated logo across documentation
Breaking Changes#
Package Rename:
Import path change: All imports must now use
braintraceinstead ofbrainscale
# Old (0.1.0)
import brainscale
from brainscale import EligibilityTrace
from brainscale.nn import Linear, GRUCell
# New (0.1.1)
import braintrace
from braintrace import EligibilityTrace
from braintrace.nn import Linear, GRUCell
Installation: Package name changed from
brainscaletobraintrace
# Old
pip install brainscale
# New
pip install braintrace
Migration Guide#
Update Import Statements#
Replace all occurrences of brainscale with braintrace:
# Find and replace in your codebase
# brainscale → braintrace
VJP Algorithm Usage#
The new VJP-based algorithms are now available through the modular interface:
Version#
Bumped version from
0.1.0to0.1.1
Version 0.1.0#
Major Changes#
State Management Refactoring#
Renamed
ETraceStatetoHiddenState: All eligibility trace state management now uses the more generalHiddenStatenaming conventionUpdated across
_etrace_algorithms.py,_etrace_concepts.py,_state_managment.pyAdded deprecation warnings for
ETraceStateto guide users tobrainstate.HiddenStateUpdated all documentation and examples to reflect the new naming
Renamed
ETraceGroupStatetoHiddenGroupState: Improved consistency in hidden state handlingUpdated in
_etrace_compiler_hidden_group.pyAdded deprecation warnings for backward compatibility
Added deprecation handling: Implemented
__getattr__in main__init__.pyto provide helpful warnings when using deprecated names:ETraceState→brainstate.HiddenStateETraceGroupState→brainstate.HiddenGroupStateETraceTreeState→brainstate.HiddenTreeState
Neural Network Module Reorganization#
Consolidated neural network modules: Removed standalone neuron, synapse, and activation modules, migrating them to
brainstateandbrainpyecosystemsDeleted files:
brainscale/nn/_neurons.py(IF, LIF, ALIF now inbrainpy.state)brainscale/nn/_synapses.py(Expon, Alpha, DualExpon, STP, STD now inbrainpy.state)brainscale/nn/_elementwise.py(activation functions now inbrainstate.nn)brainscale/nn/_poolings.py(pooling layers now inbrainstate.nn)
Renamed
_rate_rnns.pyto_rnn.py: Simplified module naming for better clarityAdded comprehensive deprecation warnings in
nn.__getattr__: Automatically redirects users to the correct modules:Neuron models (IF, LIF, ALIF) →
brainpy.stateSynapse models (Expon, Alpha, DualExpon, STP, STD) →
brainpy.stateActivation functions (ReLU, Sigmoid, etc.) →
brainstate.nnPooling layers (MaxPool, AvgPool, etc.) →
brainstate.nnDropout layers →
brainstate.nn
API Improvements#
Normalization parameter standardization: Renamed
normalized_shapetoin_sizeacross all normalization layers for consistencyUpdated in
_normalizations.pyfor LayerNorm, GroupNorm, InstanceNorm, etc.Improved clarity and consistency with other layer APIs
Enhanced input dimension validation: Improved error checking in convolutional layers to catch dimension mismatches early
Refactored imports for consistency: Updated all internal imports to use
braintoolsfor optimization and initialization utilities consistently across the codebase
Testing Infrastructure#
Added comprehensive unit tests for neural network modules:
_conv_test.py: 868 lines of tests for convolutional layers (Conv1d, Conv2d, Conv3d, ConvTranspose)_linear_test.py: 658 lines of tests for linear layers (Linear, Identity)_normalizations_test.py: 695 lines of tests for normalization layers (LayerNorm, BatchNorm, GroupNorm, etc.)_readout_test.py: 763 lines of tests for readout layers (LeakyRateReadout, LeakySpikeReadout)_rnn_test.py: 710 lines of tests for RNN cells (VanillaRNNCell, GRUCell, LSTMCell, MGUCell, etc.)Total: 3,694 lines of new test coverage
Documentation Updates#
Streamlined API documentation: Updated
docs/apis/nn.rstto remove redundant sections and enhance RNN overviewUpdated tutorials and examples: All 16 tutorial notebooks and 11 example scripts updated to reflect new APIs:
Concepts tutorials (en/zh)
RNN and SNN online learning guides
Batching strategies documentation
ETrace state management examples
Graph visualization tutorials
Code Quality Improvements#
Removed redundant docstrings: Cleaned up duplicate documentation in
LeakyRateReadoutandLeakySpikeReadoutImproved code organization: Streamlined
__all__definitions across all modulesEnhanced readability: Consistent import structure and better code formatting throughout
Dependency Updates#
Updated
requirements.txt: Refined dependency specifications to ensure compatibility with latestbrainstateandbrainpyversionsUpdated
pyproject.toml: Bumped version to 0.1.0 and updated project metadata
Breaking Changes#
API Changes:
State class renaming (with deprecation warnings):
ETraceState→ Usebrainstate.HiddenStateinsteadETraceGroupState→ Usebrainstate.HiddenGroupStateinsteadETraceTreeState→ Usebrainstate.HiddenTreeStateinstead
Neural network component migration (with deprecation warnings):
Neuron models (IF, LIF, ALIF) → Use
brainpy.statemoduleSynapse models (Expon, Alpha, etc.) → Use
brainpy.statemoduleActivation functions → Use
brainstate.nnmodulePooling layers → Use
brainstate.nnmodule
Normalization parameter rename:
normalized_shape→in_size(for LayerNorm, GroupNorm, etc.)
Module file reorganization:
nn/_rate_rnns.py→nn/_rnn.pyRemoved:
_neurons.py,_synapses.py,_elementwise.py,_poolings.py
Migration Guide#
For State Management:#
# Old (0.0.11)
from brainscale import ETraceState, ETraceGroupState
# New (0.1.0)
from brainstate import HiddenState, HiddenGroupState
For Neural Network Components:#
# Old (0.0.11)
from brainscale.nn import IF, LIF, Expon, ReLU, MaxPool2d
# New (0.1.0)
from brainpy.state import IF, LIF, Expon
from brainstate.nn import ReLU, MaxPool2d
For Normalization Layers:#
# Old (0.0.11)
norm = LayerNorm(normalized_shape=(128,))
# New (0.1.0)
norm = LayerNorm(in_size=128)
Note: All deprecated APIs include automatic warnings that will guide you to the correct replacements. The old APIs will continue to work in 0.1.0 but will be removed in a future release.
Version#
Bumped version from
0.0.11to0.1.0
Version 0.0.11#
Major Changes#
Import Refactoring#
Migrated imports from
brainstatetobraintools: All initialization-related imports now usebraintools.initinstead ofbrainstate.initUpdated imports in:
brainscale/nn/_neurons.py: Changedfrom brainstate import inittofrom braintools import initbrainscale/nn/_linear.py: Changedfrom brainstate import inittofrom braintools import initbrainscale/nn/_conv.py: Updated initialization importsbrainscale/nn/_synapses.py: Updated initialization importsbrainscale/nn/_readout.py: Updated initialization imports
Migrated neural network model imports from
brainstate.nntobrainpy: Updated base classes for neuron modelsIF,LIF,ALIFnow inherit frombrainpyinstead ofbrainstate.nnMaintained API compatibility while using the new
brainpybackend
Updated functional API calls: Changed from
brainstate.functional.sigmoidtobrainstate.nn.sigmoidin RNN cells
Dependency Updates#
Added
brainpyas a required dependency inrequirements.txt
Documentation Enhancements#
Improved docstring formatting across the codebase:
Enhanced parameter documentation with proper type annotations using NumPy-style docstrings
Added missing “Returns” sections to property and method docstrings
Converted inline examples to proper “Examples” sections with code blocks
Updated documentation in:
brainscale/_etrace_algorithms.py: EnhancedEligibilityTraceandETraceAlgorithmdocumentationbrainscale/_etrace_compiler_base.py: Improved parameter and return type documentationbrainscale/_etrace_compiler_module_info.py: Enhanced module documentation
Core Algorithm Updates#
RNN State Management: Updated all RNN cells to use
braintools.init.paramfor state initialization and resetValinaRNNCell: Updatedinit_state()andreset_state()methodsGRUCell: Updated state management and activation functionsCFNCell: Updated forget and input gate implementationsMGUCell: Updated minimal gated unit state handling
Test Updates#
Refactored test imports: Updated test files to use new import paths
brainscale/_etrace_model_test.py: Updated with new import structurebrainscale/_etrace_vjp_algorithms_test.py: Aligned with new API
Version#
Bumped version from
0.0.10to0.0.11
Files Changed (17 files)#
.gitignore: Added new patternsbrainscale/__init__.py: Updated version numberbrainscale/_etrace_algorithms.py: Enhanced documentation and importsbrainscale/_etrace_compiler_base.py: Improved documentationbrainscale/_etrace_compiler_graph.py: Minor updatesbrainscale/_etrace_compiler_hidden_group.py: Minor updatesbrainscale/_etrace_compiler_module_info.py: Enhanced documentationbrainscale/_etrace_model_test.py: Updated test importsbrainscale/_etrace_vjp_algorithms_test.py: Updated test importsbrainscale/_etrace_vjp_graph_executor.py: Updated importsbrainscale/nn/_conv.py: Migrated to braintools importsbrainscale/nn/_linear.py: Migrated to braintools importsbrainscale/nn/_neurons.py: Migrated to brainpy and braintoolsbrainscale/nn/_rate_rnns.py: Migrated to braintools and updated functional APIsbrainscale/nn/_readout.py: Updated importsbrainscale/nn/_synapses.py: Updated importsrequirements.txt: Added brainpy dependency
Breaking Changes#
None. All changes maintain backward compatibility at the API level.
Migration Guide#
If you have custom code using brainscale:
No changes required for end users
If extending brainscale internally, note that initialization utilities now come from
braintoolsinstead ofbrainstate