braintrace 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. {braintrace-0.2.6 → braintrace-0.2.7}/PKG-INFO +4 -4
  2. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/base.py +7 -2
  3. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/d_rtrl_test.py +90 -0
  4. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/io_dim_vjp.py +38 -7
  5. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/io_dim_vjp_test.py +21 -0
  6. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/param_dim_vjp.py +29 -0
  7. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/random_projection_vjp.py +33 -11
  8. braintrace-0.2.7/braintrace/_algorithm/tests/output_bypass_test.py +362 -0
  9. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/scan_descent_support_test.py +8 -13
  10. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/while_support_test.py +116 -0
  11. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/vjp_base.py +115 -14
  12. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/vjp_graph_executor.py +45 -6
  13. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compatible_imports.py +24 -2
  14. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/base.py +8 -7
  15. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/canonicalize.py +4 -3
  16. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/diagnostics.py +11 -0
  17. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/graph.py +22 -1
  18. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hid_param_op.py +14 -10
  19. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hidden_group.py +22 -0
  20. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hidden_group_test.py +113 -0
  21. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hidden_pertubation.py +239 -19
  22. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hidden_pertubation_test.py +62 -19
  23. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/module_info.py +21 -8
  24. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/module_info_test.py +83 -0
  25. braintrace-0.2.7/braintrace/_compiler/output_bypass.py +277 -0
  26. braintrace-0.2.7/braintrace/_compiler/output_bypass_test.py +220 -0
  27. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/scan_descent.py +15 -14
  28. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/oracle_models_test.py +9 -6
  29. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_version.py +1 -1
  30. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace.egg-info/PKG-INFO +4 -4
  31. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace.egg-info/SOURCES.txt +3 -0
  32. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace.egg-info/requires.txt +1 -1
  33. {braintrace-0.2.6 → braintrace-0.2.7}/pyproject.toml +5 -5
  34. {braintrace-0.2.6 → braintrace-0.2.7}/LICENSE +0 -0
  35. {braintrace-0.2.6 → braintrace-0.2.7}/README.md +0 -0
  36. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/__init__.py +0 -0
  37. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/__init___test.py +0 -0
  38. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/__init__.py +0 -0
  39. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/_common.py +0 -0
  40. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/_common_test.py +0 -0
  41. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/axes.py +0 -0
  42. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/axes_test.py +0 -0
  43. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/base_test.py +0 -0
  44. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/d_rtrl.py +0 -0
  45. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/dni.py +0 -0
  46. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/dni_test.py +0 -0
  47. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/e_prop.py +0 -0
  48. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/e_prop_test.py +0 -0
  49. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/graph_executor.py +0 -0
  50. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/graph_executor_test.py +0 -0
  51. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/ostl.py +0 -0
  52. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/ostl_test.py +0 -0
  53. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/param_dim_vjp_test.py +0 -0
  54. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/pp_prop.py +0 -0
  55. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/pp_prop_test.py +0 -0
  56. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/random_projection_vjp_test.py +0 -0
  57. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/sequence.py +0 -0
  58. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/sequence_test.py +0 -0
  59. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/snap_n.py +0 -0
  60. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/snap_n_test.py +0 -0
  61. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/approx_correctness_test.py +0 -0
  62. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/axis_acceptance_test.py +0 -0
  63. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/axis_discrimination_test.py +0 -0
  64. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/axis_golden_test.py +0 -0
  65. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +0 -0
  66. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/cross_check_test.py +0 -0
  67. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/diagnostic_exploration_test.py +0 -0
  68. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/exact_correctness_test.py +0 -0
  69. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/public_api_test.py +0 -0
  70. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/scan_fusion_test.py +0 -0
  71. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/snn_model_correctness_test.py +0 -0
  72. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/tests/transform_correctness_test.py +0 -0
  73. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/three_factor.py +0 -0
  74. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/three_factor_test.py +0 -0
  75. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/uoro.py +0 -0
  76. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/uoro_test.py +0 -0
  77. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/vjp_base_test.py +0 -0
  78. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_algorithm/vjp_graph_executor_test.py +0 -0
  79. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compatible_imports_test.py +0 -0
  80. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compile.py +0 -0
  81. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compile_test.py +0 -0
  82. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/__init__.py +0 -0
  83. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/base_test.py +0 -0
  84. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/canonicalize_test.py +0 -0
  85. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/diagnostics_test.py +0 -0
  86. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/graph_test.py +0 -0
  87. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/hid_param_op_test.py +0 -0
  88. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/jaxpr_graph.py +0 -0
  89. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/jaxpr_graph_test.py +0 -0
  90. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/position_graph.py +0 -0
  91. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/position_graph_test.py +0 -0
  92. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/report.py +0 -0
  93. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/report_test.py +0 -0
  94. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/scan_descent_test.py +0 -0
  95. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/tests/cell_relation_guardrail_test.py +0 -0
  96. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/tests/compiler_oracle_test.py +0 -0
  97. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_compiler/tests/compiler_property_test.py +0 -0
  98. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_grad_exponential.py +0 -0
  99. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_grad_exponential_test.py +0 -0
  100. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_input_data.py +0 -0
  101. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_input_data_test.py +0 -0
  102. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_legacy/__init__.py +0 -0
  103. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_legacy/_ops.py +0 -0
  104. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_legacy/_ops_test.py +0 -0
  105. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_legacy/_params.py +0 -0
  106. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_legacy/_params_test.py +0 -0
  107. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_misc.py +0 -0
  108. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_misc_test.py +0 -0
  109. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/__init__.py +0 -0
  110. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/_primitive.py +0 -0
  111. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/_primitive_test.py +0 -0
  112. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/_registries.py +0 -0
  113. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/_registries_test.py +0 -0
  114. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/conv.py +0 -0
  115. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/conv_test.py +0 -0
  116. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/dense.py +0 -0
  117. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/dense_test.py +0 -0
  118. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/einsum.py +0 -0
  119. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/einsum_test.py +0 -0
  120. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/elemwise.py +0 -0
  121. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/elemwise_test.py +0 -0
  122. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/embedding.py +0 -0
  123. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/embedding_test.py +0 -0
  124. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/grouped.py +0 -0
  125. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/grouped_test.py +0 -0
  126. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/lora.py +0 -0
  127. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/lora_test.py +0 -0
  128. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/op_rule_oracle.py +0 -0
  129. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/op_rule_oracle_test.py +0 -0
  130. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/sparse.py +0 -0
  131. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_op/sparse_test.py +0 -0
  132. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_state_management.py +0 -0
  133. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_state_management_test.py +0 -0
  134. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/__init__.py +0 -0
  135. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/compiler_models.py +0 -0
  136. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/models.py +0 -0
  137. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/oracle.py +0 -0
  138. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/oracle_models.py +0 -0
  139. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/oracle_test.py +0 -0
  140. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/scenario_catalog.py +0 -0
  141. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_testing/scenario_catalog_test.py +0 -0
  142. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_typing.py +0 -0
  143. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_typing_test.py +0 -0
  144. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/_version_test.py +0 -0
  145. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/__init__.py +0 -0
  146. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/__init___test.py +0 -0
  147. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_conv.py +0 -0
  148. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_conv_test.py +0 -0
  149. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_embedding.py +0 -0
  150. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_embedding_test.py +0 -0
  151. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_linear.py +0 -0
  152. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_linear_test.py +0 -0
  153. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_readout.py +0 -0
  154. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_readout_test.py +0 -0
  155. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_rnn.py +0 -0
  156. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/nn/_rnn_test.py +0 -0
  157. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace/py.typed +0 -0
  158. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace.egg-info/dependency_links.txt +0 -0
  159. {braintrace-0.2.6 → braintrace-0.2.7}/braintrace.egg-info/top_level.txt +0 -0
  160. {braintrace-0.2.6 → braintrace-0.2.7}/setup.cfg +0 -0
  161. {braintrace-0.2.6 → braintrace-0.2.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrace
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Enabling Scalable Online Learning for Brain Dynamics.
5
5
  Author-email: BrainTrace Developers <chao.brain@qq.com>
6
6
  License: Apache-2.0 license
@@ -13,20 +13,20 @@ Classifier: Natural Language :: English
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.13
19
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
20
20
  Classifier: Intended Audience :: Science/Research
21
21
  Classifier: License :: OSI Approved :: Apache Software License
22
22
  Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
23
23
  Classifier: Topic :: Scientific/Engineering :: Mathematics
24
24
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
25
  Classifier: Topic :: Software Development :: Libraries
26
- Requires-Python: >=3.11
26
+ Requires-Python: >=3.12
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: jax>=0.8.0
29
+ Requires-Dist: jax>=0.9.0
30
30
  Requires-Dist: brainstate>=0.5.2
31
31
  Requires-Dist: brainunit
32
32
  Requires-Dist: brainevent>=0.1.2
@@ -21,7 +21,7 @@
21
21
 
22
22
  from __future__ import annotations
23
23
 
24
- from typing import Dict, Any, Optional
24
+ from typing import Dict, Any, Optional, Tuple, cast
25
25
 
26
26
  import brainstate
27
27
 
@@ -257,11 +257,16 @@ class ETraceAlgorithm(SequenceDriverMixin, brainstate.nn.Module):
257
257
  # - the weight states, which is invariant during the training process
258
258
  # - the hidden states, the recurrent states, which may be changed between different training epochs
259
259
  # - the other states, which may be changed between different training epochs
260
+ # ``FlattedDict.split`` returns one ``FlattedDict`` per filter (the
261
+ # stubs only promise a ``NestedDict``)
260
262
  (
261
263
  self._param_states,
262
264
  self._hidden_states,
263
265
  self._other_states
264
- ) = self.graph.module_info.retrieved_model_states.split(brainstate.ParamState, brainstate.HiddenState, ...)
266
+ ) = cast(
267
+ Tuple[brainstate.util.FlattedDict, brainstate.util.FlattedDict, brainstate.util.FlattedDict],
268
+ self.graph.module_info.retrieved_model_states.split(brainstate.ParamState, brainstate.HiddenState, ...),
269
+ )
265
270
 
