braintrace 0.2.1__tar.gz → 0.2.3__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 (129) hide show
  1. {braintrace-0.2.1 → braintrace-0.2.3}/PKG-INFO +7 -4
  2. {braintrace-0.2.1 → braintrace-0.2.3}/README.md +1 -0
  3. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/__init__.py +10 -6
  4. braintrace-0.2.1/braintrace/api_contract_test.py → braintrace-0.2.3/braintrace/__init___test.py +65 -19
  5. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/__init__.py +2 -0
  6. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/_common.py +21 -19
  7. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/_common_test.py +2 -2
  8. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/base.py +64 -11
  9. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/base_test.py +25 -2
  10. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/d_rtrl.py +2 -0
  11. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/d_rtrl_test.py +182 -236
  12. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/e_prop.py +14 -14
  13. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/e_prop_test.py +32 -6
  14. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/graph_executor.py +24 -64
  15. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/graph_executor_test.py +1 -1
  16. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/io_dim_vjp.py +44 -24
  17. braintrace-0.2.3/braintrace/_algorithm/io_dim_vjp_test.py +423 -0
  18. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/oracle_test.py +29 -13
  19. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/ostl.py +15 -10
  20. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/ostl_test.py +44 -8
  21. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/osttp.py +18 -11
  22. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/osttp_test.py +33 -5
  23. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/otpe.py +23 -21
  24. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/otpe_test.py +30 -4
  25. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/ottt.py +21 -19
  26. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/ottt_test.py +30 -4
  27. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/param_dim_vjp.py +124 -201
  28. braintrace-0.2.3/braintrace/_algorithm/param_dim_vjp_test.py +459 -0
  29. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/pp_prop.py +4 -3
  30. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/pp_prop_test.py +31 -5
  31. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/approx_correctness_test.py +2 -2
  32. braintrace-0.2.3/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +327 -0
  33. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/cross_check_test.py +3 -3
  34. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/diagnostic_exploration_test.py +2 -5
  35. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/exact_correctness_test.py +2 -2
  36. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/public_api_test.py +1 -1
  37. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/scan_fusion_test.py +2 -2
  38. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm/tests}/transform_correctness_test.py +3 -3
  39. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/vjp_base.py +24 -10
  40. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/vjp_base_test.py +5 -5
  41. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/vjp_graph_executor.py +69 -64
  42. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/vjp_graph_executor_test.py +1 -1
  43. braintrace-0.2.3/braintrace/_compile.py +302 -0
  44. braintrace-0.2.3/braintrace/_compile_test.py +435 -0
  45. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/__init__.py +10 -7
  46. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/base.py +2 -2
  47. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/base_test.py +13 -9
  48. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/diagnostics_test.py +1 -1
  49. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/graph.py +15 -1
  50. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/graph_test.py +1 -1
  51. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hid_param_op.py +2 -1
  52. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hid_param_op_test.py +4 -4
  53. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hidden_group.py +272 -13
  54. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hidden_group_test.py +268 -4
  55. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hidden_pertubation.py +3 -2
  56. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/hidden_pertubation_test.py +2 -2
  57. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/model4test.py +1 -1
  58. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/module_info.py +1 -1
  59. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/module_info_test.py +1 -1
  60. braintrace-0.2.3/braintrace/_compiler/report.py +231 -0
  61. braintrace-0.2.3/braintrace/_compiler/report_test.py +53 -0
  62. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/scenario_catalog_test.py +46 -4
  63. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler/tests}/cell_relation_guardrail_test.py +1 -1
  64. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler/tests}/compiler_oracle_test.py +1 -1
  65. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler/tests}/compiler_property_test.py +2 -2
  66. braintrace-0.2.3/braintrace/_docs_examples_test.py +247 -0
  67. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_etrace_model_test.py +1 -1
  68. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_grad_exponential.py +7 -5
  69. braintrace-0.2.3/braintrace/_grad_exponential_test.py +82 -0
  70. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_input_data.py +10 -8
  71. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_legacy/_ops.py +6 -6
  72. braintrace-0.2.3/braintrace/_legacy/_ops_test.py +185 -0
  73. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_legacy/_params.py +7 -7
  74. braintrace-0.2.1/braintrace/_legacy/_legacy_test.py → braintrace-0.2.3/braintrace/_legacy/_params_test.py +6 -134
  75. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_misc.py +14 -12
  76. braintrace-0.2.3/braintrace/_misc_test.py +163 -0
  77. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/__init__.py +10 -2
  78. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/_primitive.py +34 -21
  79. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/_primitive_test.py +1 -1
  80. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/_registries.py +71 -1
  81. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/_registries_test.py +16 -4
  82. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/conv.py +107 -28
  83. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/conv_test.py +50 -5
  84. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/dense.py +274 -43
  85. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/dense_test.py +215 -8
  86. braintrace-0.2.3/braintrace/_op/elemwise.py +421 -0
  87. braintrace-0.2.3/braintrace/_op/elemwise_test.py +394 -0
  88. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/lora.py +112 -26
  89. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/lora_test.py +39 -4
  90. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/op_rule_oracle_test.py +8 -8
  91. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/sparse.py +123 -33
  92. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/sparse_test.py +185 -21
  93. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_state_managment.py +0 -1
  94. braintrace-0.2.3/braintrace/_state_managment_test.py +108 -0
  95. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_typing.py +7 -1
  96. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_version.py +1 -1
  97. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/__init__.py +8 -4
  98. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_conv.py +7 -2
  99. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_conv_test.py +1 -1
  100. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_linear.py +88 -24
  101. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_linear_test.py +344 -14
  102. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_readout.py +11 -9
  103. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_readout_test.py +1 -1
  104. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_rnn.py +60 -58
  105. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/nn/_rnn_test.py +2 -2
  106. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace.egg-info/PKG-INFO +7 -4
  107. braintrace-0.2.3/braintrace.egg-info/SOURCES.txt +121 -0
  108. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace.egg-info/requires.txt +5 -3
  109. {braintrace-0.2.1 → braintrace-0.2.3}/pyproject.toml +92 -7
  110. braintrace-0.2.1/braintrace/_compile.py +0 -153
  111. braintrace-0.2.1/braintrace/_compile_test.py +0 -125
  112. braintrace-0.2.1/braintrace/_etrace_op/elemwise.py +0 -239
  113. braintrace-0.2.1/braintrace/_etrace_op/elemwise_test.py +0 -244
  114. braintrace-0.2.1/braintrace/legacy_deprecation_test.py +0 -53
  115. braintrace-0.2.1/braintrace.egg-info/SOURCES.txt +0 -112
  116. {braintrace-0.2.1 → braintrace-0.2.3}/LICENSE +0 -0
  117. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/oracle.py +0 -0
  118. {braintrace-0.2.1/braintrace/_etrace_algorithms → braintrace-0.2.3/braintrace/_algorithm}/oracle_models.py +0 -0
  119. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_compatible_imports.py +0 -0
  120. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_compatible_imports_test.py +0 -0
  121. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/diagnostics.py +0 -0
  122. {braintrace-0.2.1/braintrace/_etrace_compiler → braintrace-0.2.3/braintrace/_compiler}/scenario_catalog.py +0 -0
  123. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_input_data_test.py +0 -0
  124. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/_legacy/__init__.py +0 -0
  125. {braintrace-0.2.1/braintrace/_etrace_op → braintrace-0.2.3/braintrace/_op}/op_rule_oracle.py +0 -0
  126. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace/py.typed +0 -0
  127. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace.egg-info/dependency_links.txt +0 -0
  128. {braintrace-0.2.1 → braintrace-0.2.3}/braintrace.egg-info/top_level.txt +0 -0
  129. {braintrace-0.2.1 → braintrace-0.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrace
3
- Version: 0.2.1
3
+ Version: 0.2.3
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
@@ -26,10 +26,10 @@ Classifier: Topic :: Software Development :: Libraries
26
26
  Requires-Python: >=3.11
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
- Requires-Dist: brainstate>=0.2.2
29
+ Requires-Dist: brainstate>=0.5.2
30
30
  Requires-Dist: brainunit
31
- Requires-Dist: brainstate
32
- Requires-Dist: brainpy-state
31
+ Requires-Dist: brainevent
32
+ Requires-Dist: brainpy.state
33
33
  Requires-Dist: braintools
34
34
  Provides-Extra: cpu
35
35
  Requires-Dist: jax[cpu]; extra == "cpu"
@@ -41,10 +41,12 @@ Provides-Extra: tpu
41
41
  Requires-Dist: jax[tpu]; extra == "tpu"
42
42
  Provides-Extra: testing
43
43
  Requires-Dist: pytest; extra == "testing"
44
+ Requires-Dist: pytest-cov; extra == "testing"
44
45
  Requires-Dist: jax[cpu]; extra == "testing"
45
46
  Requires-Dist: hypothesis; extra == "testing"
46
47
  Provides-Extra: dev
47
48
  Requires-Dist: pytest; extra == "dev"
49
+ Requires-Dist: pytest-cov; extra == "dev"
48
50
  Requires-Dist: jax[cpu]; extra == "dev"
49
51
  Requires-Dist: hypothesis; extra == "dev"
50
52
  Requires-Dist: mypy>=1.8; extra == "dev"
@@ -64,6 +66,7 @@ Dynamic: license-file
64
66
  <a href="https://brainx.chaobrain.com/braintrace/"><img alt="Documentation" src="https://readthedocs.org/projects/braintrace/badge/?version=latest"></a>
65
67
  <a href="https://badge.fury.io/py/braintrace"><img alt="PyPI version" src="https://badge.fury.io/py/braintrace.svg"></a>
66
68
  <a href="https://github.com/chaobrain/braintrace/actions/workflows/CI.yml"><img alt="Continuous Integration" src="https://github.com/chaobrain/braintrace/actions/workflows/CI.yml/badge.svg"></a>
69
+ <a href="https://codecov.io/gh/chaobrain/braintrace"><img alt="Code Coverage" src="https://codecov.io/gh/chaobrain/braintrace/branch/main/graph/badge.svg"></a>
67
70
  </p>
68
71
 
69
72
  [``braintrace``](https://github.com/chaobrain/braintrace) provides online learning algorithms for biological neural networks.
@@ -11,6 +11,7 @@
11
11
  <a href="https://brainx.chaobrain.com/braintrace/"><img alt="Documentation" src="https://readthedocs.org/projects/braintrace/badge/?version=latest"></a>
12
12
  <a href="https://badge.fury.io/py/braintrace"><img alt="PyPI version" src="https://badge.fury.io/py/braintrace.svg"></a>
13
13
  <a href="https://github.com/chaobrain/braintrace/actions/workflows/CI.yml"><img alt="Continuous Integration" src="https://github.com/chaobrain/braintrace/actions/workflows/CI.yml/badge.svg"></a>
14
+ <a href="https://codecov.io/gh/chaobrain/braintrace"><img alt="Code Coverage" src="https://codecov.io/gh/chaobrain/braintrace/branch/main/graph/badge.svg"></a>
14
15
  </p>
15
16
 
16
17
  [``braintrace``](https://github.com/chaobrain/braintrace) provides online learning algorithms for biological neural networks.
@@ -66,11 +66,13 @@ Examples
66
66
  """
67
67
 
68
68
 
69
- from typing import TYPE_CHECKING
69
+ from __future__ import annotations
70
+
71
+ from typing import TYPE_CHECKING, Any
70
72
 
71
73
  from . import nn
72
74
  from ._compile import compile
73
- from ._etrace_algorithms import (
75
+ from ._algorithm import (
74
76
  ETraceAlgorithm,
75
77
  EligibilityTrace,
76
78
  ETraceGraphExecutor,
@@ -91,7 +93,7 @@ from ._etrace_algorithms import (
91
93
  KappaFilter,
92
94
  PresynapticTrace,
93
95
  )
94
- from ._etrace_compiler import (
96
+ from ._compiler import (
95
97
  ETraceGraph,
96
98
  compile_etrace_graph,
97
99
  HiddenParamOpRelation,
@@ -106,10 +108,11 @@ from ._etrace_compiler import (
106
108
  ModuleInfo,
107
109
  extract_module_info,
108
110
  CompilationRecord,
111
+ CompilationReport,
109
112
  DiagnosticKind,
110
113
  DiagnosticLevel,
111
114
  )
112
- from ._etrace_op import (
115
+ from ._op import (
113
116
  ETPPrimitive,
114
117
  matmul,
115
118
  element_wise,
@@ -198,6 +201,7 @@ __all__ = [
198
201
 
199
202
  # compiler diagnostics
200
203
  'CompilationRecord',
204
+ 'CompilationReport',
201
205
  'DiagnosticKind',
202
206
  'DiagnosticLevel',
203
207
 
@@ -242,7 +246,7 @@ _DEPRECATED_LEGACY = {
242
246
  }
243
247
 
244
248
 
245
- def __getattr__(name):
249
+ def __getattr__(name: str) -> Any:
246
250
  if name in _DEPRECATED_LEGACY:
247
251
  import warnings
248
252
  warnings.warn(
@@ -256,5 +260,5 @@ def __getattr__(name):
256
260
  raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
257
261
 
258
262
 
259
- def __dir__():
263
+ def __dir__() -> list[str]:
260
264
  return sorted(list(__all__) + list(_DEPRECATED_LEGACY))
@@ -13,10 +13,18 @@
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
15
 
16
- """L0 public-API contract: every ``braintrace.__all__`` symbol resolves to a
17
- usable object; public algorithms run a minimal end-to-end step; documented error
18
- paths fire on their real triggers; legacy/nn deprecations warn. Also pins the
19
- implementation facts that drifted from CLAUDE.md (F-17)."""
16
+ """Tests for the ``braintrace`` package root (``braintrace/__init__.py``).
17
+
18
+ Two concerns, both owned by the package root:
19
+
20
+ * **Public-API contract** — every ``braintrace.__all__`` symbol resolves to a
21
+ usable object; public algorithms run a minimal end-to-end step; documented
22
+ error paths fire on their real triggers; legacy/nn deprecations warn; and the
23
+ implementation facts that drifted from CLAUDE.md (F-17) stay pinned.
24
+ * **Legacy deprecation forwarding** — the v0.1.x shims served lazily by
25
+ ``__getattr__`` warn on access, stay out of ``__all__``, and appear in
26
+ ``__dir__``.
27
+ """
20
28
 
21
29
  import brainstate
22
30
  import jax
@@ -24,8 +32,13 @@ import jax.numpy as jnp
24
32
  import pytest
25
33
 
26
34
  import braintrace
27
- from braintrace._etrace_algorithms.oracle_models import tanh_rnn
35
+ import braintrace._legacy as legacy
36
+ from braintrace._algorithm.oracle_models import tanh_rnn
37
+
28
38
 
39
+ # ===========================================================================
40
+ # Public-API contract
41
+ # ===========================================================================
29
42
 
30
43
  # --- Task 1: every __all__ symbol resolves to a non-None object --------------
31
44
 
@@ -130,18 +143,12 @@ def test_dynamic_weight_assignment_raises_not_supported():
130
143
 
131
144
  # --- Task 5: weight inside control flow raises NotImplementedError -----------
132
145
 
133
- @pytest.mark.xfail(
134
- strict=True,
135
- reason="F-SCAN-WEIGHT: the weight-in-control-flow guard intends to raise "
136
- "NotImplementedError, but its error-message construction at "
137
- "base.py:133 indexes invar_to_hidden_path with the *weight* var "
138
- "(absent from that hidden-path map), so a KeyError escapes instead. "
139
- "The guard fires, but the wrong exception type surfaces.",
140
- )
141
146
  def test_weight_used_inside_scan_raises_not_implemented():
142
- """check_unsupported_op should raise NotImplementedError when a weight var is
143
- used within a control-flow op. Today it raises KeyError while building the
144
- message (F-SCAN-WEIGHT) — this test pins the intended contract via xfail."""
147
+ """check_unsupported_op raises NotImplementedError when a weight var is used
148
+ within a control-flow op. Previously (F-SCAN-WEIGHT) the message construction
149
+ indexed the hidden-path map with the weight var and a KeyError escaped instead;
150
+ the message now renders the weight variable directly, so the intended
151
+ NotImplementedError surfaces."""
145
152
 
146
153
  class ScanModel(brainstate.nn.Module):
147
154
  def __init__(self):
@@ -193,8 +200,8 @@ def test_nn_unknown_name_raises_attribute_error():
193
200
  # --- Task 7: lock the drifted implementation facts (F-17) --------------------
194
201
 
195
202
  def test_ostl_is_two_classes_not_a_factory():
196
- from braintrace._etrace_algorithms.param_dim_vjp import ParamDimVjpAlgorithm
197
- from braintrace._etrace_algorithms.io_dim_vjp import IODimVjpAlgorithm
203
+ from braintrace._algorithm.param_dim_vjp import ParamDimVjpAlgorithm
204
+ from braintrace._algorithm.io_dim_vjp import IODimVjpAlgorithm
198
205
  assert isinstance(braintrace.OSTLRecurrent, type)
199
206
  assert isinstance(braintrace.OSTLFeedforward, type)
200
207
  assert issubclass(braintrace.OSTLRecurrent, ParamDimVjpAlgorithm)
@@ -202,7 +209,7 @@ def test_ostl_is_two_classes_not_a_factory():
202
209
 
203
210
 
204
211
  def test_iodim_lives_in_io_dim_vjp_module():
205
- from braintrace._etrace_algorithms.io_dim_vjp import IODimVjpAlgorithm
212
+ from braintrace._algorithm.io_dim_vjp import IODimVjpAlgorithm
206
213
  assert braintrace.IODimVjpAlgorithm is IODimVjpAlgorithm
207
214
  assert braintrace.pp_prop is braintrace.ES_D_RTRL # aliases
208
215
 
@@ -212,3 +219,42 @@ def test_expected_rnn_cells_exist():
212
219
  for cell in ('ValinaRNNCell', 'GRUCell', 'MGUCell', 'LSTMCell', 'URLSTMCell',
213
220
  'MinimalRNNCell', 'MiniGRU', 'MiniLSTM', 'LRUCell'):
214
221
  assert hasattr(nn, cell), f"missing cell: {cell}"
222
+
223
+
224
+ # ===========================================================================
225
+ # Legacy v0.1.x deprecation forwarding
226
+ # ===========================================================================
227
+
228
+ _LEGACY_NAMES = [
229
+ 'ETraceOp', 'MatMulOp', 'ElemWiseOp', 'ConvOp', 'SpMatMulOp', 'LoraOp',
230
+ 'ETraceParam', 'ElemWiseParam', 'NonTempParam',
231
+ 'FakeETraceParam', 'FakeElemWiseParam',
232
+ ]
233
+
234
+
235
+ @pytest.mark.parametrize('name', _LEGACY_NAMES)
236
+ def test_legacy_access_warns_and_returns_class(name):
237
+ with pytest.warns(DeprecationWarning):
238
+ obj = getattr(braintrace, name)
239
+ assert obj is getattr(legacy, name)
240
+
241
+
242
+ @pytest.mark.parametrize('name', _LEGACY_NAMES)
243
+ def test_legacy_names_not_in_all(name):
244
+ assert name not in braintrace.__all__
245
+
246
+
247
+ def test_from_import_warns():
248
+ with pytest.warns(DeprecationWarning):
249
+ from braintrace import MatMulOp # noqa: F401
250
+
251
+
252
+ def test_unknown_attribute_raises_attribute_error():
253
+ with pytest.raises(AttributeError):
254
+ _ = braintrace.ThisNameDoesNotExist
255
+
256
+
257
+ def test_legacy_names_in_dir():
258
+ d = dir(braintrace)
259
+ for name in _LEGACY_NAMES:
260
+ assert name in d
@@ -21,6 +21,8 @@ and paper-faithful SNN algorithms (EProp, OSTLRecurrent, OSTLFeedforward, OTPE,
21
21
  OTTT, OSTTP).
22
22
  """
23
23
 
24
+ from __future__ import annotations
25
+
24
26
  from ._common import FixedRandomFeedback, KappaFilter, PresynapticTrace
25
27
  from .base import ETraceAlgorithm, EligibilityTrace
26
28
  from .d_rtrl import D_RTRL
@@ -15,15 +15,17 @@
15
15
 
16
16
  """Shared helpers for the SNN online-learning algorithms."""
17
17
 
18
+ from __future__ import annotations
19
+
18
20
  from functools import partial
19
21
  from typing import Any, Dict, Optional
20
22
 
21
23
  import brainstate
22
24
  import jax
23
25
  import jax.numpy as jnp
24
- import saiunit as u
26
+ import brainunit as u
25
27
 
26
- from braintrace._typing import PyTree
28
+ from braintrace._typing import ArrayLike, PyTree
27
29
 
28
30
  __all__ = [
29
31
  'PresynapticTrace',
@@ -42,12 +44,12 @@ class _ZeroResetState(brainstate.ShortTermState):
42
44
  for ``batch_size`` (or prepended for scalar-shaped states).
43
45
  """
44
46
 
45
- def __init__(self, init_value):
47
+ def __init__(self, init_value: Any) -> None:
46
48
  super().__init__(init_value)
47
49
  self._init_shape = jnp.shape(init_value)
48
50
  self._init_dtype = init_value.dtype
49
51
 
50
- def reset_state(self, batch_size: Optional[int] = None, **kwargs):
52
+ def reset_state(self, batch_size: Optional[int] = None, **kwargs: Any) -> None:
51
53
  """Re-zero the state at its original shape.
52
54
 
53
55
  Parameters
@@ -105,13 +107,13 @@ class PresynapticTrace(_ZeroResetState):
105
107
 
106
108
  __module__ = 'braintrace'
107
109
 
108
- def __init__(self, init_value, leak: float):
110
+ def __init__(self, init_value: Any, leak: float) -> None:
109
111
  super().__init__(init_value)
110
112
  if not (0.0 < leak < 1.0):
111
113
  raise ValueError(f'leak must be in (0, 1); got {leak}')
112
114
  self.leak = float(leak)
113
115
 
114
- def update(self, x):
116
+ def update(self, x: ArrayLike) -> Any:
115
117
  r"""Apply one accumulation step :math:`\hat{a} \leftarrow \lambda \cdot \hat{a} + x`.
116
118
 
117
119
  Parameters
@@ -163,13 +165,13 @@ class KappaFilter(_ZeroResetState):
163
165
 
164
166
  __module__ = 'braintrace'
165
167
 
166
- def __init__(self, init_value, kappa: float):
168
+ def __init__(self, init_value: Any, kappa: float) -> None:
167
169
  super().__init__(init_value)
168
170
  if not (0.0 <= kappa < 1.0):
169
171
  raise ValueError(f'kappa must be in [0, 1); got {kappa}')
170
172
  self.kappa = float(kappa)
171
173
 
172
- def update(self, x):
174
+ def update(self, x: ArrayLike) -> Any:
173
175
  r"""Apply one low-pass step :math:`x_{\mathrm{filt}} \leftarrow (1-\kappa) x + \kappa\, x_{\mathrm{filt}}`.
174
176
 
175
177
  Parameters
@@ -230,14 +232,14 @@ class FixedRandomFeedback:
230
232
 
231
233
  __module__ = 'braintrace'
232
234
 
233
- def __init__(self, n_target: int, n_layer: int, key, init_scale: float = 0.1):
235
+ def __init__(self, n_target: int, n_layer: int, key: Any, init_scale: float = 0.1) -> None:
234
236
  self.B = jax.lax.stop_gradient(
235
237
  init_scale * jax.random.normal(key, (n_target, n_layer))
236
238
  )
237
239
  self.n_target = int(n_target)
238
240
  self.n_layer = int(n_layer)
239
241
 
240
- def project(self, y_target):
242
+ def project(self, y_target: Any) -> Any:
241
243
  """Project the target onto the frozen feedback matrix.
242
244
 
243
245
  Parameters
@@ -267,7 +269,7 @@ def extract_y_target(args: tuple, *, index: int = -1) -> Optional[jax.Array]:
267
269
  def _reset_state_in_a_dict(
268
270
  state_dict: Dict[Any, brainstate.State],
269
271
  batch_size: Optional[int],
270
- ):
272
+ ) -> None:
271
273
  """
272
274
  Reset the values in a dictionary of states to zero.
273
275
 
@@ -293,7 +295,7 @@ def _reset_state_in_a_dict(
293
295
  def _zeros_like_batch_or_not(
294
296
  batch_size: Optional[int],
295
297
  x: jax.Array
296
- ):
298
+ ) -> Any:
297
299
  """
298
300
  Create a zeros array with the same shape and type as the input array,
299
301
  optionally including a batch dimension.
@@ -325,7 +327,7 @@ def _batched_zeros_like(
325
327
  batch_size: Optional[int],
326
328
  num_state: int, # the number of hidden states
327
329
  x: jax.Array # the input array
328
- ):
330
+ ) -> Any:
329
331
  """
330
332
  Create a batched zeros array with the same shape as the input array,
331
333
  extended by the number of hidden states.
@@ -354,7 +356,7 @@ def _batched_zeros_like(
354
356
  return u.math.zeros((batch_size, *x.shape, num_state), x.dtype)
355
357
 
356
358
 
357
- def _sum_dim(xs: jax.Array, axis: int = -1):
359
+ def _sum_dim(xs: jax.Array, axis: int = -1) -> Any:
358
360
  """
359
361
  Sums the elements along the last dimension of each array in a PyTree.
360
362
 
@@ -373,7 +375,7 @@ def _sum_dim(xs: jax.Array, axis: int = -1):
373
375
  return jax.tree.map(lambda x: u.math.sum(x, axis=axis), xs)
374
376
 
375
377
 
376
- def _unit_safe_add(a, b):
378
+ def _unit_safe_add(a: Any, b: Any) -> Any:
377
379
  """Add two leaves, stripping units only when one side has units and the other does not.
378
380
 
379
381
  Gradient contributions for the same weight may come from paths that
@@ -390,7 +392,7 @@ def _unit_safe_add(a, b):
390
392
  return u.math.add(a, b)
391
393
 
392
394
 
393
- def _extract_leaf(pytree_val: PyTree, leaf_idx: int):
395
+ def _extract_leaf(pytree_val: PyTree, leaf_idx: int) -> Any:
394
396
  """Return the leaf at ``leaf_idx`` in ``jax.tree.leaves(pytree_val)``.
395
397
 
396
398
  Bare arrays (treedef with a single leaf) return the array unchanged.
@@ -409,7 +411,7 @@ def _extract_leaf(pytree_val: PyTree, leaf_idx: int):
409
411
  def _wrap_leaves_as_pytree(
410
412
  reference_pytree: PyTree,
411
413
  leaf_grads: Dict[int, jax.Array],
412
- ):
414
+ ) -> Any:
413
415
  """Build a pytree matching ``reference_pytree`` with ``leaf_grads``
414
416
  inserted at the given leaf indices; any other leaf is zero-filled.
415
417
 
@@ -441,7 +443,7 @@ def _wrap_leaves_as_pytree(
441
443
 
442
444
 
443
445
  def _route_grads_by_path(
444
- relation,
446
+ relation: Any,
445
447
  per_key_grads: Dict[str, jax.Array],
446
448
  weight_vals: Dict[Any, PyTree],
447
449
  target_dict: Dict[Any, PyTree],
@@ -478,7 +480,7 @@ def _update_dict(
478
480
  key: Any,
479
481
  value: PyTree,
480
482
  error_when_no_key: Optional[bool] = False
481
- ):
483
+ ) -> None:
482
484
  """Update the dictionary.
483
485
 
484
486
  If the key exists, then add the value to the existing value.
@@ -20,9 +20,9 @@ import jax
20
20
  import jax.numpy as jnp
21
21
  import numpy.testing as npt
22
22
  import pytest
23
- import saiunit as u
23
+ import brainunit as u
24
24
 
25
- from braintrace._etrace_algorithms._common import (
25
+ from braintrace._algorithm._common import (
26
26
  FixedRandomFeedback,
27
27
  KappaFilter,
28
28
  PresynapticTrace,
@@ -19,11 +19,13 @@
19
19
 
20
20
  # -*- coding: utf-8 -*-
21
21
 
22
+ from __future__ import annotations
23
+
22
24
  from typing import Dict, Any, Optional
23
25
 
24
26
  import brainstate
25
27
 
26
- from braintrace._etrace_compiler import ETraceGraph
28
+ from braintrace._compiler import ETraceGraph, CompilationReport
27
29
  from .graph_executor import ETraceGraphExecutor
28
30
  from .._typing import Path
29
31
 
@@ -89,7 +91,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
89
91
  model: brainstate.nn.Module,
90
92
  graph_executor: ETraceGraphExecutor,
91
93
  name: Optional[str] = None,
92
- ):
94
+ ) -> None:
93
95
  super().__init__(name=name) # type: ignore[call-arg] # brainstate hides Module.__init__ from type checkers
94
96
 
95
97
  # the model
@@ -131,6 +133,28 @@ class ETraceAlgorithm(brainstate.nn.Module):
131
133
  """
132
134
  return self.graph_executor.graph
133
135
 
136
+ @property
137
+ def report(self) -> CompilationReport:
138
+ """Structured, read-only report of the compiled eligibility-trace graph.
139
+
140
+ Returns
141
+ -------
142
+ CompilationReport
143
+ A view over :attr:`graph`.
144
+
145
+ Raises
146
+ ------
147
+ RuntimeError
148
+ If accessed before the graph has been compiled.
149
+ """
150
+ if not self.is_compiled:
151
+ raise RuntimeError(
152
+ 'The eligibility-trace graph is not compiled yet. Build the '
153
+ 'learner with braintrace.compile(...) (or call .compile_graph(...)) '
154
+ 'before accessing .report.'
155
+ )
156
+ return CompilationReport(self.graph)
157
+
134
158
  @property
135
159
  def executor(self) -> ETraceGraphExecutor:
136
160
  """
@@ -188,7 +212,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
188
212
  assert self._other_states is not None
189
213
  return self._other_states
190
214
 
191
- def _split_state(self):
215
+ def _split_state(self) -> None:
192
216
  # --- the state separation --- #
193
217
  #
194
218
  # [NOTE]
@@ -206,7 +230,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
206
230
  self._other_states
207
231
  ) = self.graph.module_info.retrieved_model_states.split(brainstate.ParamState, brainstate.HiddenState, ...)
208
232
 
209
- def compile_graph(self, *args) -> None:
233
+ def compile_graph(self, *args: Any) -> None:
210
234
  r"""
211
235
  Compile the eligibility trace graph of the relationship between etrace weights, states and operators.
212
236
 
@@ -222,6 +246,18 @@ class ETraceAlgorithm(brainstate.nn.Module):
222
246
  The input arguments.
223
247
  """
224
248
 
249
+ # ``vmap_new_states`` / ``vmap2_new_states`` run an eager *discovery
250
+ # probe* that executes the surrounding ``init`` (including this call)
251
+ # once against throwaway, un-batched states before the real mapped
252
+ # pass. Compiling there would bind the executor to those probe states
253
+ # (which are discarded and left untagged), so the subsequent
254
+ # ``brainstate.nn.Vmap(..., vmap_states='new')`` would not cover them
255
+ # and writing a batched value raises ``BatchAxisError``. Defer to the
256
+ # real mapped pass, which creates the 'new'-tagged batched states.
257
+ _in_probe = getattr(brainstate.transform, 'in_new_state_probe', None)
258
+ if _in_probe is not None and _in_probe():
259
+ return
260
+
225
261
  if not self.is_compiled:
226
262
  # --- invalidate cached state splits --- #
227
263
  self._param_states = None
@@ -261,13 +297,30 @@ class ETraceAlgorithm(brainstate.nn.Module):
261
297
  """
262
298
  return self.graph_executor.state_id_to_path
263
299
 
264
- def show_graph(self) -> None:
265
- """
266
- Show the etrace graph.
300
+ def show_graph(
301
+ self,
302
+ verbose: bool = True,
303
+ return_msg: bool = False,
304
+ ) -> None | str:
305
+ """Display the eligibility-trace graph.
306
+
307
+ Delegates to :meth:`ETraceGraphExecutor.show_graph`.
308
+
309
+ Parameters
310
+ ----------
311
+ verbose : bool, optional
312
+ If True (default), print the summary to stdout.
313
+ return_msg : bool, optional
314
+ If True, return the summary string. Default False.
315
+
316
+ Returns
317
+ -------
318
+ None or str
319
+ The summary string if ``return_msg`` is True, else None.
267
320
  """
268
- self.graph_executor.show_graph()
321
+ return self.graph_executor.show_graph(verbose=verbose, return_msg=return_msg)
269
322
 
270
- def __call__(self, *args) -> Any:
323
+ def __call__(self, *args: Any) -> Any:
271
324
  """
272
325
  Update the model and the eligibility trace states.
273
326
 
@@ -283,7 +336,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
283
336
  """
284
337
  return self.update(*args)
285
338
 
286
- def update(self, *args) -> Any:
339
+ def update(self, *args: Any) -> Any:
287
340
  """
288
341
  Update the model and the eligibility trace states.
289
342
 
@@ -304,7 +357,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
304
357
  """
305
358
  raise NotImplementedError
306
359
 
307
- def init_etrace_state(self, *args, **kwargs) -> None:
360
+ def init_etrace_state(self, *args: Any, **kwargs: Any) -> None:
308
361
  """
309
362
  Initialize the eligibility trace states of the etrace algorithm.
310
363
 
@@ -21,8 +21,8 @@ import brainstate
21
21
  import jax.numpy as jnp
22
22
 
23
23
  import braintrace
24
- from braintrace._etrace_algorithms import ETraceAlgorithm, EligibilityTrace
25
- from braintrace._etrace_algorithms.graph_executor import ETraceGraphExecutor
24
+ from braintrace._algorithm import ETraceAlgorithm, EligibilityTrace
25
+ from braintrace._algorithm.graph_executor import ETraceGraphExecutor
26
26
 
27
27
 
28
28
  # ---------------------------------------------------------------------------
@@ -580,5 +580,28 @@ class TestETraceAlgorithmTwoLayerIntegration(unittest.TestCase):
580
580
  self.assertGreaterEqual(len(algo.hidden_states), 2)
581
581
 
582
582
 
583
+ def test_report_before_compile_raises():
584
+ import brainstate, pytest, braintrace
585
+ from braintrace._algorithm.oracle_models import tanh_rnn
586
+ model = tanh_rnn(n_in=3, n_rec=4, seed=0).factory()
587
+ brainstate.nn.init_all_states(model, batch_size=1)
588
+ algo = braintrace.D_RTRL(model)
589
+ with pytest.raises(RuntimeError):
590
+ _ = algo.report
591
+
592
+
593
+ def test_report_after_compile_matches_show_graph():
594
+ import brainstate, jax.numpy as jnp, braintrace
595
+ from braintrace import CompilationReport
596
+ from braintrace._algorithm.oracle_models import tanh_rnn
597
+ model = tanh_rnn(n_in=3, n_rec=4, seed=0).factory()
598
+ brainstate.nn.init_all_states(model, batch_size=1)
599
+ algo = braintrace.D_RTRL(model)
600
+ algo.compile_graph(jnp.ones((3,), 'float32'))
601
+ assert isinstance(algo.report, CompilationReport)
602
+ # show_graph(return_msg=True) is exactly report.to_str(1)
603
+ assert algo.show_graph(verbose=False, return_msg=True) == algo.report.to_str(1)
604
+
605
+
583
606
  if __name__ == '__main__':
584
607
  unittest.main()
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
15
 
16
+ from __future__ import annotations
17
+
16
18
  from .param_dim_vjp import ParamDimVjpAlgorithm
17
19
 
18
20
  __all__ = [