Repository: brian-team/brian2 (Python)

Issue:
SpikeMonitor indices .shape -> AttributeError: No attribute with name stop
calling `.shape` on the indices of a `NeuronMonitor` gives an attribute error:

```python
spikes.i.shape

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/groups/group.py:382, in VariableOwner.state(self, name, use_units, level)
    381 try:
--> 382     var = self.variables[name]
    383 except KeyError as exc:

File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/core/variables.py:1646, in Variables.__getitem__(self, item)
   1645 def __getitem__(self, item):
-> 1646     return self._variables[item]

KeyError: 'stop'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/groups/group.py:420, in VariableOwner.__getattr__(self, name)
    419         use_units = True
--> 420     return self.state(name, use_units)
    422 except KeyError:

File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/groups/group.py:384, in VariableOwner.state(self, name, use_units, level)
    383 except KeyError as exc:
--> 384     raise KeyError(f"State variable {name} not found.") from exc
    386 if use_units:

KeyError: 'State variable stop not found.'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
Cell In[4], line 1
----> 1 spikes.i.shape

File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/core/variables.py:1586, in VariableView.shape(self)
   1582 if self.ndim == 1:
   1583     if not self.variable.scalar:
   1584         # This is safer than using the variable size, since it also works for subgroups
   1585         # see GitHub issue #1555
-> 1586         size = self.group.stop - self.group.start
   1587         assert size <= self.variable.size
   1588     else:

File ~/.pyenv/versions/3.13.0/lib/python3.13/site-packages/brian2/groups/group.py:423, in VariableOwner.__getattr__(self, name)
    420     return self.state(name, use_units)
    422 except KeyError:
--> 423     raise AttributeError(f"No attribute with name {name}")

AttributeError: No attribute with name stop
```

This seems to be caused by #1556 (this issue is not present for older brian2 <2.8.0 before this change)


Discussion:
Thank you very much for the report (and the fix!), your analysis sounds correct, this is indeed a regression we introduced when fixing another issue.

PS: I updated the title `NeuronMonitor` → `SpikeMonitor`



Patch:
diff --git a/brian2/core/variables.py b/brian2/core/variables.py
index 6d9d8680b..8d74b4a5c 100644
--- a/brian2/core/variables.py
+++ b/brian2/core/variables.py
@@ -1579,8 +1579,16 @@ def __hash__(self):
     # Get access to some basic properties of the underlying array
     @property
     def shape(self):
+        if isinstance(self.variable, DynamicArrayVariable):
+            # For dynamic variables, we need to make sure that their size is up-to-date in standalone mode
+            # We can trigger the update by asking for the values, even though we don't need them here
+            self.variable.get_value()
         if self.ndim == 1:
-            if not self.variable.scalar:
+            if (
+                hasattr(self, "group")
+                and hasattr(self.group, "start")
+                and hasattr(self.group, "stop")
+            ):
                 # This is safer than using the variable size, since it also works for subgroups
                 # see GitHub issue #1555
                 size = self.group.stop - self.group.start


Test patch:
diff --git a/brian2/tests/test_monitor.py b/brian2/tests/test_monitor.py
index efb2259b4..4e514b05f 100644
--- a/brian2/tests/test_monitor.py
+++ b/brian2/tests/test_monitor.py
@@ -156,6 +156,7 @@ def test_spike_monitor_subgroups():
     spikes_2 = SpikeMonitor(G[2:4])
     spikes_3 = SpikeMonitor(G[4:])
     run(defaultclock.dt)
+    assert spikes_all.i.shape == (3,)
     assert_allclose(spikes_all.i, [0, 4, 5])
     assert_allclose(spikes_all.t, [0, 0, 0] * ms)
     assert_allclose(spikes_1.i, [0])