Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-contiguous subgroups #1229

Open
wants to merge 68 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
604e633
WIP: First steps to generalize subgroups to be non-contiguous
mstimberg Feb 21, 2020
d67ca90
Update SpikeMonitor for non-contiguous subgroups
mstimberg Feb 24, 2020
eeff84b
Sort indices before their use in Subgroup
mstimberg Feb 25, 2020
97659da
Fix logic error in Cython/C++ SpikeMonitor code
mstimberg Feb 25, 2020
4672adf
Make PopulationRateMonitor work with non-contiguous subgroups
mstimberg Feb 25, 2020
4c677f0
Add subgroups test for StateMonitor
mstimberg Feb 26, 2020
db2dcd5
Fix subgroups of SpatialNeuron
mstimberg Feb 26, 2020
aa9237d
Forbid using non-contiguous subgroups in Synapses
mstimberg Feb 26, 2020
db3ddf4
Enable string expressions for subgroups
mstimberg Feb 26, 2020
f0a34d1
Make string expressions for subgroups/indexing work in standalone
mstimberg Feb 27, 2020
5acd917
Fix string indexing with references to external constants
mstimberg Feb 28, 2020
33f537e
Make doctest compatible with Windows output for 32bit ints
mstimberg Sep 14, 2020
b477583
Do not allow unsorted indices in subgroups
mstimberg Sep 14, 2020
0cfe648
Merge branch 'master' into non_contiguous_subgroup
mstimberg Jan 4, 2021
e48f89b
Merge branch 'master' into non_contiguous_subgroup
mstimberg Feb 22, 2021
92aff0b
Merge branch 'master' into non_contiguous_subgroup
mstimberg Apr 21, 2021
cdb5427
Fix a remaining case of a deprecated numpy scalar
mstimberg Apr 21, 2021
35bf32f
Merge remote-tracking branch 'origin/master' into non_contiguous_subg…
mstimberg Feb 8, 2022
3032909
Merge branch 'master' into non_contiguous_subgroup
mstimberg Feb 9, 2022
3177307
Merge remote-tracking branch 'origin/master' into non_contiguous_subg…
mstimberg Apr 29, 2022
89430b9
minor optimization for non-contiguous subgroups
mstimberg May 2, 2022
269d9e0
make PoissonGroup work with non-contiguous subgroups
mstimberg May 2, 2022
73cd6d9
make SpikeMonitor generated code deterministic
mstimberg May 2, 2022
d0abc65
Check for illegal subgroup indices in consistent way
mstimberg May 2, 2022
afd5324
Merge branch 'non_contiguous_subgroup' of github.com:brian-team/brian…
mstimberg May 2, 2022
20e0efb
revert run_tests script to original version
mstimberg May 2, 2022
3ae9e1a
Add/update tests for subgroups
mstimberg May 2, 2022
fd2933d
Merge branch 'master' into non_contiguous_subgroup
mstimberg Jun 8, 2022
c57e129
Merge branch 'master' into non_contiguous_subgroup
mstimberg Oct 19, 2022
d8707aa
Update documentation for non-contiguous subgroups
mstimberg Nov 10, 2022
7e495bb
Some more basic tests for incorrect subgroup creation
mstimberg Nov 10, 2022
ec0fe52
Merge branch 'master' into non_contiguous_subgroup
mstimberg Nov 10, 2022
aaacc26
Fix exception type in subgroup tests
mstimberg Nov 14, 2022
22629b8
Test+doc for non-contiguous indices in SpatialNeuron
mstimberg Nov 14, 2022
209d239
Merge branch 'master' into non_contiguous_subgroup
mstimberg Nov 22, 2022
65ec4c3
Remove deprecated usage of np.bool
mstimberg Nov 22, 2022
82e3ffc
Fragile but basic working version of SynapticSubgroup
mstimberg Mar 23, 2023
6f84542
Merge branch 'master' into non_contiguous_subgroup
mstimberg Mar 24, 2023
5509437
Remove unnecessary imports
mstimberg Mar 24, 2023
fe861b7
Add tests for synaptic subgroups
mstimberg Mar 24, 2023
73d214f
Support array indexing for multi-synaptic indices
mstimberg Mar 24, 2023
e51b0ae
Refactor `Group.Indexing` for better readability
mstimberg Mar 29, 2023
6ceba16
Add new tests for synaptic subgroups
mstimberg Mar 29, 2023
dbbb741
Small refactoring for synaptic indexing
mstimberg Mar 30, 2023
2ff3cb1
Fix a test
mstimberg Mar 31, 2023
fe0add7
Write sizes of dynamic arrays to disk after run in standalone mode
mstimberg Mar 31, 2023
eaf1efa
Simplify RateMonitor template for non-contiguous subgroup
mstimberg Apr 7, 2023
3759bf7
Minor reformatting in test
mstimberg Apr 7, 2023
2c4787b
Fail for out-of-range 1d array indices in Synapses
mstimberg Apr 19, 2023
53ae0d8
Handle scalar synaptic indices correctly
mstimberg Apr 20, 2023
6d27f53
Raise errors for boolean indices of incorrect shape
mstimberg Apr 20, 2023
adc17ec
Handle IndexErrors correctly for synapses as well
mstimberg Apr 20, 2023
fed868e
Install coveralls with pip everywhere
mstimberg Apr 20, 2023
d163840
Merge branch 'master' into non_contiguous_subgroup
mstimberg Jun 15, 2023
a7f6cbe
Fix incorrect merge
mstimberg Jun 15, 2023
1c050b2
Merge remote-tracking branch 'origin/master' into non_contiguous_subg…
mstimberg Oct 6, 2023
a7cd7f3
Do not force float64 in TimedArray
mstimberg Oct 26, 2023
437e3fe
Invalidate caches on each run
mstimberg Oct 26, 2023
de0ee35
Merge remote-tracking branch 'origin/master' into non_contiguous_subg…
mstimberg Oct 26, 2023
b476451
Fix cache invalidation
mstimberg Oct 30, 2023
285dffb
Correctly the return type of TimedArray functions in generated code
mstimberg Oct 30, 2023
4cb865b
WIP: Allow non-contiguous subgroups for synapses
mstimberg Nov 2, 2023
bb7ddb9
Allow non-contiguous subgroups for synapses
mstimberg Nov 14, 2023
ce5bb3b
Non-contiguous subgroups for synapses: summed variables
mstimberg Nov 14, 2023
16e2ffc
Test summed variable error for overlapping subgroups
mstimberg Nov 16, 2023
3097569
Merge branch 'master' into non_contiguous_subgroup
mstimberg Sep 13, 2024
f1ea1b5
minor fixes
mstimberg Sep 13, 2024
c21ec60
Re-add write sizes of dynamic arrays to disk
mstimberg Sep 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions brian2/codegen/runtime/cython_rt/templates/ratemonitor.pyx
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
{# USES_VARIABLES { N, t, rate, _clock_t, _clock_dt, _spikespace,
_num_source_neurons, _source_start, _source_stop } #}
{# USES_VARIABLES { N, t, rate, _clock_t, _clock_dt, _spikespace} #}
{% extends 'common_group.pyx' %}

{% block maincode %}

cdef size_t _num_spikes = {{_spikespace}}[_num{{_spikespace}}-1]

# For subgroups, we do not want to record all spikes

{% if subgroup %}
cdef int32_t _filtered_spikes = 0
cdef size_t _source_index_counter = 0
{% if contiguous %}{# contiguous subgroup #}
# We assume that spikes are ordered
cdef int _start_idx = -1
cdef int _end_idx = -1
cdef size_t _j
_start_idx = _num_spikes
_end_idx = _num_spikes
for _j in range(_num_spikes):
_idx = {{_spikespace}}[_j]
if _idx >= _source_start:
_start_idx = _j
break
if _start_idx == -1:
_start_idx = _num_spikes
for _j in range(_start_idx, _num_spikes):
for _j in range(_num_spikes-1, _start_idx-1, -1):
_idx = {{_spikespace}}[_j]
if _idx >= _source_stop:
_end_idx = _j
if _idx < _source_stop:
break
_end_idx = _j
_filtered_spikes = _end_idx - _start_idx
{% else %}{# non-contiguous subgroup #}
for _j in range(_num_spikes):
_idx = {{_spikespace}}[_j]
if _idx < {{_source_indices}}[_source_index_counter]:
continue
while {{_source_indices}}[_source_index_counter] < _idx:
_source_index_counter += 1
if (_source_index_counter < {{source_N}} and
_idx == {{_source_indices}}[_source_index_counter]):
_source_index_counter += 1
_filtered_spikes += 1

if _source_index_counter == {{source_N}}:
break
if _end_idx == -1:
_end_idx =_num_spikes
_num_spikes = _end_idx - _start_idx
{% endif %}
_num_spikes = _filtered_spikes
{% endif %}

# Calculate the new length for the arrays
cdef size_t _new_len = {{_dynamic_t}}.shape[0] + 1
Expand All @@ -36,6 +49,6 @@

# Set the new values
{{_dynamic_t}}.data[_new_len-1] = {{_clock_t}}
{{_dynamic_rate}}.data[_new_len-1] = _num_spikes/{{_clock_dt}}/_num_source_neurons
{{_dynamic_rate}}.data[_new_len-1] = _num_spikes/{{_clock_dt}}/{{source_N}}

{% endblock %}
53 changes: 51 additions & 2 deletions brian2/codegen/runtime/cython_rt/templates/spikemonitor.pyx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{# USES_VARIABLES { N, _clock_t, count,
_source_start, _source_stop} #}
{# USES_VARIABLES { N, _clock_t, count} #}
{% extends 'common_group.pyx' %}

{% block maincode %}
Expand All @@ -9,11 +8,20 @@

cdef size_t _num_events = {{_eventspace}}[_num{{_eventspace}}-1]
cdef size_t _start_idx, _end_idx, _curlen, _newlen, _j
{% if subgroup and not contiguous %}
# We use the same data structure as for the eventspace to store the
# "filtered" events, i.e. the events that are indexed in the subgroup
cdef int[{{source_N}} + 1] _filtered_events
cdef size_t _source_index_counter = 0
_filtered_events[{{source_N}}] = 0
{% endif %}
{% for varname, var in record_variables | dictsort %}
cdef {{cpp_dtype(var.dtype)}}[:] _{{varname}}_view
{% endfor %}
if _num_events > 0:
{% if subgroup %}
# For subgroups, we do not want to record all spikes
{% if contiguous %}
# We assume that spikes are ordered
_start_idx = _num_events
_end_idx = _num_events
Expand All @@ -28,6 +36,25 @@
break
_end_idx = _j
_num_events = _end_idx - _start_idx
{% else %}
for _j in range(_num_events):
_idx = {{_eventspace}}[_j]
if _idx < {{_source_indices}}[_source_index_counter]:
continue
if _idx > {{_source_indices}}[{{source_N}}-1]:
break
while {{_source_indices}}[_source_index_counter] < _idx:
_source_index_counter += 1
if (_source_index_counter < {{source_N}} and
_idx == {{_source_indices}}[_source_index_counter]):
_source_index_counter += 1
_filtered_events[_filtered_events[{{source_N}}]] = _idx
_filtered_events[{{source_N}}] += 1
if _source_index_counter == {{source_N}}:
break
_num_events = _filtered_events[{{source_N}}]
{% endif %}
{% endif %}
if _num_events > 0:
# scalar code
_vectorisation_idx = 1
Expand All @@ -41,6 +68,8 @@
_{{varname}}_view = {{get_array_name(var, access_data=False)}}.data
{% endfor %}
# Copy the values across
{% if subgroup %}
{% if contiguous %}
for _j in range(_start_idx, _end_idx):
_idx = {{_eventspace}}[_j]
_vectorisation_idx = _idx
Expand All @@ -49,4 +78,24 @@
_{{varname}}_view [_curlen + _j - _start_idx] = _to_record_{{varname}}
{% endfor %}
{{count}}[_idx - _source_start] += 1
{% else %}
for _j in range(_num_events):
_idx = _filtered_events[_j]
_vectorisation_idx = _idx
{{ vector_code|autoindent }}
{% for varname in record_variables | sort %}
_{{varname}}_view [_curlen + _j] = _to_record_{{varname}}
{% endfor %}
{{count}}[_to_record_i] += 1
{% endif %}
{% else %}
for _j in range(_num_events):
_idx = {{_eventspace}}[_j]
_vectorisation_idx = _idx
{{ vector_code|autoindent }}
{% for varname in record_variables | sort %}
_{{varname}}_view [_curlen + _j] = _to_record_{{varname}}
{% endfor %}
{{count}}[_idx] += 1
{% endif %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

# Set all the target variable values to zero
for _target_idx in range({{_target_size_name}}):
{% if _target_contiguous %}
{{_target_var_array}}[_target_idx + {{_target_start}}] = 0
{% else %}
{{_target_var_array}}[{{_target_indices}}[_target_idx]] = 0
{% endif %}

# scalar code
_vectorisation_idx = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ cdef void _flush_buffer(buf, dynarr, int buf_len):
{{scalar_code['update']|autoindent}}

for _{{outer_index}} in range({{outer_index_size}}):
{% if outer_contiguous %}
_raw{{outer_index_array}} = _{{outer_index}} + {{outer_index_offset}}

{% else %}
_raw{{outer_index_array}} = {{get_array_name(variables[outer_sub_idx])}}[_{{outer_index}}]
{% endif %}
{% if not result_index_condition %}
{{vector_code['create_cond']|autoindent}}
if not _cond:
Expand Down Expand Up @@ -162,8 +165,11 @@ cdef void _flush_buffer(buf, dynarr, int buf_len):
{% endif %}

{{vector_code['generator_expr']|autoindent}}
{% if result_contiguous %}
_raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}}

{% else %}
_raw{{result_index_array}} = {{get_array_name(variables[result_sub_idx])}}[_{{result_index}}]
{% endif %}
{% if result_index_condition %}
{% if result_index_used %}
{# The condition could index outside of array range #}
Expand Down
2 changes: 1 addition & 1 deletion brian2/codegen/runtime/numpy_rt/numpy_rt.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def __iter__(self):
return iter(self.indices)

# Allow conversion to a proper array with np.array(...)
def __array__(self, dtype=None, copy=None):
def __array__(self, dtype=np.int32, copy=None):
if copy is False:
raise ValueError("LazyArange does not support copy=False")
if self.indices is None:
Expand Down
11 changes: 8 additions & 3 deletions brian2/codegen/runtime/numpy_rt/templates/ratemonitor.py_
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{# USES_VARIABLES { rate, t, _spikespace, _num_source_neurons,
_clock_t, _clock_dt, _source_start, _source_stop, N } #}
{# USES_VARIABLES { rate, t, _spikespace, _clock_t, _clock_dt, N } #}
{% extends 'common_group.py_' %}

{% block maincode %}
_spikes = {{_spikespace}}[:{{_spikespace}}[-1]]
{% if subgroup %}
# Take subgroups into account
{% if contiguous %}
_spikes = _spikes[(_spikes >= _source_start) & (_spikes < _source_stop)]
{% else %}
_spikes = _numpy.intersect1d(_spikes, {{_source_indices}}, assume_unique=True)
{% endif %}
{% endif %}
_new_len = {{N}} + 1
_owner.resize(_new_len)
{{N}} = _new_len
{{_dynamic_t}}[-1] = {{_clock_t}}
{{_dynamic_rate}}[-1] = 1.0 * len(_spikes) / {{_clock_dt}} / _num_source_neurons
{{_dynamic_rate}}[-1] = 1.0 * len(_spikes) / {{_clock_dt}} / {{source_N}}
{% endblock %}
22 changes: 17 additions & 5 deletions brian2/codegen/runtime/numpy_rt/templates/spikemonitor.py_
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{# USES_VARIABLES {N, count, _clock_t, _source_start, _source_stop, _source_N} #}
{# USES_VARIABLES {N, count, _clock_t} #}
{% extends 'common_group.py_' %}

{% block maincode %}
Expand All @@ -9,11 +9,15 @@
_n_events = {{_eventspace}}[-1]
if _n_events > 0:
_events = {{_eventspace}}[:_n_events]
{% if subgroup %}
# Take subgroups into account
if _source_start != 0 or _source_stop != _source_N:
_events = _events[(_events >= _source_start) & (_events < _source_stop)]
_n_events = len(_events)

{% if contiguous %}
_events = _events[(_events >= _source_start) & (_events < _source_stop)]
{% else %}
_events = _numpy.intersect1d(_events, {{_source_indices}}, assume_unique=True)
{% endif %}
_n_events = len(_events)
{% endif %}
if _n_events > 0:
_vectorisation_idx = 1
{{scalar_code|autoindent}}
Expand All @@ -28,5 +32,13 @@ if _n_events > 0:
{% set dynamic_varname = get_array_name(var, access_data=False) %}
{{dynamic_varname}}[_curlen:_newlen] = _to_record_{{varname}}
{% endfor %}
{% if not subgroup %}
{{count}}[_events] += 1
{% else %}
{% if contiguous %}
{{count}}[_events - _source_start] += 1
{% else %}
{{count}}[_to_record_i] += 1
{% endif %}
{% endif %}
{% endblock %}
4 changes: 4 additions & 0 deletions brian2/codegen/runtime/numpy_rt/templates/summed_variable.py_
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ _vectorisation_idx = LazyArange(N)
# We write to the array, using the name provided as a keyword argument to the
# template
# Note that for subgroups, we do not want to overwrite the full array
{% if not _target_contiguous %}
{{_target_var_array}}[{{_target_indices}}] = _numpy.broadcast_to(_synaptic_var, (N, ))
{% else %}
{% if _target_start > 0 %}
_indices = {{_index_array}} - {{_target_start}}
{% else %}
Expand All @@ -32,4 +35,5 @@ _length = _target_stop - {{_target_start}}
{{_target_var_array}}[{{_target_start}}:_target_stop] = _numpy.bincount(_indices,
minlength=_length,
weights=_numpy.broadcast_to(_synaptic_var, (N, )))
{% endif %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ _vectorisation_idx = 1
"k" is called the inner variable #}

for _{{outer_index}} in range({{outer_index_size}}):
{% if outer_contiguous %}
_raw{{outer_index_array}} = _{{outer_index}} + {{outer_index_offset}}
{% else %}
_raw{{outer_index_array}} = {{get_array_name(variables[outer_sub_idx])}}[_{{outer_index}}]
{% endif %}
{% if not result_index_condition %}
{{vector_code['create_cond']|autoindent}}
if not _cond:
Expand All @@ -126,7 +130,11 @@ for _{{outer_index}} in range({{outer_index_size}}):
_vectorisation_idx = {{inner_variable}}
{{vector_code['generator_expr']|autoindent}}
_vectorisation_idx = _{{result_index}}
_raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}}
{% if result_contiguous %}
_raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}};
{% else %}
_raw{{result_index_array}} = {{get_array_name(variables[result_sub_idx])}}[_{{result_index}}]
{% endif %}
{% if result_index_condition %}
{% if result_index_used %}
{# The condition could index outside of array range #}
Expand Down Expand Up @@ -184,7 +192,11 @@ for _{{outer_index}} in range({{outer_index_size}}):
{% endif %}

_vectorisation_idx = _{{result_index}}
{% if result_contiguous %}
_raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}}
{% else %}
_raw{{result_index_array}} = {{get_array_name(variables[result_sub_idx])}}[_{{result_index}}]
{% endif %}
{{vector_code['update']|autoindent}}

if not _numpy.isscalar(_n):
Expand Down
34 changes: 22 additions & 12 deletions brian2/core/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from collections import Counter, defaultdict, namedtuple
from collections.abc import Mapping, Sequence

import numpy as np

from brian2.core.base import BrianObject, BrianObjectException
from brian2.core.clocks import Clock, defaultclock
from brian2.core.names import Nameable
Expand Down Expand Up @@ -321,18 +323,26 @@ def _check_multiple_summed_updaters(objects):
"the target group instead."
)
raise NotImplementedError(msg)
elif (
obj.target.start < other_target.stop
and other_target.start < obj.target.stop
):
# Overlapping subgroups
msg = (
"Multiple 'summed variables' target the "
f"variable '{obj.target_var.name}' in overlapping "
f"groups '{other_target.name}' and '{obj.target.name}'. "
"Use separate variables in the target groups instead."
)
raise NotImplementedError(msg)
else:
if getattr(obj.target, "contiguous", True):
target_indices = np.arange(obj.target.start, obj.target.stop)
else:
target_indices = obj.target.indices[:]
if getattr(other_target, "contiguous", True):
other_indices = np.arange(other_target.start, other_target.stop)
else:
other_indices = other_target.indices[:]
if np.intersect1d(
target_indices, other_indices, assume_unique=True
).size:
# Overlapping subgroups
msg = (
"Multiple 'summed variables' target the "
f"variable '{obj.target_var.name}' in overlapping "
f"groups '{other_target.name}' and '{obj.target.name}'. "
"Use separate variables in the target groups instead."
)
raise NotImplementedError(msg)
summed_targets[obj.target_var] = obj.target


Expand Down
2 changes: 1 addition & 1 deletion brian2/core/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def __setstate__(self, state):

@property
def is_boolean(self):
return np.issubdtype(self.dtype, np.bool_)
return np.issubdtype(self.dtype, bool)

@property
def is_integer(self):
Expand Down
4 changes: 4 additions & 0 deletions brian2/devices/cpp_standalone/codeobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,12 @@ def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
#: Store whether this code object defines before/after blocks
self.before_after_blocks = []
#: Store variables that are updated by this code object and therefore invalidate the cache
self.invalidate_cache_variables = set()

def __call__(self, **kwds):
for var in self.invalidate_cache_variables:
get_device().array_cache[var] = None
return self.run()

def compile_block(self, block):
Expand Down
Loading
Loading