Skip to content

Added before_run codeblock in numpy and C++ templates #1603

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 35 additions & 12 deletions brian2/codegen/runtime/numpy_rt/templates/spikegenerator.py_
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
{# USES_VARIABLES { _spikespace, neuron_index, _timebins, _period_bins, _lastindex, t_in_timesteps } #}
{% extends 'common_group.py_' %}
{% extends 'common_group.py.jinja2' %}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might have been better to call templates like this, but it is currently called common_group.py_.

{% block before_code %}
# Copy of the SpikeGeneratorGroup.before_run code
dt = {{dt.item()}} # Always copy dt
period = {{period_}} # Always copy period
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables are not available to the template, since it does not know that they are needed. You can use USES_VARIABLES to specify them. See https://brian2.readthedocs.io/en/stable/developer/codegen.html#template-keywords and other templates.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _ suffix syntax is only used in Python user code to get the values of a variable without its units. In a template, variables are always the underlying numpy arrays without unit information, you can therefore simply use period here.

Suggested change
period = {{period_}} # Always copy period
period = {{period}} # Always copy period

# Always recalculate timesteps
from brian2 import defaultclock # Use Brian 2's clock instead of 't'
current_t = defaultclock.t
timesteps = ({{_spike_time}} / dt).astype(np.int32)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should use {{t}} instead of the defaultclock.t (even though most of the time the two will be the same).

current_step = int(current_t / dt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names used here have to be the names as added with self.variables.add_, not the name of the SpikeGeneratorGroup attribute – in this case, the name should be spike_time not _spike_time.


# Always update _lastindex
in_the_past = np.nonzero(timesteps < current_step)[0]
if len(in_the_past):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Python templates, numpy is available as _numpy, not np.

Suggested change
in_the_past = np.nonzero(timesteps < current_step)[0]
in_the_past = _numpy.nonzero(timesteps < current_step)[0]

{{_lastindex}}[0] = in_the_past[-1] + 1
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For scalar variables, Brian already applies the [0] automatically.

Suggested change
{{_lastindex}}[0] = in_the_past[-1] + 1
{{_lastindex}} = in_the_past[-1] + 1

{{_lastindex}}[0] = 0

# Always recalculate _timebins
shift = 1e-3 * dt
timebins = np.asarray(({{_spike_time}} + shift) / dt, dtype=np.int32)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
timebins = np.asarray(({{_spike_time}} + shift) / dt, dtype=np.int32)
timebins = _numpy.asarray(({{_spike_time}} + shift) / dt, dtype=np.int32)

{{_timebins}}[:] = timebins

# Always recalculate period_bins (ignore limit checks)
period_bins = int(round(period / dt))
{{_period_bins}}[0] = period_bins
{% endblock %}

{% block maincode %}
_the_period = {{_period_bins}}
_timebin = {{t_in_timesteps}}
_timebin = int(defaultclock.t / {{dt.item()}}) # Use Brian 2's clock instead of 't_in_timesteps'
_n_spikes = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment about .item() above.


_lastindex_before = {{_lastindex}}

if _the_period > 0:
_timebin %= _the_period
# If there is a periodicity in the SpikeGenerator, we need to reset the
# lastindex when the period has passed
if _lastindex_before > 0 and {{_timebins}}[_lastindex_before - 1] >= _timebin:
_lastindex_before = 0
# Always reset _lastindex if period is applied
_timebin %= _the_period
_lastindex_before = 0

_n_spikes = _numpy.searchsorted({{_timebins}}[_lastindex_before:],
_timebin, side='right')
_n_spikes = _numpy.searchsorted({{_timebins}}, _timebin, side='right')

{{_lastindex}} = _lastindex_before + _n_spikes

_indices = {{neuron_index}}[_lastindex_before:_lastindex_before+_n_spikes]

{{_spikespace}}[:_n_spikes] = _indices
Expand Down
62 changes: 47 additions & 15 deletions brian2/devices/cpp_standalone/templates/spikegenerator.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
{# USES_VARIABLES { _spikespace, neuron_index, _timebins, _period_bins, _lastindex, t_in_timesteps, N } #}
{# USES_VARIABLES { _spikespace, neuron_index, _timebins, _period_bins, _lastindex, N, _spike_time, dt, period } #}
{% extends 'common_group.cpp' %}

{% block before_code %}
// Copy of the SpikeGeneratorGroup.before_run code
const double _dt = {{dt.item()}}; // Always copy dt
const double _period = {{period_}}; // Always copy period

// Always recalculate _timesteps
std::vector<int32_t> _timesteps({{_spike_time}}.size());
for (size_t i = 0; i < _timesteps.size(); i++) {
_timesteps[i] = static_cast<int32_t>({{_spike_time}}[i] / _dt);
}

// Get current simulation time from Brian 2's clock instead of 't'
extern double defaultclock_t;
const int32_t _current_step = static_cast<int32_t>(defaultclock_t / _dt);

// Always update _lastindex
int32_t _last_idx = 0;
for (size_t i = 0; i < _timesteps.size(); i++) {
if (_timesteps[i] < _current_step) {
_last_idx = i + 1;
} else {
break;
}
}
{{_lastindex}} = _last_idx;

// Always recalculate _timebins
const double _shift = 1e-3 * _dt;
std::vector<int32_t> _timebins({{_spike_time}}.size());
for (size_t i = 0; i < _timebins.size(); i++) {
_timebins[i] = static_cast<int32_t>({{_spike_time}}[i] + _shift) / _dt;
}
{{_timebins}} = _timebins;

// Always recalculate _period_bins
{{_period_bins}} = static_cast<int32_t>(std::round(_period / _dt));
{% endblock %}

{% block maincode %}

const int32_t _the_period = {{_period_bins}};
int32_t _timebin = {{t_in_timesteps}};
const int32_t _n_spikes = 0;

if (_the_period > 0) {
_timebin %= _the_period;
// If there is a periodicity in the SpikeGenerator, we need to reset the
// lastindex when the period has passed
if ({{_lastindex}} > 0 && {{_timebins}}[{{_lastindex}} - 1] >= _timebin)
{{_lastindex}} = 0;
}
int32_t _timebin = static_cast<int32_t>(defaultclock_t / {{dt.item()}}); // Use Brian 2's clock instead of 't_in_timesteps'

// Always recalculate timebin with period
_timebin %= _the_period;
{{_lastindex}} = 0;

int32_t _cpp_numspikes = 0;

for(size_t _idx={{_lastindex}}; _idx < _num_timebins; _idx++)
{
for (size_t _idx = {{_lastindex}}; _idx < _num_timebins; _idx++) {
if ({{_timebins}}[_idx] > _timebin)
break;

Expand All @@ -28,5 +61,4 @@

{{_lastindex}} += _cpp_numspikes;


{% endblock %}
{% endblock %}
Loading