Skip to content

Commit

Permalink
Use an epsilon independent of dt when comparing times
Browse files Browse the repository at this point in the history
  • Loading branch information
mstimberg authored and jangmarker committed Aug 25, 2020
1 parent 52e49e6 commit 4f7a5ca
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 12 deletions.
19 changes: 8 additions & 11 deletions brian2/core/clocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ def check_dt(new_dt, old_dt, target_t):
------
ValueError
If using the new dt value would lead to a difference in the target
time of more than `Clock.epsilon_dt` times ``new_dt`` (by default,
0.01% of the new dt).
time of more than `Clock.epsilon_dt` (by default, 10ns).
Examples
--------
Expand All @@ -53,7 +52,7 @@ def check_dt(new_dt, old_dt, target_t):
old_t = np.int64(np.round(target_t / old_dt)) * old_dt
new_t = np.int64(np.round(target_t / new_dt)) * new_dt
error_t = target_t
if abs(new_t - old_t)/new_dt > Clock.epsilon_dt:
if abs(new_t - old_t) > Clock.epsilon_dt:
raise ValueError(('Cannot set dt from {old} to {new}, the '
'time {t} is not a multiple of '
'{new}').format(old=str(old_dt * second),
Expand All @@ -75,10 +74,8 @@ class Clock(VariableOwner):
Notes
-----
Clocks are run in the same `Network.run` iteration if `~Clock.t` is the
same. The condition for two
clocks to be considered as having the same time is
``abs(t1-t2)<epsilon*abs(t1)``, a standard test for equality of floating
point values. The value of ``epsilon`` is ``1e-14``.
same. The condition for two clocks to be considered as having the same time
is ``abs(t1-t2)<10*ns``.
'''

def __init__(self, dt, name='clock*'):
Expand Down Expand Up @@ -122,7 +119,7 @@ def _set_t_update_dt(self, target_t=0*second):
def _calc_timestep(self, target_t):
'''
Calculate the integer time step for the target time. If it cannot be
exactly represented (up to 0.01% of dt), round up.
exactly represented (up to 10ns), round up.
Parameters
----------
Expand All @@ -137,7 +134,7 @@ def _calc_timestep(self, target_t):
new_i = np.int64(np.round(target_t / self.dt_))
new_t = new_i * self.dt_
if (new_t == target_t or
np.abs(new_t - target_t)/self.dt_ <= Clock.epsilon_dt):
np.abs(new_t - target_t) <= Clock.epsilon_dt):
new_timestep = new_i
else:
new_timestep = np.int64(np.ceil(target_t / self.dt_))
Expand Down Expand Up @@ -190,9 +187,9 @@ def set_interval(self, start, end):
self._i_end),
'many_timesteps')

#: The relative difference for times (in terms of dt) so that they are
#: The absolute difference for times (in seconds) so that clocks are
#: considered identical.
epsilon_dt = 1e-4
epsilon_dt = 1e-8


class DefaultClockProxy(object):
Expand Down
2 changes: 1 addition & 1 deletion brian2/core/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ def _nextclocks(self):
minclock, min_time, minclock_dt = min(clocks_times_dt, key=lambda k: k[1])
curclocks = {clock for clock, time, dt in clocks_times_dt if
(time == min_time or
abs(time - min_time)/min(minclock_dt, dt) < Clock.epsilon_dt)}
abs(time - min_time) < Clock.epsilon_dt)}
return minclock, curclocks

@device_override('network_run')
Expand Down
27 changes: 27 additions & 0 deletions brian2/tests/test_clocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_defaultclock():
assert_equal(defaultclock.dt, 1*ms)
assert defaultclock.name == 'defaultclock'


@pytest.mark.codegen_independent
def test_set_interval_warning():
clock = Clock(dt=0.1*ms)
Expand All @@ -56,10 +57,36 @@ def test_set_interval_warning():
assert logs[0][1].endswith('many_timesteps')


@pytest.mark.codegen_independent
def test_very_long_dt():
# See github issue #1054
clock1 = Clock(dt=0.1*ms)
clock2 = Clock(dt=100000*second)
clock1.set_interval(0*ms, 1*ms)
clock2.set_interval(0*ms, 1*ms) # The clock should advance
assert clock1.timestep[:] == 0
assert clock2.timestep[:] == 0
assert clock1._i_end == 10
assert clock2._i_end == 1
# Simulate advancing the clock
clock1.variables['timestep'].set_value(clock1._i_end)
clock1.variables['t'].set_value(clock1._i_end * clock1.dt)
clock2.variables['timestep'].set_value(clock2._i_end)
clock2.variables['t'].set_value(clock2._i_end * clock2.dt)

clock1.set_interval(1*ms, 2*ms)
clock2.set_interval(1*ms, 2*ms) # The clock should not advance
assert clock1.timestep[:] == 10
assert clock2.timestep[:] == 1
assert clock1._i_end == 20
assert clock2._i_end == 1


if __name__ == '__main__':
test_clock_attributes()
restore_initial_state()
test_clock_dt_change()
restore_initial_state()
test_defaultclock()
test_set_interval_warning()
test_very_long_dt()

0 comments on commit 4f7a5ca

Please sign in to comment.