Skip to content

Commit 9984387

Browse files
authored
fix: IntegratorRunner dict-monitor regression + stale post-audit test updates (#854)
fix: resolve IntegratorRunner dict-monitor regression; update stale bug-pinning tests Source fix ---------- `IntegratorRunner` dict monitors regressed: after `_format_dict_monitors` each value is a ``(var_name_or_Variable, index)`` tuple, but the resolution code tested ``isinstance(i, str)`` on that tuple (always False), so a string variable-name was passed through to the base runner, which then looked it up as an attribute of ``self.target`` and failed. Integrator state variables live in ``self.variables``, so resolve string names there before handing the base runner an already-resolved ``(Variable, index)`` pair. Test updates ------------ Several post-audit tests pinned now-fixed behaviour. Re-pointed them at the corrected contract: - nvar_coverage_test: order < 2 now raises ValueError (not AssertionError). - dnn_toolbox_fixes_test: Adan now constructs and updates cleanly. - boost_misc_test: offline ElasticNet / Logistic regression now fit & predict. - train_analysis_glue_fixes_test: logistic regression now fits without error.
1 parent 3e29c8e commit 9984387

5 files changed

Lines changed: 45 additions & 35 deletions

File tree

‎brainpy/boost_misc_test.py‎

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,13 @@ def test_offline_lasso_regression_and_predict():
110110
assert pred.shape[0] == x.shape[0]
111111

112112

113-
def test_offline_elastic_net_regression_fit_and_broken_predict():
114-
"""ElasticNet ``call`` (fit) runs through the gradient-descent solver.
115-
116-
NOTE: ``ElasticNetRegression.predict`` is inconsistent with ``call``: ``call``
117-
builds features via ``polynomial_features(inputs, degree=...)`` (default
118-
``add_bias=True`` -> bias column added), while ``predict`` passes
119-
``add_bias=self.add_bias`` (default ``False``). The feature counts therefore
120-
differ (7 vs 6) and ``predict`` raises a shape ``TypeError``. We exercise the
121-
fit path and pin the broken predict.
113+
def test_offline_elastic_net_regression_fit_and_predict():
114+
"""ElasticNet ``call`` (fit) and ``predict`` are now consistent.
115+
116+
Fixed in audit 2026-06-19: ``call`` previously built features with the default
117+
``add_bias=True`` while ``predict`` used ``add_bias=self.add_bias`` (default
118+
``False``), so the feature counts differed and ``predict`` crashed. ``call``
119+
now passes ``add_bias=self.add_bias``, so fit and predict agree.
122120
"""
123121
from brainpy.algorithms.offline import ElasticNetRegression
124122

@@ -127,8 +125,8 @@ def test_offline_elastic_net_regression_fit_and_broken_predict():
127125
model = ElasticNetRegression(alpha=0.05, degree=2, l1_ratio=0.5, max_iter=30)
128126
w = model(y, x)
129127
assert not bool(jnp.isnan(w).any())
130-
with pytest.raises((TypeError, ValueError)):
131-
model.predict(w, x)
128+
pred = model.predict(w, x)
129+
assert pred.shape[0] == x.shape[0]
132130

133131

134132
def test_offline_polynomial_regression_and_predict():
@@ -158,19 +156,22 @@ def test_offline_polynomial_ridge_regression_and_predict():
158156
assert pred.shape[0] == x.shape[0]
159157

160158

161-
def test_offline_logistic_regression_known_indexerror():
162-
"""NOTE: dead/broken path. ``LogisticRegression.call`` flattens ``targets``
163-
to 1-D and then indexes ``targets.shape[1]``, raising ``IndexError``. Its
164-
body (offline.py lines 391-415) is unreachable; we pin the broken behavior.
159+
def test_offline_logistic_regression_fit():
160+
"""``LogisticRegression.call`` now fits instead of crashing.
161+
162+
Fixed in audit 2026-06-19: ``call`` previously flattened ``targets`` to 1-D
163+
and then indexed ``targets.shape[1]`` (``IndexError``); it now initialises a
164+
1-D parameter vector and runs the gradient-descent solver.
165165
"""
166166
from brainpy.algorithms.offline import LogisticRegression
167167

168168
bm.random.seed(6)
169169
rng = np.random.RandomState(6)
170170
x = jnp.asarray(rng.randn(20, 2).astype('float32'))
171171
y = jnp.asarray((np.asarray(x)[:, :1] > 0).astype('float32'))
172-
with pytest.raises(IndexError):
173-
LogisticRegression(max_iter=50)(y, x)
172+
w = LogisticRegression(max_iter=50)(y, x)
173+
assert w is not None
174+
assert not bool(jnp.isnan(jnp.asarray(w)).any())
174175

175176

176177
def test_offline_registry_helpers_and_base_repr():

‎brainpy/dnn/dnn_toolbox_fixes_test.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -406,15 +406,15 @@ def test_adamw_invalid_hyperparams():
406406
bp.optim.AdamW(lr=0.01, weight_decay=-1.0)
407407

408408

409-
def test_adan_constructs_update_is_known_bug():
410-
"""Adan constructs, but REMAINING BUG: ``Adan.update`` passes a tuple operand to
411-
``lax.cond`` while the branch lambdas expect two args (optimizer.py:818),
412-
raising TypeError. Pinned so the construct path is covered + the bug documented."""
409+
def test_adan_constructs_and_updates():
410+
"""Adan constructs AND ``update`` runs (fixed in audit 2026-06-19: the
411+
``lax.cond`` first-step guard previously splatted its tuple operand, raising
412+
``TypeError`` on every call). The update now applies and leaves the var finite."""
413413
w = _train_var()
414414
adan = bp.optim.Adan(lr=0.01, train_vars={'w': w})
415415
repr(adan)
416-
with pytest.raises(TypeError):
417-
adan.update({'w': bm.ones((2, 3)) * 0.1})
416+
adan.update({'w': bm.ones((2, 3)) * 0.1})
417+
assert not bool(jnp.isnan(bm.as_jax(w)).any())
418418

419419

420420
def test_adan_invalid_betas():

‎brainpy/dyn/rates/nvar_coverage_test.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def test_multi_order_stride_constant(self):
6565

6666
class TestNVARErrors(parameterized.TestCase):
6767
def test_order_below_two_raises(self):
68-
with self.assertRaises(AssertionError):
68+
# ``NVAR.__init__`` validates ``order`` via ``check.is_integer(min_bound=2)``,
69+
# which now (correctly) raises a ``ValueError`` for ``order < 2``.
70+
with self.assertRaises(ValueError):
6971
NVAR(num_in=3, delay=2, order=1)
7072

7173
def test_non_bool_constant_raises(self):

‎brainpy/integrators/runner.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,15 @@ def __init__(
179179
monitors = {k: (self.variables[k], i) for k, i in monitors}
180180
elif isinstance(monitors, dict):
181181
monitors = self._format_dict_monitors(monitors)
182-
monitors = {k: ((self.variables[i], i) if isinstance(i, str) else i) for k, i in monitors.items()}
182+
# ``_format_dict_monitors`` yields ``{user_key: (var_name_or_Variable, index)}``.
183+
# The integrator's state variables live in ``self.variables`` (they are NOT
184+
# attributes of ``self.target``), so a string variable-name must be resolved
185+
# here against ``self.variables``; the base runner then receives an already
186+
# resolved ``(Variable, index)`` pair and passes it through unchanged.
187+
monitors = {k: ((self.variables[v[0]], v[1])
188+
if (isinstance(v, (tuple, list)) and isinstance(v[0], str))
189+
else v)
190+
for k, v in monitors.items()}
183191
else:
184192
raise ValueError
185193

‎brainpy/train_analysis_glue_fixes_test.py‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,22 +239,21 @@ def test_ridge_intercept_not_over_penalized():
239239
assert abs(slope) < abs(intercept), f"slope not shrunk: slope={slope}"
240240

241241

242-
def test_logistic_regression_remaining_bug():
243-
"""Document a remaining (un-audited) bug in ``LogisticRegression.call``.
244-
245-
``call`` flattens ``targets`` to 1-D and then indexes ``targets.shape[1]``,
246-
which raises ``IndexError: tuple index out of range``. This is independent
247-
of the H-46/H-47 ridge fixes and is *not* in the assigned fix scope, so the
248-
test pins the current broken behavior (and exercises the code path) rather
249-
than asserting success. See summary notes.
242+
def test_logistic_regression_fit_runs():
243+
"""``LogisticRegression.call`` now fits instead of raising ``IndexError``.
244+
245+
Fixed in audit 2026-06-19: ``call`` previously flattened ``targets`` to 1-D
246+
and then indexed ``targets.shape[1]``; it now initialises a 1-D parameter
247+
vector and runs the gradient-descent solver to completion.
250248
"""
251249
from brainpy.algorithms.offline import LogisticRegression
252250

253251
rng = np.random.RandomState(3)
254252
x = jnp.asarray(rng.randn(30, 2))
255253
y = jnp.asarray((np.asarray(x)[:, :1] > 0).astype("float32"))
256-
with pytest.raises(IndexError):
257-
LogisticRegression(max_iter=100)(y, x)
254+
w = LogisticRegression(max_iter=100)(y, x)
255+
assert w is not None
256+
assert not bool(jnp.isnan(jnp.asarray(w)).any())
258257

259258

260259
def test_offline_least_square_and_polynomial():

0 commit comments

Comments
 (0)