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 19 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: 33 additions & 14 deletions brian2/codegen/runtime/cython_rt/templates/ratemonitor.pyx
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
{# 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]

{% 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 %}
{% if subgroup %}
# For subgroups, we do not want to record all spikes
{% if contiguous %}
# 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
if _end_idx == -1:
_end_idx =_num_spikes
_end_idx = _j
_num_spikes = _end_idx - _start_idx
{% else %}
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_events[_filtered_events[{{source_N}}]] = _idx
_filtered_events[{{source_N}}] += 1
if _source_index_counter == {{source_N}}:
break
_num_spikes = _filtered_events[{{source_N}}]
{% endif %}
{% endif %}

# Calculate the new length for the arrays
cdef size_t _new_len = {{_dynamic_t}}.shape[0] + 1
Expand All @@ -36,6 +55,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 %}
51 changes: 49 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,23 @@
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
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 +66,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 +76,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 %}
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 %}
38 changes: 36 additions & 2 deletions brian2/devices/cpp_standalone/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import numpy as np

import brian2
from brian2.codegen.codeobject import check_compiler_kwds
from brian2.codegen.codeobject import check_compiler_kwds, create_runner_codeobj
from brian2.codegen.cpp_prefs import get_compiler_and_args, get_msvc_env
from brian2.codegen.runtime.numpy_rt import NumpyCodeObject
from brian2.core.network import Network
from brian2.devices.device import Device, all_devices, set_device, reset_device
from brian2.core.functions import Function
Expand Down Expand Up @@ -475,7 +476,10 @@ def variableview_set_with_index_array(self, variableview, item,
indices)
staticarrayname_value = self.static_array(f"_value_{arrayname}",
value)
self.array_cache[variableview.variable] = None
# Put values into the cache
cache_variable = self.array_cache[variableview.variable]
if cache_variable is not None:
cache_variable[indices] = value
self.main_queue.append(('set_array_by_array', (arrayname,
staticarrayname_index,
staticarrayname_value)))
Expand Down Expand Up @@ -541,6 +545,36 @@ def variableview_get_with_expression(self, variableview, code,
"variables with string expressions in "
"standalone scripts.")

def index_wrapper_get_item(self, index_wrapper, item, level):
if isinstance(item, str):
variables = Variables(None)
variables.add_auxiliary_variable('_indices', dtype=np.int32)
variables.add_auxiliary_variable('_cond', dtype=np.bool)

abstract_code = '_cond = ' + item
namespace = get_local_namespace(level=level+2)
try:
codeobj = create_runner_codeobj(index_wrapper.group,
abstract_code,
'group_get_indices',
run_namespace=namespace,
additional_variables=variables,
codeobj_class=NumpyCodeObject
)
except NotImplementedError:
raise NotImplementedError('Cannot calculate indices with string '
'expressions in standalone mode if '
'the expression refers to variable '
'with values not known before running '
'the simulation.')
indices = codeobj()
# Delete code object from device to avoid trying to build it later
del self.code_objects[codeobj.name]
# Handle subgroups correctly
return index_wrapper.indices(indices)
else:
return index_wrapper.indices(item)

def code_object_class(self, codeobj_class=None, fallback_pref=None):
"""
Return `CodeObject` class (either `CPPStandaloneCodeObject` class or input)
Expand Down
74 changes: 52 additions & 22 deletions brian2/devices/cpp_standalone/templates/ratemonitor.cpp
Original file line number Diff line number Diff line change
@@ -1,36 +1,66 @@
{# USES_VARIABLES { N, rate, t, _spikespace, _clock_t, _clock_dt,
_num_source_neurons, _source_start, _source_stop } #}
{# USES_VARIABLES { N, rate, t, _spikespace, _clock_t, _clock_dt} #}
{# WRITES_TO_READ_ONLY_VARIABLES { N } #}
{% extends 'common_group.cpp' %}

{% block maincode %}
size_t _num_spikes = {{_spikespace}}[_num_spikespace-1];
{% 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
int32_t _filtered_events[{{source_N}} + 1];
_filtered_events[{{source_N}}] = 0;
size_t _source_index_counter = 0;
{% endif %}
{% if subgroup %}
// For subgroups, we do not want to record all spikes
// We assume that spikes are ordered
int _start_idx = -1;
int _end_idx = -1;
for(size_t _j=0; _j<_num_spikes; _j++)
size_t _start_idx = _num_spikes;
size_t _end_idx = _num_spikes;
if (_num_spikes > 0)
{
const size_t _idx = {{_spikespace}}[_j];
if (_idx >= _source_start) {
_start_idx = _j;
break;
{% if contiguous %}
for(size_t _j=0; _j<_num_spikes; _j++)
{
const int _idx = {{_spikespace}}[_j];
if (_idx >= _source_start) {
_start_idx = _j;
break;
}
}
}
if (_start_idx == -1)
_start_idx = _num_spikes;
for(size_t _j=_start_idx; _j<_num_spikes; _j++)
{
const size_t _idx = {{_spikespace}}[_j];
if (_idx >= _source_stop) {
for(size_t _j=_num_spikes-1; _j>=_start_idx; _j--)
{
const int _idx = {{_spikespace}}[_j];
if (_idx < _source_stop) {
break;
}
_end_idx = _j;
break;
}
_num_spikes = _end_idx - _start_idx;
{% else %}
for (size_t _j=0; _j<_num_spikes; _j++)
{
const size_t _idx = {{_spikespace}}[_j];
if (_idx < {{_source_indices}}[_source_index_counter])
continue;
while ({{_source_indices}}[_source_index_counter] < _idx)
{
_source_index_counter++;
}
if (_source_index_counter < {{source_N}} &&
_idx == {{_source_indices}}[_source_index_counter])
{
_source_index_counter += 1;
_filtered_events[_filtered_events[{{source_N}}]++] = _idx;
if (_source_index_counter == {{source_N}})
break;
}
if (_source_index_counter == {{source_N}})
break;
}
_num_spikes = _filtered_events[{{source_N}}];
{% endif %}
}
if (_end_idx == -1)
_end_idx =_num_spikes;
_num_spikes = _end_idx - _start_idx;
{{_dynamic_rate}}.push_back(1.0*_num_spikes/{{_clock_dt}}/_num_source_neurons);
{% endif %}
{{_dynamic_rate}}.push_back(1.0*_num_spikes/{{_clock_dt}}/{{source_N}});
{{_dynamic_t}}.push_back({{_clock_t}});
{{N}}++;
{% endblock %}
Expand Down
Loading