Skip to content

Commit

Permalink
Merge pull request #44 from JesusEV/test/history-cleaning
Browse files Browse the repository at this point in the history
Validate e-prop history cleaning
  • Loading branch information
JesusEV authored Jan 14, 2025
2 parents 3a1c2c9 + 28912a0 commit 51ea23e
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 0 deletions.
1 change: 1 addition & 0 deletions models/eprop_iaf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf::get_eprop_history_duration );
insert_( names::learning_signal, &eprop_iaf::get_learning_signal_ );
insert_( names::surrogate_gradient, &eprop_iaf::get_surrogate_gradient_ );
insert_( names::V_m, &eprop_iaf::get_v_m_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_iaf_adapt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf_adapt >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf_adapt::get_eprop_history_duration );
insert_( names::adaptation, &eprop_iaf_adapt::get_adaptation_ );
insert_( names::V_th_adapt, &eprop_iaf_adapt::get_v_th_adapt_ );
insert_( names::learning_signal, &eprop_iaf_adapt::get_learning_signal_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_iaf_adapt_bsshslm_2020.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf_adapt_bsshslm_2020 >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf_adapt_bsshslm_2020::get_eprop_history_duration );
insert_( names::adaptation, &eprop_iaf_adapt_bsshslm_2020::get_adaptation_ );
insert_( names::V_th_adapt, &eprop_iaf_adapt_bsshslm_2020::get_v_th_adapt_ );
insert_( names::learning_signal, &eprop_iaf_adapt_bsshslm_2020::get_learning_signal_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_iaf_bsshslm_2020.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf_bsshslm_2020 >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf_bsshslm_2020::get_eprop_history_duration );
insert_( names::learning_signal, &eprop_iaf_bsshslm_2020::get_learning_signal_ );
insert_( names::surrogate_gradient, &eprop_iaf_bsshslm_2020::get_surrogate_gradient_ );
insert_( names::V_m, &eprop_iaf_bsshslm_2020::get_v_m_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_iaf_psc_delta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf_psc_delta >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf_psc_delta::get_eprop_history_duration );
insert_( names::V_m, &eprop_iaf_psc_delta::get_v_m_ );
insert_( names::learning_signal, &eprop_iaf_psc_delta::get_learning_signal_ );
insert_( names::surrogate_gradient, &eprop_iaf_psc_delta::get_surrogate_gradient_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_iaf_psc_delta_adapt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_iaf_psc_delta_adapt >::create()
{
insert_( names::eprop_history_duration, &eprop_iaf_psc_delta_adapt::get_eprop_history_duration );
insert_( names::V_m, &eprop_iaf_psc_delta_adapt::get_v_m_ );
insert_( names::adaptation, &eprop_iaf_psc_delta_adapt::get_adaptation_ );
insert_( names::V_th_adapt, &eprop_iaf_psc_delta_adapt::get_v_th_adapt_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_readout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_readout >::create()
{
insert_( names::eprop_history_duration, &eprop_readout::get_eprop_history_duration );
insert_( names::error_signal, &eprop_readout::get_error_signal_ );
insert_( names::readout_signal, &eprop_readout::get_readout_signal_ );
insert_( names::target_signal, &eprop_readout::get_target_signal_ );
Expand Down
1 change: 1 addition & 0 deletions models/eprop_readout_bsshslm_2020.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ template <>
void
RecordablesMap< eprop_readout_bsshslm_2020 >::create()
{
insert_( names::eprop_history_duration, &eprop_readout_bsshslm_2020::get_eprop_history_duration );
insert_( names::error_signal, &eprop_readout_bsshslm_2020::get_error_signal_ );
insert_( names::readout_signal, &eprop_readout_bsshslm_2020::get_readout_signal_ );
insert_( names::readout_signal_unnorm, &eprop_readout_bsshslm_2020::get_readout_signal_unnorm_ );
Expand Down
8 changes: 8 additions & 0 deletions nestkernel/eprop_archiving_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ class EpropArchivingNode : public Node
*/
void erase_used_eprop_history( const long eprop_isi_trace_cutoff );

/**
* @brief Retrieves eprop history size.
*
* Retrieves the size of the eprop history buffer.
*/

double get_eprop_history_duration() const;

protected:
//! Number of incoming eprop synapses
size_t eprop_indegree_;
Expand Down
7 changes: 7 additions & 0 deletions nestkernel/eprop_archiving_node_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ EpropArchivingNode< HistEntryT >::erase_used_eprop_history( const long eprop_isi
get_eprop_history( std::numeric_limits< long >::min() ), get_eprop_history( update_history_.begin()->t_ - 1 ) );
}

template < typename HistEntryT >
inline double
EpropArchivingNode< HistEntryT >::get_eprop_history_duration() const
{
return Time::get_resolution().get_ms() * eprop_history_.size();
}

} // namespace nest

#endif // EPROP_ARCHIVING_NODE_IMPL_H
1 change: 1 addition & 0 deletions nestkernel/nest_names.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const Name elements( "elements" );
const Name elementsize( "elementsize" );
const Name ellipsoidal( "ellipsoidal" );
const Name elliptical( "elliptical" );
const Name eprop_history_duration( "eprop_history_duration" );
const Name eprop_isi_trace_cutoff( "eprop_isi_trace_cutoff" );
const Name eprop_learning_window( "eprop_learning_window" );
const Name eprop_reset_neurons_on_update( "eprop_reset_neurons_on_update" );
Expand Down
1 change: 1 addition & 0 deletions nestkernel/nest_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ extern const Name elements;
extern const Name elementsize;
extern const Name ellipsoidal;
extern const Name elliptical;
extern const Name eprop_history_duration;
extern const Name eprop_isi_trace_cutoff;
extern const Name eprop_learning_window;
extern const Name eprop_reset_neurons_on_update;
Expand Down
99 changes: 99 additions & 0 deletions testsuite/pytests/test_eprop_bsshslm_2020_plasticity.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,3 +808,102 @@ def generate_evidence_accumulation_input_output(batch_size, n_in, steps, input):
if batch_size == 1:
assert np.allclose(loss, loss_tf_reference, rtol=1e-6)
assert np.allclose(loss, loss_nest_reference, rtol=1e-8)


@pytest.mark.parametrize(
"neuron_model,eprop_history_duration_reference",
[
("eprop_iaf_bsshslm_2020", np.hstack([np.arange(x, y) for x, y in [[1, 3], [1, 61], [21, 61], [41, 48]]])),
(
"eprop_readout_bsshslm_2020",
np.hstack([np.arange(x, y) for x, y in [[1, 4], [2, 22], [21, 61], [21, 61], [41, 47]]]),
),
],
)
def test_eprop_history_cleaning(neuron_model, eprop_history_duration_reference):
"""
Test the e-prop archiving mechanism's cleaning process by ensuring that the length of the `eprop_history`
buffer matches the expected values based on a given input firing pattern. These reference length values
were obtained from a simulation with the verified NEST e-prop implementation run with Linux 5.8.7-1-default,
Python v3.12.5, Numpy v2.0.1, and NEST@3a1c2c914.
"""

# Define timing of task

duration = {"step": 1.0, "sequence": 20.0}

# Set up simulation

params_setup = {
"print_time": False,
"resolution": duration["step"],
"eprop_update_interval": duration["sequence"],
"total_num_virtual_procs": 1,
}

nest.ResetKernel()
nest.set(**params_setup)

# Create neurons

gen_spk_in = nest.Create("spike_generator", 3)
nrns_in = nest.Create("parrot_neuron", 3)
nrns_rec = nest.Create(neuron_model, 1)

# Create recorders

params_mm_rec = {
"interval": duration["step"],
"record_from": ["eprop_history_duration"],
}

mm_rec = nest.Create("multimeter", params_mm_rec)

# Create connections

params_conn_all_to_all = {"rule": "all_to_all", "allow_autapses": False}
params_conn_one_to_one = {"rule": "one_to_one"}

params_syn_base = {
"synapse_model": "eprop_synapse_bsshslm_2020",
"delay": duration["step"],
"weight": 1.0,
}

params_syn_static = {
"synapse_model": "static_synapse",
"delay": duration["step"],
}

params_syn_in = params_syn_base.copy()

nest.Connect(gen_spk_in, nrns_in, params_conn_one_to_one, params_syn_static)
nest.Connect(nrns_in, nrns_rec, params_conn_all_to_all, params_syn_in)
nest.Connect(mm_rec, nrns_rec, params_conn_all_to_all, params_syn_static)

# Create input

input_spike_times = [
[10.0, 20.0, 50.0, 60.0],
[10.0, 30.0, 50.0, 90.0],
[40.0, 60.0],
]

params_gen_spk_in = [{"spike_times": spike_times} for spike_times in input_spike_times]

nest.SetStatus(gen_spk_in, params_gen_spk_in)

# Simulate

nest.Simulate(110.0)

# Evaluate training error

events_mm_rec = mm_rec.get("events")

eprop_history_duration = events_mm_rec["eprop_history_duration"]
senders = events_mm_rec["senders"]

eprop_history_duration = np.array([eprop_history_duration[senders == i] for i in set(senders)])[0]

assert np.allclose(eprop_history_duration, eprop_history_duration_reference, rtol=1e-8)
108 changes: 108 additions & 0 deletions testsuite/pytests/test_eprop_plasticity.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,3 +555,111 @@ def test_eprop_surrogate_gradients(surrogate_gradient_type, surrogate_gradient_r
surrogate_gradient = events_mm_rec["surrogate_gradient"][-5:]

assert np.allclose(surrogate_gradient, surrogate_gradient_reference, rtol=1e-8)


@pytest.mark.parametrize(
"neuron_model,eprop_isi_trace_cutoff,eprop_history_duration_reference",
[
(
"eprop_iaf",
5.0,
np.hstack(
[
np.arange(x, y)
for x, y in [[1, 12], [6, 16], [11, 21], [16, 26], [21, 31], [17, 27], [12, 42], [12, 30]]
]
),
),
("eprop_iaf", 100000.0, np.hstack([np.arange(x, y) for x, y in [[1, 52], [33, 43], [23, 53], [43, 61]]])),
("eprop_readout", 100000.0, np.hstack([np.arange(x, y) for x, y in [[1, 52], [33, 43], [23, 53], [43, 61]]])),
],
)
def test_eprop_history_cleaning(neuron_model, eprop_isi_trace_cutoff, eprop_history_duration_reference):
"""
Test the e-prop archiving mechanism's cleaning process by ensuring that the length of the `eprop_history`
buffer matches the expected values based on a given input firing pattern. These reference length values
were obtained from a simulation with the verified NEST e-prop implementation run with Linux 5.8.7-1-default,
Python v3.12.5, Numpy v2.0.1, and NEST@3a1c2c914.
"""

# Define timing of task

duration = {"step": 1.0}

# Set up simulation

params_setup = {
"print_time": False,
"resolution": duration["step"],
"total_num_virtual_procs": 1,
}

nest.ResetKernel()
nest.set(**params_setup)

# Create neurons

params_nrn_rec = {
"eprop_isi_trace_cutoff": eprop_isi_trace_cutoff,
}

gen_spk_in = nest.Create("spike_generator", 3)
nrns_in = nest.Create("parrot_neuron", 3)
nrns_rec = nest.Create(neuron_model, 1, params_nrn_rec)

# Create recorders

params_mm_rec = {
"interval": duration["step"],
"record_from": ["eprop_history_duration"],
}

mm_rec = nest.Create("multimeter", params_mm_rec)

# Create connections

params_conn_all_to_all = {"rule": "all_to_all", "allow_autapses": False}
params_conn_one_to_one = {"rule": "one_to_one"}

params_syn_base = {
"synapse_model": "eprop_synapse",
"delay": duration["step"],
"weight": 1.0,
}

params_syn_static = {
"synapse_model": "static_synapse",
"delay": duration["step"],
}

params_syn_in = params_syn_base.copy()

nest.Connect(gen_spk_in, nrns_in, params_conn_one_to_one, params_syn_static)
nest.Connect(nrns_in, nrns_rec, params_conn_all_to_all, params_syn_in)
nest.Connect(mm_rec, nrns_rec, params_conn_all_to_all, params_syn_static)

# Create input

input_spike_times = [
[10.0, 20.0, 50.0, 60.0],
[10.0, 30.0, 50.0, 90.0],
[40.0, 60.0],
]

params_gen_spk_in = [{"spike_times": spike_times} for spike_times in input_spike_times]

nest.SetStatus(gen_spk_in, params_gen_spk_in)

# Simulate

nest.Simulate(110.0)

# Evaluate eprop history size

events_mm_rec = mm_rec.get("events")

eprop_history_duration = events_mm_rec["eprop_history_duration"]
senders = events_mm_rec["senders"]
eprop_history_duration = np.array([eprop_history_duration[senders == i] for i in set(senders)])[0]

assert np.allclose(eprop_history_duration, eprop_history_duration_reference, rtol=1e-8)

0 comments on commit 51ea23e

Please sign in to comment.