def test_set_interval_warning(): clock = Clock(dt=0.1 * ms) with catch_logs() as logs: clock.set_interval(0 * second, 1000 * second) # no problem assert len(logs) == 0 with catch_logs() as logs: clock.set_interval(0 * second, 10000000000 * second) # too long assert len(logs) == 1 assert logs[0][1].endswith('many_timesteps')
def test_warning_internal_variables(): group1 = SimpleGroup(namespace=None, variables={'N': Constant('N', 5)}) group2 = SimpleGroup(namespace=None, variables={'N': Constant('N', 7)}) with catch_logs() as l: group1.resolve_all(['N'], run_namespace={'N': 5}) # should not raise a warning assert len(l) == 0, 'got warnings: %s' % str(l) with catch_logs() as l: group2.resolve_all(['N'], run_namespace={'N': 5}) # should raise a warning assert len(l) == 1, 'got warnings: %s' % str(l) assert l[0][1].endswith('.resolution_conflict')
def test_missing_refractory_warning(): # Forgotten refractory argument with catch_logs() as l: group = NeuronGroup(1, 'dv/dt = -v / (10*ms) : 1 (unless refractory)', threshold='v > 1', reset='v = 0') assert len(l) == 1 assert l[0][0] == 'WARNING' and l[0][1].endswith('no_refractory')
def test_math_functions(func, needs_c99_support): ''' Test that math functions give the same result, regardless of whether used directly or in generated Python or C++ code. ''' if not get_device() == RuntimeDevice or prefs.codegen.target != 'numpy': if needs_c99_support and not compiler_supports_c99(): pytest.skip('Support for function "{}" needs a compiler with C99 ' 'support.') test_array = np.array([-1, -0.5, 0, 0.5, 1]) with catch_logs() as _: # Let's suppress warnings about illegal values # Calculate the result directly numpy_result = func(test_array) # Calculate the result in a somewhat complicated way by using a # subexpression in a NeuronGroup if func.__name__ == 'absolute': # we want to use the name abs instead of absolute func_name = 'abs' else: func_name = func.__name__ G = NeuronGroup( len(test_array), '''func = {func}(variable) : 1 variable : 1'''.format(func=func_name)) G.variable = test_array mon = StateMonitor(G, 'func', record=True) net = Network(G, mon) net.run(defaultclock.dt) assert_allclose( numpy_result, mon.func_.flatten(), err_msg='Function %s did not return the correct values' % func.__name__)
def test_check_for_invalid_values_linear_integrator(): # A differential equation that cannot be solved by the linear # integrator should return nan values to warn the user, and not silently # return incorrect values. See discussion on # https://github.com/angela-team/angela2/issues/626 a = 0.0 / ms b = 1.0 / ms c = -0.5 / ms d = -0.1 / ms eqs = ''' dx/dt = a * x + b * y : 1 dy/dt = c * x + d * y : 1 ''' G = NeuronGroup(1, eqs, threshold='x > 100', reset='x = 0', method='exact', method_options={'simplify': False}) G.x = 1 angelaLogger._log_messages.clear( ) # because the log message is set to be shown only once with catch_logs() as clog: try: run(1 * ms) # this check allows for the possibility that we improve the linear # integrator in the future so that it can handle this equation if numpy.isnan(G.x[0]): assert 'invalid_values' in repr(clog) else: assert G.x[0] != 0 except angelaObjectException as exc: assert isinstance(exc.__cause__, UnsupportedEquationsException)
def test_explicit_namespace(): ''' Test resolution with an explicitly provided namespace ''' group = SimpleGroup(namespace={'variable': 42}, variables={}) # Explicitly provided with catch_logs() as l: assert group._resolve('variable', {}).get_value_with_unit() == 42 assert len(l) == 0 # Value from an explicit run namespace with catch_logs() as l: assert group._resolve('yet_another_var', run_namespace={ 'yet_another_var': 17 }).get_value_with_unit() == 17 assert len(l) == 0
def test_delete_directory(): set_device('cpp_standalone', build_on_run=True, directory=None) group = NeuronGroup(10, 'dv/dt = -v / (10*ms) : volt', method='exact') group.v = np.arange(10) * mV # uses the static array mechanism run(defaultclock.dt) # Add a new file dummy_file = os.path.join(device.project_dir, 'results', 'dummy.txt') open(dummy_file, 'w').flush() assert os.path.isfile(dummy_file) with catch_logs() as logs: device.delete(directory=True) assert len(logs) == 1 assert os.path.isfile(dummy_file) with catch_logs() as logs: device.delete(directory=True, force=True) assert len(logs) == 0 # everything should be deleted assert not os.path.exists(device.project_dir)
def test_warning(): from angela2.core.functions import DEFAULT_FUNCTIONS from angela2.units.stdunits import cm as angela_cm # Name in external namespace clashes with unit/function name exp = 23 cm = 42 group = SimpleGroup(namespace=None, variables={}) namespace = get_local_namespace(level=0) with catch_logs() as l: resolved = group.resolve_all(['exp'], namespace)['exp'] assert resolved == DEFAULT_FUNCTIONS['exp'] assert len(l) == 1, 'got warnings: %s' % str(l) assert l[0][1].endswith('.resolution_conflict') with catch_logs() as l: resolved = group.resolve_all(['cm'], namespace)['cm'] assert resolved.get_value_with_unit() == angela_cm assert len(l) == 1, 'got warnings: %s' % str(l) assert l[0][1].endswith('.resolution_conflict')
def test_state_variables_group_as_index_problematic(): G = NeuronGroup(10, 'v : 1') SG = G[4:9] G.v = 1 tests = [('i', 1), ('N', 1), ('N + i', 2), ('v', 0)] for value, n_warnings in tests: with catch_logs() as l: G.v.__setitem__(SG, value) assert len(l) == n_warnings, 'expected %d, got %d warnings' % ( n_warnings, len(l)) assert all([ entry[1].endswith('ambiguous_string_expression') for entry in l ])
def test_spikegenerator_extreme_period(): ''' Basic test for `SpikeGeneratorGroup`. ''' indices = np.array([0, 1, 2]) times = np.array([0, 1, 2]) * ms SG = SpikeGeneratorGroup(5, indices, times, period=1e6*second) s_mon = SpikeMonitor(SG) with catch_logs() as l: run(10*ms) assert_equal(s_mon.i, np.array([0, 1, 2])) assert_allclose(s_mon.t, [0, 1, 2]*ms) assert len(l) == 1 and l[0][1].endswith('spikegenerator_long_period')
def test_poissongroup_namespace(): rate_const = 0 * Hz P = PoissonGroup(1, rates='rate_const', namespace={'rate_const': 1 / defaultclock.dt}, name='poissongroup_%s' % (uuid.uuid4().hex)) P2 = PoissonGroup(1, rates='rate_const') mon = SpikeMonitor(P) mon2 = SpikeMonitor(P2) with catch_logs() as l: run(2 * defaultclock.dt) assert len(l) == 1 assert l[0][1].endswith('resolution_conflict') assert mon.num_spikes == 2 assert mon2.num_spikes == 0
def test_spikegenerator_multiple_runs(): indices = np.zeros(5) times = np.arange(5)*ms spike_gen = SpikeGeneratorGroup(1, indices, times) # all good spike_mon = SpikeMonitor(spike_gen) run(5*ms) # Setting the same spike times again should not do anything, since they are # before the start of the current simulation spike_gen.set_spikes(indices, times) # however, a warning should be raised with catch_logs() as l: run(5*ms) device.build(direct_call=False, **device.build_options) assert len(l) == 1 and l[0][1].endswith('ignored_spikes') assert spike_mon.num_spikes == 5
def test_multiple_noise_variables_deterministic_noise( fake_randn_randn_fixture): all_eqs = [ '''dx/dt = y : 1 dy/dt = -y / (10*ms) + dt**-.5*0.5*ms**-1.5 + dt**-.5*0.5*ms**-1.5: Hz ''', '''dx/dt = y + dt**-.5*0.5*ms**-0.5: 1 dy/dt = -y / (10*ms) + dt**-.5*0.5 * ms**-1.5 : Hz ''' ] all_eqs_noise = [ '''dx/dt = y : 1 dy/dt = -y / (10*ms) + xi_1 * ms**-1.5 + xi_2 * ms**-1.5: Hz ''', '''dx/dt = y + xi_1*ms**-0.5: 1 dy/dt = -y / (10*ms) + xi_2 * ms**-1.5 : Hz ''' ] for eqs, eqs_noise in zip(all_eqs, all_eqs_noise): G = NeuronGroup(2, eqs, method='euler') G.x = [5, 17] G.y = [25, 5] * Hz mon = StateMonitor(G, ['x', 'y'], record=True) net = Network(G, mon) net.run(10 * ms) no_noise_x, no_noise_y = mon.x[:], mon.y[:] for method_name, method in [('euler', euler), ('heun', heun)]: with catch_logs('WARNING'): G = NeuronGroup(2, eqs_noise, method=method) G.x = [5, 17] G.y = [25, 5] * Hz mon = StateMonitor(G, ['x', 'y'], record=True) net = Network(G, mon) net.run(10 * ms) assert_allclose(mon.x[:], no_noise_x, err_msg='Method %s gave incorrect results' % method_name) assert_allclose(mon.y[:], no_noise_y, err_msg='Method %s gave incorrect results' % method_name)
def test_multiple_noise_variables_extended(): # Some actual simulations with multiple noise variables eqs = '''dx/dt = y : 1 dy/dt = - 1*ms**-1*y - 40*ms**-2*x : Hz ''' all_eqs_noise = [ '''dx/dt = y : 1 dy/dt = noise_factor*ms**-1.5*xi_1 + noise_factor*ms**-1.5*xi_2 - 1*ms**-1*y - 40*ms**-2*x : Hz ''', '''dx/dt = y + noise_factor*ms**-0.5*xi_1: 1 dy/dt = noise_factor*ms**-1.5*xi_2 - 1*ms**-1*y - 40*ms**-2*x : Hz ''' ] G = NeuronGroup(2, eqs, method='euler') G.x = [0.5, 1] G.y = [0, 0.5] * Hz mon = StateMonitor(G, ['x', 'y'], record=True) net = Network(G, mon) net.run(10 * ms) no_noise_x, no_noise_y = mon.x[:], mon.y[:] for eqs_noise in all_eqs_noise: for method_name, method in [('euler', euler), ('heun', heun)]: with catch_logs('WARNING'): G = NeuronGroup(2, eqs_noise, method=method) G.x = [0.5, 1] G.y = [0, 0.5] * Hz mon = StateMonitor(G, ['x', 'y'], record=True) net = Network(G, mon) # We run it deterministically, but still we'd detect major errors (e.g. # non-stochastic terms that are added twice, see #330 net.run(10 * ms, namespace={'noise_factor': 0}) assert_allclose(mon.x[:], no_noise_x, err_msg='Method %s gave incorrect results' % method_name) assert_allclose(mon.y[:], no_noise_y, err_msg='Method %s gave incorrect results' % method_name)
def test_synapses_access_subgroups_problematic(): G1 = NeuronGroup(5, 'x:1') G2 = NeuronGroup(10, 'y:1') SG1 = G1[2:5] SG2 = G2[4:9] S = Synapses(G1, G2, 'w:1') S.connect() # Note that "j" is not ambiguous, because the equivalent in the target group # is called "i" (this previously raised a warning) tests = [ ((SG1, slice(None)), 'i', 1), ((SG1, slice(None)), 'i + N_pre', 2), ((SG1, slice(None)), 'N_pre', 1), ((slice(None), SG2), 'j', 0), ((slice(None), SG2), 'N_post', 1), ((slice(None), SG2), 'N', 1), ((SG1, SG2), 'i', 1), ((SG1, SG2), 'i + j', 1), ((SG1, SG2), 'N_pre', 1), ((SG1, SG2), 'j', 0), ((SG1, SG2), 'N_post', 1), ((SG1, SG2), 'N', 1), # These should not raise a warning ((SG1, SG2), 'w', 0), ((SG1, SG2), 'x_pre', 0), ((SG1, SG2), 'y_post', 0), ((SG1, SG2), 'y', 0) ] for item, value, n_warnings in tests: with catch_logs() as l: S.w.__setitem__(item, value) assert len(l) == n_warnings, 'expected %d, got %d warnings' % ( n_warnings, len(l)) assert all([ entry[1].endswith('ambiguous_string_expression') for entry in l ])
def test_rate_monitor_smoothed_rate(): # Test the filter response by having a single spiking neuron G = SpikeGeneratorGroup(1, [0], [1]*ms) r_mon = PopulationRateMonitor(G) run(3*ms) index = int(np.round(1*ms/defaultclock.dt)) except_index = np.array([idx for idx in range(len(r_mon.rate)) if idx != index]) assert_array_equal(r_mon.rate[except_index], 0*Hz) assert_allclose(r_mon.rate[index], 1/defaultclock.dt) ### Flat window # Using a flat window of size = dt should not change anything assert_allclose(r_mon.rate, r_mon.smooth_rate(window='flat', width=defaultclock.dt)) smoothed = r_mon.smooth_rate(window='flat', width=5*defaultclock.dt) assert_array_equal(smoothed[:index-2], 0*Hz) assert_array_equal(smoothed[index+3:], 0*Hz) assert_allclose(smoothed[index-2:index+3], 0.2/defaultclock.dt) with catch_logs(log_level=logging.INFO): smoothed2 = r_mon.smooth_rate(window='flat', width=5.4*defaultclock.dt) assert_array_equal(smoothed, smoothed2) ### Gaussian window width = 5*defaultclock.dt smoothed = r_mon.smooth_rate(window='gaussian', width=width) # 0 outside of window assert_array_equal(smoothed[:index-10], 0*Hz) assert_array_equal(smoothed[index+11:], 0*Hz) # Gaussian around the spike gaussian = np.exp(-(r_mon.t[index-10:index+11] - 1*ms)**2/(2*width**2)) gaussian /= sum(gaussian) assert_allclose(smoothed[index-10:index+11], 1/defaultclock.dt*gaussian) ### Arbitrary window window = np.ones(5) smoothed_flat = r_mon.smooth_rate(window='flat', width=5*defaultclock.dt) smoothed_custom = r_mon.smooth_rate(window=window) assert_allclose(smoothed_flat, smoothed_custom)
def test_locally_constant_check(): default_dt = defaultclock.dt # The linear state update can handle additive time-dependent functions # (e.g. a TimedArray) but only if it can be safely assumed that the function # is constant over a single time check ta0 = TimedArray(np.array([1]), dt=default_dt) # ok ta1 = TimedArray(np.array([1]), dt=2 * default_dt) # ok ta2 = TimedArray(np.array([1]), dt=default_dt / 2) # not ok ta3 = TimedArray(np.array([1]), dt=default_dt * 1.5) # not ok for ta_func, ok in zip([ta0, ta1, ta2, ta3], [True, True, False, False]): # additive G = NeuronGroup(1, 'dv/dt = -v/(10*ms) + ta(t)*Hz : 1', method='exact', namespace={'ta': ta_func}) net = Network(G) if ok: # This should work net.run(0 * ms) else: # This should not with catch_logs(): with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # multiplicative G = NeuronGroup(1, 'dv/dt = -v*ta(t)/(10*ms) : 1', method='exact', namespace={'ta': ta_func}) net = Network(G) if ok: # This should work net.run(0 * ms) else: # This should not with catch_logs(): with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # If the argument is more than just "t", we cannot guarantee that it is # actually locally constant G = NeuronGroup(1, 'dv/dt = -v*ta(t/2.0)/(10*ms) : 1', method='exact', namespace={'ta': ta0}) net = Network(G) with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # Arbitrary functions are not constant over a time step G = NeuronGroup(1, 'dv/dt = -v/(10*ms) + sin(2*pi*100*Hz*t)*Hz : 1', method='exact') net = Network(G) with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # Stateful functions aren't either G = NeuronGroup(1, 'dv/dt = -v/(10*ms) + rand()*Hz : 1', method='exact') net = Network(G) with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # Neither is "t" itself G = NeuronGroup(1, 'dv/dt = -v/(10*ms) + t/second**2 : 1', method='exact') net = Network(G) with pytest.raises(angelaObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # But if the argument is not referring to t, all should be well G = NeuronGroup(1, 'dv/dt = -v/(10*ms) + sin(2*pi*100*Hz*5*second)*Hz : 1', method='exact') net = Network(G) net.run(0 * ms)
def test_determination(): ''' Test the determination of suitable state updaters. ''' # To save some typing apply_stateupdater = StateUpdateMethod.apply_stateupdater eqs = Equations('dv/dt = -v / (10*ms) : 1') # Just make sure that state updaters know about the two state variables variables = {'v': Variable(name='v'), 'w': Variable(name='w')} # all methods should work for these equations. # First, specify them explicitly (using the object) for integrator in ( linear, euler, exponential_euler, #TODO: Removed "independent" here due to the issue in sympy 0.7.4 rk2, rk4, heun, milstein): with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=integrator) assert len(logs) == 0, 'Got %d unexpected warnings: %s' % ( len(logs), str([l[2] for l in logs])) # Equation with multiplicative noise, only milstein and heun should work eqs = Equations('dv/dt = -v / (10*ms) + v*xi*second**-.5: 1') for integrator in (linear, independent, euler, exponential_euler, rk2, rk4): with pytest.raises(UnsupportedEquationsException): apply_stateupdater(eqs, variables, integrator) for integrator in (heun, milstein): with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=integrator) assert len(logs) == 0, 'Got %d unexpected warnings: %s' % ( len(logs), str([l[2] for l in logs])) # Arbitrary functions (converting equations into abstract code) should # always work my_stateupdater = lambda eqs, vars, options: 'x_new = x' with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=my_stateupdater) # No warning here assert len(logs) == 0 # Specification with names eqs = Equations('dv/dt = -v / (10*ms) : 1') for name, integrator in [ ('exact', exact), ('linear', linear), ('euler', euler), #('independent', independent), #TODO: Removed "independent" here due to the issue in sympy 0.7.4 ('exponential_euler', exponential_euler), ('rk2', rk2), ('rk4', rk4), ('heun', heun), ('milstein', milstein) ]: with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=name) # No warning here assert len(logs) == 0 # Now all except heun and milstein should refuse to work eqs = Equations('dv/dt = -v / (10*ms) + v*xi*second**-.5: 1') for name in [ 'linear', 'exact', 'independent', 'euler', 'exponential_euler', 'rk2', 'rk4' ]: with pytest.raises(UnsupportedEquationsException): apply_stateupdater(eqs, variables, method=name) # milstein should work with catch_logs() as logs: apply_stateupdater(eqs, variables, method='milstein') assert len(logs) == 0 # heun should work with catch_logs() as logs: apply_stateupdater(eqs, variables, method='heun') assert len(logs) == 0 # non-existing name with pytest.raises(ValueError): apply_stateupdater(eqs, variables, method='does_not_exist') # Automatic state updater choice should return linear for linear equations, # euler for non-linear, non-stochastic equations and equations with # additive noise, heun for equations with multiplicative noise # Because it is somewhat fragile, the "independent" state updater is not # included in this list all_methods = [ 'linear', 'exact', 'exponential_euler', 'euler', 'heun', 'milstein' ] eqs = Equations('dv/dt = -v / (10*ms) : 1') with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert ('linear' in logs[0][2]) or ('exact' in logs[0][2]) # This is conditionally linear eqs = Equations('''dv/dt = -(v + w**2)/ (10*ms) : 1 dw/dt = -w/ (10*ms) : 1''') with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert 'exponential_euler' in logs[0][2] # # Do not test for now # eqs = Equations('dv/dt = sin(t) / (10*ms) : 1') # assert apply_stateupdater(eqs, variables) is independent eqs = Equations('dv/dt = -sqrt(v) / (10*ms) : 1') with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'euler'" in logs[0][2] eqs = Equations('dv/dt = -v / (10*ms) + 0.1*second**-.5*xi: 1') with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'euler'" in logs[0][2] eqs = Equations('dv/dt = -v / (10*ms) + v*0.1*second**-.5*xi: 1') with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'heun'" in logs[0][2]