266
271
  def compile_graph(self, *args: Any) -> None:
267
272
  r"""
@@ -1112,3 +1112,93 @@ class TestCoupledTraceBoundedness:
1112
1112
  assert all(bool(jnp.all(jnp.isfinite(leaf))) for leaf in leaves)
1113
1113
  max_abs = max(float(jnp.max(jnp.abs(leaf))) for leaf in leaves)
1114
1114
  assert max_abs < 1e3, f'eligibility trace grew to {max_abs}'
1115
+
1116
+
1117
+ # ---------------------------------------------------------------------------
1118
+ # Hidden-to-hidden edges without an equation (#184)
1119
+ # ---------------------------------------------------------------------------
1120
+
1121
+ class _LatchModel(brainstate.nn.Module):
1122
+ """``latch`` depends only on the input; ``V`` reads the old latch.
1123
+
1124
+ ``workaround=True`` writes ``latch <- new + 0 * latch`` (the pre-fix
1125
+ workaround), which keeps a hidden-to-hidden edge in the jaxpr.
1126
+ """
1127
+
1128
+ def __init__(self, workaround=False, n_in=3, n=4):
1129
+ super().__init__()
1130
+ self.workaround = workaround
1131
+ self.n = n
1132
+ self.w = brainstate.ParamState(0.5 * jax.random.normal(jax.random.PRNGKey(0), (n_in, n)))
1133
+
1134
+ def init_state(self, batch_size=None, **kwargs):
1135
+ shape = (self.n,) if batch_size is None else (batch_size, self.n)
1136
+ self.latch = brainstate.HiddenState(jnp.zeros(shape))
1137
+ self.V = brainstate.HiddenState(jnp.zeros(shape))
1138
+
1139
+ def update(self, x):
1140
+ I = self.latch.value
1141
+ new = braintrace.matmul(x, self.w.value)
1142
+ self.latch.value = new + 0 * self.latch.value if self.workaround else new
1143
+ self.V.value = 0.9 * self.V.value + I
1144
+ return jnp.tanh(self.V.value)
1145
+
1146
+
1147
+ class _DelayLineModel(brainstate.nn.Module):
1148
+ """Two-step delay line ``h2^t = h1^{t-1}`` (a pure copy) feeding ``V``."""
1149
+
1150
+ def __init__(self, workaround=False, n_in=3, n=4):
1151
+ super().__init__()
1152
+ self.workaround = workaround
1153
+ self.n = n
1154
+ self.w = brainstate.ParamState(0.5 * jax.random.normal(jax.random.PRNGKey(1), (n_in, n)))
1155
+
1156
+ def init_state(self, batch_size=None, **kwargs):
1157
+ shape = (self.n,) if batch_size is None else (batch_size, self.n)
1158
+ self.h1 = brainstate.HiddenState(jnp.zeros(shape))
1159
+ self.h2 = brainstate.HiddenState(jnp.zeros(shape))
1160
+ self.V = brainstate.HiddenState(jnp.zeros(shape))
1161
+
1162
+ def update(self, x):
1163
+ old1, old2 = self.h1.value, self.h2.value
1164
+ self.V.value = 0.9 * self.V.value + old2
1165
+ self.h1.value = 0.5 * old1 + braintrace.matmul(x, self.w.value)
1166
+ self.h2.value = old1 + 0 * old2 if self.workaround else old1
1167
+ return jnp.tanh(self.V.value)
1168
+
1169
+
1170
+ class TestEquationFreeHiddenEdges:
1171
+ """Input-only latches and pure copies train exactly under D-RTRL (#184)."""
1172
+
1173
+ xs = jax.random.uniform(jax.random.PRNGKey(7), (8, 1, 3))
1174
+
1175
+ @pytest.mark.parametrize('model_cls', [_LatchModel, _DelayLineModel])
1176
+ @pytest.mark.parametrize('chunk_size', [1, 3])
1177
+ def test_d_rtrl_matches_bptt(self, model_cls, chunk_size):
1178
+ from braintrace._testing.oracle import (
1179
+ assert_model_is_live, assert_param_gradients_close,
1180
+ bptt_param_gradients, chunked_online_param_gradients,
1181
+ )
1182
+ assert_model_is_live(model_cls, self.xs)
1183
+ online = chunked_online_param_gradients(
1184
+ model_cls, self.xs, chunk_size=chunk_size,
1185
+ algo_factory=lambda m: braintrace.D_RTRL(m, vjp_method='multi-step'))
1186
+ assert_param_gradients_close(online, bptt_param_gradients(model_cls, self.xs), atol=1e-5)
1187
+
1188
+ @pytest.mark.parametrize('model_cls', [_LatchModel, _DelayLineModel])
1189
+ @pytest.mark.parametrize('algo', ['d_rtrl', 'eprop'])
1190
+ def test_matches_workaround(self, model_cls, algo):
1191
+ xs = self.xs[:, 0]
1192
+ ys = jnp.full((xs.shape[0], 4), 0.3)
1193
+
1194
+ def grads(workaround):
1195
+ net = model_cls(workaround)
1196
+ brainstate.nn.init_all_states(net)
1197
+ learner = braintrace.compile(net, algo, xs[0])
1198
+ return learner.etrace_grad(
1199
+ xs, ys, step_fn=lambda x, y: jnp.mean((learner(x) - y) ** 2))
1200
+
1201
+ fixed, ref = grads(False), grads(True)
1202
+ for k in ref:
1203
+ assert float(jnp.abs(ref[k]).sum()) > 1e-6
1204
+ npt.assert_allclose(fixed[k], ref[k], atol=1e-6)
@@ -34,6 +34,7 @@ import jax
34
34
  import jax.numpy as jnp
35
35
  import brainunit as u
36
36
 
37
+ from braintrace._compatible_imports import shaped_aval
37
38
  from braintrace._compiler import ControlFlowPolicy, HiddenGroup, HiddenParamOpRelation
38
39
  from braintrace._op import (
39
40
  etp_elemwise_p,
@@ -262,8 +263,8 @@ def _init_IO_dim_state(
262
263
  if x_key not in etrace_xs:
263
264
  x_repr_fn = get_pp_x_repr(relation.primitive)
264
265
  if x_repr_fn is None:
265
- shape = relation.x_var.aval.shape
266
- dtype = relation.x_var.aval.dtype
266
+ shape = shaped_aval(relation.x_var).shape
267
+ dtype = shaped_aval(relation.x_var).dtype
267
268
  else:
268
269
  # the trace filters the primitive's x *representation*
269
270
  # (e.g. embedding: the one-hot encoding of its integer
@@ -272,12 +273,12 @@ def _init_IO_dim_state(
272
273
  x_aval = jax.eval_shape(
273
274
  lambda x_, _fn=x_repr_fn, _w=weight_avals: _fn(x_, _w),
274
275
  jax.ShapeDtypeStruct(
275
- relation.x_var.aval.shape, relation.x_var.aval.dtype),
276
+ shaped_aval(relation.x_var).shape, shaped_aval(relation.x_var).dtype),
276
277
  )
277
278
  shape, dtype = x_aval.shape, x_aval.dtype
278
279
  etrace_xs[x_key] = EligibilityTrace(u.math.zeros(shape, dtype))
279
280
 
280
- y_shape = relation.y_var.aval.shape
281
+ y_shape = shaped_aval(relation.y_var).shape
281
282
  group: HiddenGroup
282
283
  for group in relation.hidden_groups:
283
284
  # Exact match required, or (elemwise only) allow trailing-dim match
@@ -613,7 +614,7 @@ def _solve_IO_dim_weight_gradients(
613
614
  # gate the branch.)
614
615
  elemwise_batched = (
615
616
  relation.primitive is etp_elemwise_p
616
- and len(group.varshape) > relation.y_var.aval.ndim
617
+ and len(group.varshape) > shaped_aval(relation.y_var).ndim
617
618
  )
618
619
 
619
620
  if fast_solve:
@@ -926,8 +927,9 @@ class IODimVjpAlgorithm(ETraceVjpAlgorithm):
926
927
  find_this_weight = True
927
928
 
928
929
  # get the weight_op input
929
- wx_var = etrace_x_key(relation.x_var)
930
- if wx_var is not None:
930
+ # (an element-wise relation has no x trace)
931
+ if relation.x_var is not None:
932
+ wx_var = etrace_x_key(relation.x_var)
931
933
  etrace_xs[wx_var] = self.etrace_xs[wx_var].value
932
934
 
933
935
  # get the weight_op df
@@ -1106,6 +1108,35 @@ class IODimVjpAlgorithm(ETraceVjpAlgorithm):
1106
1108
 
1107
1109
  return hist_etrace_vals
1108
1110
 
1111
+ def _solve_previous_trace_gradients(
1112
+ self,
1113
+ running_index: int,
1114
+ etrace_h2w_at_t_minus_1: Tuple[
1115
+ Dict[ETraceX_Key, jax.Array],
1116
+ Dict[ETraceDF_Key, jax.Array]
1117
+ ],
1118
+ dl_to_hidden_groups: Sequence[jax.Array],
1119
+ weight_vals: Dict[Path, PyTree],
1120
+ ) -> Dict[Path, PyTree]:
1121
+ """Contract the previous trace with the hidden output-bypass signal.
1122
+
1123
+ See :meth:`ETraceVjpAlgorithm._solve_previous_trace_gradients`. The
1124
+ previous trace is one update younger, so its warm-up bias correction
1125
+ is indexed by ``running_index - 1``.
1126
+ """
1127
+ dG_weights: Dict[Path, Any] = {path: None for path in self.param_states.keys()}
1128
+ _solve_IO_dim_weight_gradients(
1129
+ etrace_h2w_at_t_minus_1,
1130
+ dG_weights,
1131
+ dl_to_hidden_groups,
1132
+ self.graph.hidden_param_op_relations,
1133
+ weight_vals,
1134
+ running_index - 1,
1135
+ self.decay_f,
1136
+ fast_solve=self.fast_solve,
1137
+ )
1138
+ return dG_weights
1139
+
1109
1140
  def _solve_weight_gradients(
1110
1141
  self,
1111
1142
  running_index: int,
@@ -247,6 +247,27 @@ class TestStateLifecycle:
247
247
  assert isinstance(result, tuple)
248
248
  assert len(result) == 2
249
249
 
250
+ def test_get_etrace_of_elementwise_weight(self):
251
+ # An element-wise relation has no x trace; only its df traces are returned.
252
+ class Leaky(brainstate.nn.Module):
253
+ def __init__(self):
254
+ super().__init__()
255
+ self.w = brainstate.ParamState(jnp.full((3,), 0.5))
256
+
257
+ def init_state(self, *args, **kwargs):
258
+ self.h = brainstate.HiddenState(jnp.zeros(3))
259
+
260
+ def update(self, x):
261
+ self.h.value = 0.9 * self.h.value + braintrace.element_wise(self.w.value) * x
262
+ return self.h.value
263
+
264
+ model = Leaky()
265
+ brainstate.nn.init_all_states(model)
266
+ algo = _compiled(model)
267
+ xs, dfs = algo.get_etrace_of(model.w)
268
+ assert xs == {}
269
+ assert len(dfs) == 1
270
+
250
271
  def test_get_etrace_of_plain_weight_raises(self):
251
272
  model = _build(om.tanh_rnn)
252
273
  algo = _compiled(model)
@@ -1408,6 +1408,35 @@ class ParamDimVjpAlgorithm(ETraceVjpAlgorithm):
1408
1408
 
1409
1409
  return new_etrace
1410
1410
 
1411
+ def _solve_previous_trace_gradients(
1412
+ self,
1413
+ running_index: int,
1414
+ etrace_h2w_at_t_minus_1: Dict[ETraceWG_Key, PyTree],
1415
+ dl_to_hidden_groups: Sequence[jax.Array],
1416
+ weight_vals: Dict[Path, PyTree],
1417
+ ) -> Dict[Path, PyTree]:
1418
+ """Contract the previous trace with the hidden output-bypass signal.
1419
+
1420
+ See :meth:`ETraceVjpAlgorithm._solve_previous_trace_gradients`. With
1421
+ trace filters active, the previous *filtered* trace (the filter's
1422
+ current value, not yet advanced this step) is the one paired.
1423
+ """
1424
+ etrace = dict(etrace_h2w_at_t_minus_1)
1425
+ if self._trace_filters:
1426
+ for key, flt in self._trace_filters.items():
1427
+ if key in etrace:
1428
+ etrace[key] = flt.value
1429
+ dG_weights: Dict[Path, Any] = {path: None for path in self.param_states}
1430
+ _solve_param_dim_weight_gradients(
1431
+ etrace,
1432
+ dG_weights,
1433
+ dl_to_hidden_groups,
1434
+ self.graph.hidden_param_op_relations,
1435
+ weight_vals,
1436
+ fast_solve=self.fast_solve,
1437
+ )
1438
+ return dG_weights
1439
+
1411
1440
  def _solve_weight_gradients(
1412
1441
  self,
1413
1442
  running_index: int,
@@ -665,6 +665,36 @@ class RandomProjectionVjpAlgorithm(ETraceVjpAlgorithm):
665
665
  # the boundary contraction
666
666
  # ------------------------------------------------------------------ #
667
667
 
668
+ def _solve_previous_trace_gradients(
669
+ self,
670
+ running_index: int,
671
+ etrace_h2w_at_t_minus_1: Dict[str, Any],
672
+ dl_to_hidden_groups: Sequence[jax.Array],
673
+ weight_vals: Dict[Path, PyTree],
674
+ ) -> Dict[Path, PyTree]:
675
+ """Contract the previous trace with the hidden output-bypass signal.
676
+
677
+ See :meth:`ETraceVjpAlgorithm._solve_previous_trace_gradients`.
678
+ """
679
+ return self._contract_trace(etrace_h2w_at_t_minus_1, dl_to_hidden_groups)
680
+
681
+ def _contract_trace(
682
+ self,
683
+ etrace: Dict[str, Any],
684
+ dl_to_hidden_groups: Sequence[jax.Array],
685
+ ) -> Dict[Path, Any]:
686
+ # ``sum_g (signal_g . s_tilde_g) * theta_tilde_g``
687
+ dG_weights: Dict[Path, Any] = {path: None for path in self.param_states}
688
+ for group in self.graph.hidden_groups:
689
+ gi = group.index
690
+ signal = u.get_mantissa(dl_to_hidden_groups[gi])
691
+ s_tilde = etrace[_S_TILDE][gi]
692
+ scale = jnp.sum(signal * s_tilde)
693
+ for path in self._etp_paths_of_group(group):
694
+ theta = etrace[_THETA_TILDE][(gi, path)]
695
+ _update_dict(dG_weights, path, _scale_tree(theta, scale))
696
+ return dG_weights
697
+
668
698
  def _solve_weight_gradients(
669
699
  self,
670
700
  running_index: int,
@@ -679,22 +709,14 @@ class RandomProjectionVjpAlgorithm(ETraceVjpAlgorithm):
679
709
  A scalar per group times a parameter-shaped array — the whole reason the
680
710
  parameter factor carries no hidden axis.
681
711
  """
682
- dG_weights: Dict[Path, Any] = {path: None for path in self.param_states}
683
-
684
- for group in self.graph.hidden_groups:
685
- gi = group.index
686
- signal = u.get_mantissa(dl_to_hidden_groups[gi])
687
- s_tilde = etrace_h2w_at_t[_S_TILDE][gi]
688
- scale = jnp.sum(signal * s_tilde)
689
- for path in self._etp_paths_of_group(group):
690
- theta = etrace_h2w_at_t[_THETA_TILDE][(gi, path)]
691
- _update_dict(dG_weights, path, _scale_tree(theta, scale))
712
+ dG_weights = self._contract_trace(etrace_h2w_at_t, dl_to_hidden_groups)
692
713
 
693
714
  # the non-etrace weight gradients (reverse-AD, in-window)
694
715
  for path, dg in dl_to_nonetws_at_t.items():
695
716
  _update_dict(dG_weights, path, dg)
696
717
 
697
- # the in-window direct term for etrace parameters (multi-step only)
718
+ # the in-window direct term for etrace parameters (multi-step), or
719
+ # the same-step output-bypass term (single-step, #185)
698
720
  if dl_to_etws_at_t is not None:
699
721
  for path, dg in dl_to_etws_at_t.items():
700
722
  _update_dict(dG_weights, path, dg, error_when_no_key=True)