diff --git a/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_mfvn_mfvn/Makefile b/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_mfvn_mfvn/Makefile new file mode 100644 index 00000000000..2613796a29d --- /dev/null +++ b/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_mfvn_mfvn/Makefile @@ -0,0 +1,28 @@ +# Copyright (c) 2017-2021 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +APP = $(notdir $(CURDIR)) + +NEURON_MODEL_H = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.h +INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_conductance.h +NEURON_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_standard.h +THRESHOLD_TYPE_H = $(NEURON_DIR)/neuron/threshold_types/threshold_type_static.h +SYNAPSE_TYPE_H = $(NEURON_DIR)/neuron/synapse_types/synapse_types_exponential_impl.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_mfvn_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.h + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_pfpc_pfpc/Makefile b/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_pfpc_pfpc/Makefile new file mode 100644 index 00000000000..eec66be7dbb --- /dev/null +++ b/neural_modelling/makefiles/neuron/IF_cond_exp_stdp_mad_pfpc_pfpc/Makefile @@ -0,0 +1,28 @@ +# Copyright (c) 2017-2021 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +APP = $(notdir $(CURDIR)) + +NEURON_MODEL_H = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.h +INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_conductance.h +NEURON_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_standard.h +THRESHOLD_TYPE_H = $(NEURON_DIR)/neuron/threshold_types/threshold_type_static.h +SYNAPSE_TYPE_H = $(NEURON_DIR)/neuron/synapse_types/synapse_types_exponential_impl.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_pfpc_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.h + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/synapse_only/Makefile b/neural_modelling/makefiles/synapse_only/Makefile index 1eb4c92a1e9..7d377fec972 100644 --- a/neural_modelling/makefiles/synapse_only/Makefile +++ b/neural_modelling/makefiles/synapse_only/Makefile @@ -25,7 +25,9 @@ MODELS = synapses\ synapses_stdp_mad_pair_additive_structural_random_distance_weight\ synapses_stdp_mad_pair_additive_structural_last_neuron_distance_weight\ synapses_stdp_mad_nearest_pair_additive_structural_random_distance_weight\ - synapses_stdp_izhikevich_neuromodulation_pair_additive + synapses_stdp_izhikevich_neuromodulation_pair_additive\ + synapses_stdp_mad_mfvn_mfvn\ + synapses_stdp_mad_pfpc_pfpc ifneq ($(SPYNNAKER_DEBUG), DEBUG) MODELS += synapses_stdp_izhikevich_neuromodulation_pair_multiplicative\ diff --git a/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_mfvn_mfvn/Makefile b/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_mfvn_mfvn/Makefile new file mode 100644 index 00000000000..48c20e6a2ef --- /dev/null +++ b/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_mfvn_mfvn/Makefile @@ -0,0 +1,23 @@ +# Copyright (c) 2017-2019 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +APP = $(notdir $(CURDIR)) + +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_mfvn_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.h + +include ../synapse_build.mk diff --git a/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_pfpc_pfpc/Makefile b/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_pfpc_pfpc/Makefile new file mode 100644 index 00000000000..e6bf26ff757 --- /dev/null +++ b/neural_modelling/makefiles/synapse_only/synapses_stdp_mad_pfpc_pfpc/Makefile @@ -0,0 +1,23 @@ +# Copyright (c) 2017-2019 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +APP = $(notdir $(CURDIR)) + +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_pfpc_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.h + +include ../synapse_build.mk diff --git a/neural_modelling/src/common/in_spikes.h b/neural_modelling/src/common/in_spikes.h index b0c204c3c79..886247cf71c 100644 --- a/neural_modelling/src/common/in_spikes.h +++ b/neural_modelling/src/common/in_spikes.h @@ -135,4 +135,16 @@ static inline void in_spikes_clear(void) { static inline spike_t in_spikes_value_at_index(uint32_t index) { return circular_buffer_value_at_index(buffer, index); } + +#if LOG_LEVEL >= LOG_DEBUG +// FLUSH SPIKES +static inline uint32_t in_spikes_flush_buffer(void) { + + uint32_t num_spikes_left = circular_buffer_size(buffer); + circular_buffer_clear(buffer); + + return num_spikes_left; +} +#endif + #endif // _IN_SPIKES_H_ diff --git a/neural_modelling/src/neuron/c_main.c b/neural_modelling/src/neuron/c_main.c index 40a98fe8085..25e381820c1 100644 --- a/neural_modelling/src/neuron/c_main.c +++ b/neural_modelling/src/neuron/c_main.c @@ -39,6 +39,10 @@ #include "profile_tags.h" #include "spike_processing.h" +// FLUSH SPIKES +//bool timer_callback_active = false; +//extern volatile bool dma_busy; + //! The combined provenance from synapses and neurons struct combined_provenance { struct neuron_provenance neuron_provenance; @@ -179,7 +183,12 @@ void background_callback(uint timer_count, uint local_time) { //! executed since start of simulation //! \param[in] unused: unused parameter kept for API consistency void timer_callback(uint timer_count, UNUSED uint unused) { - // Disable interrupts to stop DMAs and MC getting in the way of this bit +// // Get number of spikes in last tick, and reset spike counter +// spike_processing_get_and_reset_spikes_this_tick(); +// spike_processing_get_and_reset_dmas_this_tick(); +// spike_processing_get_and_reset_pipeline_restarts_this_tick(); + + // Disable interrupts to stop DMAs and MC getting in the way of this bit uint32_t state = spin1_int_disable(); // Increment time step diff --git a/neural_modelling/src/neuron/c_main_synapses.c b/neural_modelling/src/neuron/c_main_synapses.c index 6ce1e3a7542..964e9045bed 100644 --- a/neural_modelling/src/neuron/c_main_synapses.c +++ b/neural_modelling/src/neuron/c_main_synapses.c @@ -35,9 +35,14 @@ #include "c_main_synapse_common.h" #include "c_main_common.h" #include "spike_processing_fast.h" +#include "spike_profiling.h" #include "structural_plasticity/synaptogenesis_dynamics.h" #include +// FLUSH SPIKES +bool timer_callback_active = false; +extern volatile bool dma_busy; + //! values for the priority for each callback typedef enum callback_priorities { MC = -1, DMA = -2, TIMER = 0, SDP = 0 @@ -90,6 +95,12 @@ const struct synapse_regions SYNAPSE_REGIONS = { .bitfield_filter = BIT_FIELD_FILTER_REGION }; +//! spike profiling +struct spike_holder_t spike_counter; +struct spike_holder_t spike_cache; +struct spike_holder_t spike_counter_inh; +struct spike_holder_t spike_cache_inh; + //! The current timer tick value. // the timer tick callback returning the same value. uint32_t time; @@ -129,7 +140,19 @@ void resume_callback(void) { //! \param[in] unused0: unused //! \param[in] unused1: unused void timer_callback(UNUSED uint unused0, UNUSED uint unused1) { - time++; + // Get number of spikes in last tick, and reset spike counter + spike_processing_get_and_reset_spikes_this_tick(); + spike_processing_get_and_reset_dmas_this_tick(); + spike_processing_get_and_reset_pipeline_restarts_this_tick(); + + // cache and flush spike counters + // functions in file neural_modelling/src/neuron/spike_profiling.h + spike_profiling_cache_and_flush_spike_holder(&spike_counter, + &spike_cache); + spike_profiling_cache_and_flush_spike_holder(&spike_counter_inh, + &spike_cache_inh); + + time++; if (simulation_is_finished()) { // Enter pause and resume state to avoid another tick simulation_handle_pause_resume(resume_callback); diff --git a/neural_modelling/src/neuron/models/neuron_model_lif_impl.h b/neural_modelling/src/neuron/models/neuron_model_lif_impl.h index 80a249cd8f3..68cfd379d10 100644 --- a/neural_modelling/src/neuron/models/neuron_model_lif_impl.h +++ b/neural_modelling/src/neuron/models/neuron_model_lif_impl.h @@ -189,20 +189,20 @@ static inline state_t neuron_model_get_membrane_voltage(const neuron_t *neuron) } static inline void neuron_model_print_state_variables(const neuron_t *neuron) { - log_info("V membrane = %11.4k mv", neuron->V_membrane); - log_info("Refract timer = %u timesteps", neuron->refract_timer); + log_debug("V membrane = %11.4k mv", neuron->V_membrane); + log_debug("Refract timer = %u timesteps", neuron->refract_timer); } static inline void neuron_model_print_parameters(const neuron_t *neuron) { - log_info("V reset = %11.4k mv", neuron->V_reset); - log_info("V rest = %11.4k mv", neuron->V_rest); + log_debug("V reset = %11.4k mv", neuron->V_reset); + log_debug("V rest = %11.4k mv", neuron->V_rest); - log_info("I offset = %11.4k nA", neuron->I_offset); - log_info("R membrane = %11.4k Mohm", neuron->R_membrane); + log_debug("I offset = %11.4k nA", neuron->I_offset); + log_debug("R membrane = %11.4k Mohm", neuron->R_membrane); - log_info("exp(-ms/(RC)) = %11.4k [.]", neuron->exp_TC); + log_debug("exp(-ms/(RC)) = %11.4k [.]", neuron->exp_TC); - log_info("T refract = %u timesteps", neuron->T_refract); + log_debug("T refract = %u timesteps", neuron->T_refract); } diff --git a/neural_modelling/src/neuron/plasticity/stdp/maths.h b/neural_modelling/src/neuron/plasticity/stdp/maths.h index 5e0a844c7e7..2fe842971bb 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/maths.h +++ b/neural_modelling/src/neuron/plasticity/stdp/maths.h @@ -58,6 +58,7 @@ static inline int16_lut *maths_copy_int16_lut(address_t *address) { int16_lut *sdram_lut = (int16_lut *) *address; uint32_t size = sizeof(int16_lut) + (sdram_lut->size * sizeof(int16_t)); int16_lut *lut = spin1_malloc(size); + log_debug("lut size %d sdram_lut size %d", size, sdram_lut->size); if (lut == NULL) { log_error("Not enough space to allocate LUT. Try reducing the timestep," " the number of neurons per core, or the tau value; size = %u", size); @@ -77,7 +78,7 @@ static inline int16_lut *maths_copy_int16_lut(address_t *address) { //! \param[in] lut: The lookup table (result of maths_copy_int16_lut()) //! \return The value from the LUT, or zero if out of range static inline int32_t maths_lut_exponential_decay( - uint32_t time, const int16_lut *lut) { + const uint32_t time, const int16_lut *lut) { // Calculate lut index uint32_t lut_index = time >> lut->shift; @@ -85,6 +86,20 @@ static inline int32_t maths_lut_exponential_decay( return (lut_index < lut->size) ? lut->values[lut_index] : 0; } +//! \brief Get value from lookup table, time shifted +//! \param[in] time: The time that we are mapping +//! \param[in] time_shift: The time shift value +//! \param[in] lut: The lookup table (result of maths_copy_int16_lut()) +//! \return The value from the LUT, or zero if out of range +static inline int32_t maths_lut_exponential_decay_time_shifted( + const uint32_t time, const uint32_t time_shift, const int16_lut *lut) { + // Calculate lut index + uint32_t lut_index = (time >> lut->shift) >> time_shift; + + // Return value from LUT + return (lut_index < lut->size) ? lut->values[lut_index] : 0; +} + //! \brief Clamp to fit in number of bits //! \param[in] x: The value to clamp //! \param[in] shift: Width of the field to clamp the value to fit in diff --git a/neural_modelling/src/neuron/plasticity/stdp/post_events.h b/neural_modelling/src/neuron/plasticity/stdp/post_events.h index 172741394bc..98cf280f88e 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/post_events.h +++ b/neural_modelling/src/neuron/plasticity/stdp/post_events.h @@ -84,6 +84,7 @@ static inline post_event_history_t *post_events_init_buffers( uint32_t n_neurons) { post_event_history_t *post_event_history = spin1_malloc(n_neurons * sizeof(post_event_history_t)); + // Check allocations succeeded if (post_event_history == NULL) { log_error("Unable to allocate global STDP structures - Out of DTCM: Try " @@ -111,6 +112,7 @@ static inline post_event_history_t *post_events_init_buffers( static inline post_event_window_t post_events_get_window_delayed( const post_event_history_t *events, uint32_t begin_time, uint32_t end_time) { + // Start at end event - beyond end of post-event history const uint32_t count = events->count_minus_one + 1; const uint32_t *end_event_time = events->times + count; diff --git a/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h b/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h index 3799b60d931..bdfaa3fc2e6 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h +++ b/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h @@ -37,6 +37,8 @@ //! \return The product #define STDP_FIXED_MUL_16X16(a, b) maths_fixed_mul16(a, b, STDP_FIXED_POINT) +//#define print_plasticity false + //! The amount of right shift required to take a weight from s1615 format //! to STDP_FIXED_POINT format (s4,11) #define S1615_TO_STDP_RIGHT_SHIFT 4 diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_common.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_common.h index 6d73a0105a8..cfff795ba9b 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_common.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_common.h @@ -66,15 +66,6 @@ //--------------------------------------- // Structures //--------------------------------------- -//! \brief The type of history data of pre-events -//! -//! This data is stored in SDRAM in the plastic part of the synaptic matrix -typedef struct { - //! The event time - uint32_t prev_time; - //! The event trace - pre_trace_t prev_trace; -} pre_event_history_t; //! The type of configuration parameters in SDRAM (written by host) typedef struct stdp_params { diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_izhikevich_neuromodulation.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_izhikevich_neuromodulation.c index 92e083e4767..5207b339257 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_izhikevich_neuromodulation.c +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_izhikevich_neuromodulation.c @@ -42,6 +42,16 @@ typedef struct nm_final_state_t { final_state_t final_state; } nm_final_state_t; +//! \brief The type of history data of pre-events +//! +//! This data is stored in SDRAM in the plastic part of the synaptic matrix +typedef struct { + //! The event time + uint32_t prev_time; + //! The event trace + pre_trace_t prev_trace; +} pre_event_history_t; + struct synapse_row_plastic_data_t { union { struct { diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_impl.c index 845c7274b3f..a0c76255e4f 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_impl.c +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_impl.c @@ -19,6 +19,16 @@ #include "post_events.h" #include "synapse_dynamics_stdp_common.h" +//! \brief The type of history data of pre-events +//! +//! This data is stored in SDRAM in the plastic part of the synaptic matrix +typedef struct { + //! The event time + uint32_t prev_time; + //! The event trace + pre_trace_t prev_trace; +} pre_event_history_t; + //! The format of the plastic data region of a synaptic row struct synapse_row_plastic_data_t { //! The pre-event history diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_mfvn_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_mfvn_impl.c new file mode 100644 index 00000000000..9501c26a604 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_mfvn_impl.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2017-2019 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! \file +//! \brief STDP core implementation for MF-VN STDP rules as defined by e.g. Luque et al 2019 +//! https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + +#include "post_events.h" +#include "synapse_dynamics_stdp_common.h" +//#include "stdp_typedefs.h" +//#include + +#define NUM_MF_SPIKES_TO_RECORD 16 + +typedef struct { + uint32_t num_recorded_mf_spikes_minus_one; + uint32_t mf_times[NUM_MF_SPIKES_TO_RECORD]; + post_trace_t traces[NUM_MF_SPIKES_TO_RECORD]; +} pre_event_history_t; + +//! The format of the plastic data region of a synaptic row +struct synapse_row_plastic_data_t { + //! The pre-event history + pre_event_history_t history; + //! The per-synapse information + plastic_synapse_t synapses[]; +}; + +//--------------------------------------- +// Synapse update loop +//--------------------------------------- +static inline final_state_t plasticity_update_synapse( + const uint32_t time, + const uint32_t last_pre_time, const pre_trace_t last_pre_trace, + const pre_trace_t new_pre_trace, uint32_t delay_dendritic, + const uint32_t delay_axonal, update_state_t current_state, + const post_event_history_t *post_event_history, + const pre_event_history_t *pre_event_history) { + + // Apply axonal delay to time of last presynaptic spike + const uint32_t delayed_last_pre_time = last_pre_time + delay_axonal; + + // Get the post-synaptic window of events to be processed + const uint32_t window_begin_time = (delayed_last_pre_time >= + delay_dendritic) ? (delayed_last_pre_time - delay_dendritic) : 0; + const uint32_t window_end_time = time + delay_axonal - delay_dendritic; + post_event_window_t post_window = post_events_get_window_delayed( + post_event_history, window_begin_time, window_end_time); + + log_debug("\tPerforming deferred synapse update at time:%u", time); + log_debug("\t\tbegin_time:%u, end_time:%u - prev_time:%u, num_events:%u", + window_begin_time, window_end_time, post_window.prev_time, + post_window.num_events); + + // Process events in post-synaptic window + while (post_window.num_events > 0) { + const uint32_t delayed_post_time = *post_window.next_time + + delay_dendritic; + + uint32_t mf_begin_time = (delayed_post_time < 255) ? 0 : (delayed_post_time - 255); + + post_event_window_t pre_window = post_events_get_window_delayed( + pre_event_history, mf_begin_time, delayed_post_time); + + while (pre_window.num_events > 0) { + + const uint32_t delayed_pre_time = *pre_window.next_time + + delay_dendritic; + + current_state = timing_apply_post_spike( + delayed_post_time, *post_window.next_trace, + (delayed_post_time - delayed_pre_time), + last_pre_trace, post_window.prev_time, post_window.prev_trace, + current_state); + + pre_window = post_events_next(pre_window); + } + + // Go onto next event + post_window = post_events_next(post_window); + } + + // ************** + // Now look forward to current MF spike from each PC spike. + const uint32_t fwd_window_begin_time = ((time-delay_dendritic) < 255) ? + 0 : ((time-delay_dendritic) - 255); + + post_event_window_t fwd_post_window = post_events_get_window_delayed( + post_event_history, fwd_window_begin_time, window_end_time); + + while (fwd_post_window.num_events > 0) { + const uint32_t delayed_mf_time = *fwd_post_window.next_time + delay_dendritic; + + // Some of these variables aren't used + current_state = timing_apply_post_spike( + delayed_mf_time, *fwd_post_window.next_trace, + (time+delay_dendritic - delayed_mf_time), + last_pre_trace, fwd_post_window.prev_time, fwd_post_window.prev_trace, + current_state); + + fwd_post_window = post_events_next(fwd_post_window); + } + + const uint32_t delayed_pre_time = time + delay_axonal; + + // Apply spike to state + // **NOTE** dendritic delay is subtracted already + current_state = timing_apply_pre_spike( + delayed_pre_time, new_pre_trace, delayed_last_pre_time, last_pre_trace, + post_window.prev_time, post_window.prev_trace, current_state); + + // Return final synaptic word and weight + return synapse_structure_get_final_state(current_state); +} + +//--------------------------------------- +static inline index_t sparse_axonal_delay(uint32_t x) { +#if 1 + // No axonal delay, ever + __use(x); + return 0; +#else + return (x >> synapse_delay_index_type_bits) & SYNAPSE_AXONAL_DELAY_MASK; +#endif +} + +bool synapse_dynamics_initialise( + address_t address, uint32_t n_neurons, uint32_t n_synapse_types, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + + if (!synapse_dynamics_stdp_init(&address, ¶ms, n_synapse_types, + ring_buffer_to_input_buffer_left_shifts)) { + return false; + } + + post_event_history = post_events_init_buffers(n_neurons); + if (post_event_history == NULL) { + return false; + } + + return true; +} + +//--------------------------------------- +static inline plastic_synapse_t process_plastic_synapse( + uint32_t control_word, uint32_t last_pre_time, pre_trace_t last_pre_trace, + pre_trace_t new_pre_trace, weight_t *ring_buffers, uint32_t time, + uint32_t colour_delay, plastic_synapse_t synapse, + pre_event_history_t *pre_event_history) { + fixed_stdp_synapse s = synapse_dynamics_stdp_get_fixed(control_word, time, + colour_delay); + + // Create update state from the plastic synaptic word + update_state_t current_state = synapse_structure_get_update_state( + synapse, s.type); + + // Update the synapse state + uint32_t post_delay = s.delay_dendritic; + + final_state_t final_state = plasticity_update_synapse( + time - colour_delay, last_pre_time, last_pre_trace, new_pre_trace, + post_delay, s.delay_axonal, current_state, + &post_event_history[s.index], pre_event_history); + + // Add weight to ring-buffer entry, but only if not too late + if (s.delay_axonal + s.delay_dendritic >= colour_delay) { + int32_t weight = synapse_structure_get_final_weight(final_state); + synapse_dynamics_stdp_update_ring_buffers(ring_buffers, s, weight); + } + + return synapse_structure_get_final_synaptic_word(final_state); +} + +bool synapse_dynamics_process_plastic_synapses( + synapse_row_plastic_data_t *plastic_region_address, + synapse_row_fixed_part_t *fixed_region, + weight_t *ring_buffers, uint32_t time, uint32_t colour_delay, + bool *write_back) { + + // Extract separate arrays of plastic synapses (from plastic region), + // Control words (from fixed region) and number of plastic synapses + plastic_synapse_t *plastic_words = plastic_region_address->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + size_t n_plastic_synapses = synapse_row_num_plastic_controls(fixed_region); + + num_plastic_pre_synaptic_events += n_plastic_synapses; + + // Get last pre-synaptic event from event history + const uint32_t recorded_spikes_minus_one = + plastic_region_address->history.num_recorded_mf_spikes_minus_one; + const uint32_t last_pre_time = + plastic_region_address->history.mf_times[recorded_spikes_minus_one]; // colour_delay? + + // no longer need to manage this trace + const pre_trace_t last_pre_trace = 0; + + // add pre spike to struct capturing pre synaptic event history + // NOTE: this uses the post_event_history_t handling code + post_events_add(time - colour_delay, &plastic_region_address->history, 0); + + // Update pre-synaptic trace + timing_add_pre_spike(time - colour_delay, last_pre_time, last_pre_trace); + + // Loop through plastic synapses + for (; n_plastic_synapses > 0; n_plastic_synapses--) { + + // Get next control word (auto incrementing) + uint32_t control_word = *control_words++; + + plastic_words[0] = process_plastic_synapse( + control_word, last_pre_time, last_pre_trace, + last_pre_trace, ring_buffers, time, colour_delay, + plastic_words[0], &plastic_region_address->history); + plastic_words++; + } + + *write_back = true; + return true; +} + +void synapse_dynamics_process_post_synaptic_event( + uint32_t time, index_t neuron_index) { + // Add post-event + post_event_history_t *history = &post_event_history[neuron_index]; + const uint32_t last_post_time = history->times[history->count_minus_one]; + const post_trace_t last_post_trace = + history->traces[history->count_minus_one]; + post_events_add(time, history, timing_add_post_spike(time, last_post_time, + last_post_trace)); +} + +bool synapse_dynamics_find_neuron( + uint32_t id, synaptic_row_t row, weight_t *weight, uint16_t *delay, + uint32_t *offset, uint32_t *synapse_type) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + const synapse_row_plastic_data_t *plastic_data = (void *) + synapse_row_plastic_region(row); + const plastic_synapse_t *plastic_words = plastic_data->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + const size_t n_plastic_synapses = synapse_row_num_plastic_controls(fixed_region); + + // Loop through plastic synapses + for (size_t plastic_synapse = n_plastic_synapses; plastic_synapse > 0; + plastic_synapse--) { + // Take the weight anyway as this updates the plastic words + *weight = synapse_structure_get_weight(*plastic_words++); + + // Check if index is the one I'm looking for + uint32_t control_word = *control_words++; + if (synapse_row_sparse_index(control_word, synapse_index_mask) == id) { + *offset = n_plastic_synapses - plastic_synapse; + *delay = synapse_row_sparse_delay(control_word, + synapse_type_index_bits, synapse_delay_mask); + *synapse_type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + return true; + } + } + + return false; +} + +bool synapse_dynamics_remove_neuron(uint32_t offset, synaptic_row_t row) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + synapse_row_plastic_data_t *plastic_data = (void *) + synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = plastic_data->synapses; + + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + // Delete weight at offset + plastic_words[offset] = plastic_words[plastic_synapse - 1]; + + // Delete control word at offset + control_words[offset] = control_words[plastic_synapse - 1]; + control_words[plastic_synapse - 1] = 0; + + // Decrement FP + fixed_region->num_plastic--; + return true; +} + +bool synapse_dynamics_add_neuron(uint32_t id, synaptic_row_t row, + weight_t weight, uint32_t delay, uint32_t type) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + synapse_row_plastic_data_t *plastic_data = synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = plastic_data->synapses; + plastic_synapse_t new_weight = synapse_structure_create_synapse(weight); + control_t new_control = control_conversion(id, delay, type); + + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + // Add weight at offset + plastic_words[plastic_synapse] = new_weight; + + // Add control word at offset + control_words[plastic_synapse] = new_control; + + // Increment FP + fixed_region->num_plastic++; + return true; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_pfpc_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_pfpc_impl.c new file mode 100644 index 00000000000..972d55fd14c --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_stdp_mad_pfpc_impl.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2017-2019 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! \file +//! \brief STDP implementation for MF-VN STDP rules as defined by e.g. Luque et al 2019 +//! https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + + +#include "post_events.h" +#include "synapse_dynamics_stdp_common.h" +// sPyNNaker neural modelling includes +#include +#include + +#define NUM_PF_SPIKES_TO_RECORD 16 + +typedef struct { + uint32_t num_recorded_pf_spikes_minus_one; + uint32_t pf_times[NUM_PF_SPIKES_TO_RECORD]; + post_trace_t traces[NUM_PF_SPIKES_TO_RECORD]; +} pre_event_history_t; + +//! The format of the plastic data region of a synaptic row +struct synapse_row_plastic_data_t { + //! The pre-event history + pre_event_history_t history; + //! The per-synapse information + plastic_synapse_t synapses[]; +}; + +//--------------------------------------- +// Synapse update loop +//--------------------------------------- +static inline final_state_t plasticity_update_synapse( + const uint32_t time, + const uint32_t last_pre_time, const pre_trace_t last_pre_trace, + const pre_trace_t new_pre_trace, uint32_t delay_dendritic, + const uint32_t delay_axonal, update_state_t current_state, + const post_event_history_t *post_event_history, + const pre_event_history_t *pre_event_history) { + + // Apply axonal delay to time of last presynaptic spike + const uint32_t delayed_last_pre_time = last_pre_time + delay_axonal; + + // Get the post-synaptic window of events to be processed + const uint32_t window_begin_time = (delayed_last_pre_time >= + delay_dendritic) ? (delayed_last_pre_time - delay_dendritic) : 0; + const uint32_t window_end_time = time + delay_axonal - delay_dendritic; + post_event_window_t post_window = post_events_get_window_delayed( + post_event_history, window_begin_time, window_end_time); + + // Process events in post-synaptic window + while (post_window.num_events > 0) { + const uint32_t delayed_post_time = *post_window.next_time + + delay_dendritic; + + uint32_t pf_begin_time = (delayed_post_time < 255) ? 0 : (delayed_post_time - 255); + + post_event_window_t pre_window = post_events_get_window_delayed( + pre_event_history, pf_begin_time, delayed_post_time); + + while (pre_window.num_events > 0) { + + const uint32_t delayed_pre_time = *pre_window.next_time + + delay_dendritic; + + current_state = timing_apply_post_spike( + delayed_post_time, *post_window.next_trace, + (delayed_post_time - delayed_pre_time), + last_pre_trace, post_window.prev_time, post_window.prev_trace, + current_state); + + pre_window = post_events_next(pre_window); + + } + + // Go onto next event + post_window = post_events_next(post_window); + } + + const uint32_t delayed_pre_time = time + delay_axonal; + + // Apply spike to state + // **NOTE** dendritic delay is subtracted already + current_state = timing_apply_pre_spike( + delayed_pre_time, new_pre_trace, delayed_last_pre_time, last_pre_trace, + post_window.prev_time, post_window.prev_trace, current_state); + + // Return final synaptic word and weight + return synapse_structure_get_final_state(current_state); +} + +//--------------------------------------- +// Synaptic row plastic-region implementation +//--------------------------------------- +static inline plastic_synapse_t* _plastic_synapses( + address_t plastic_region_address) { + const uint32_t pre_event_history_size_words = + sizeof(pre_event_history_t) / sizeof(uint32_t); + static_assert(pre_event_history_size_words * sizeof(uint32_t) + == sizeof(pre_event_history_t), + "Size of pre_event_history_t structure should be a multiple" + " of 32-bit words"); + + return (plastic_synapse_t*) + (&plastic_region_address[pre_event_history_size_words]); +} + +//--------------------------------------- +static inline pre_event_history_t *_plastic_event_history( + address_t plastic_region_address) { + return (pre_event_history_t*) (&plastic_region_address[0]); +} + +//--------------------------------------- +static inline index_t _sparse_axonal_delay(uint32_t x) { +#if 1 + // No axonal delay, ever + __use(x); + return 0; +#else + return (x >> synapse_delay_index_type_bits) & SYNAPSE_AXONAL_DELAY_MASK; +#endif +} + +bool synapse_dynamics_initialise( + address_t address, uint32_t n_neurons, uint32_t n_synapse_types, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + + if (!synapse_dynamics_stdp_init(&address, ¶ms, n_synapse_types, + ring_buffer_to_input_buffer_left_shifts)) { + return false; + } + + post_event_history = post_events_init_buffers(n_neurons); + if (post_event_history == NULL) { + return false; + } + + return true; +} + +//--------------------------------------- +static inline plastic_synapse_t process_plastic_synapse( + uint32_t control_word, uint32_t last_pre_time, pre_trace_t last_pre_trace, + pre_trace_t new_pre_trace, weight_t *ring_buffers, uint32_t time, + uint32_t colour_delay, plastic_synapse_t synapse, + pre_event_history_t *pre_event_history) { + + fixed_stdp_synapse s = synapse_dynamics_stdp_get_fixed(control_word, time, + colour_delay); + + // Create update state from the plastic synaptic word + update_state_t current_state = synapse_structure_get_update_state( + synapse, s.type); + + // Update the synapse state + uint32_t post_delay = s.delay_dendritic; + + final_state_t final_state = plasticity_update_synapse( + time - colour_delay, last_pre_time, last_pre_trace, new_pre_trace, + post_delay, s.delay_axonal, current_state, + &post_event_history[s.index], pre_event_history); + + // Add weight to ring-buffer entry, but only if not too late + if (s.delay_axonal + s.delay_dendritic >= colour_delay) { + int32_t weight = synapse_structure_get_final_weight(final_state); + synapse_dynamics_stdp_update_ring_buffers(ring_buffers, s, weight); + } + + return synapse_structure_get_final_synaptic_word(final_state); +} + +bool synapse_dynamics_process_plastic_synapses( + synapse_row_plastic_data_t *plastic_region_address, + synapse_row_fixed_part_t *fixed_region, + weight_t *ring_buffers, uint32_t time, uint32_t colour_delay, + bool *write_back) { + + // Extract separate arrays of plastic synapses (from plastic region), + // Control words (from fixed region) and number of plastic synapses + plastic_synapse_t *plastic_words = plastic_region_address->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + size_t n_plastic_synapses = synapse_row_num_plastic_controls(fixed_region); + + num_plastic_pre_synaptic_events += n_plastic_synapses; + + // Get last pre-synaptic event from event history + const uint32_t recorded_spikes_minus_one = + plastic_region_address->history.num_recorded_pf_spikes_minus_one; + const uint32_t last_pre_time = + plastic_region_address->history.pf_times[recorded_spikes_minus_one]; // colour_delay? + + // no longer need to manage this trace + const pre_trace_t last_pre_trace = 0; + + // add pre spike to struct capturing pre synaptic event history + // NOTE: this uses the post_event_history_t handling code + post_events_add(time - colour_delay, &plastic_region_address->history, 0); + + // Update pre-synaptic trace + timing_add_pre_spike(time - colour_delay, last_pre_time, last_pre_trace); + + // Loop through plastic synapses + for (; n_plastic_synapses > 0; n_plastic_synapses--) { + + // Get next control word (auto incrementing) + uint32_t control_word = *control_words++; + + plastic_words[0] = process_plastic_synapse( + control_word, last_pre_time, last_pre_trace, + last_pre_trace, ring_buffers, time, colour_delay, + plastic_words[0], &plastic_region_address->history); + plastic_words++; + } + + *write_back = true; + return true; +} + +void synapse_dynamics_process_post_synaptic_event( + uint32_t time, index_t neuron_index) { + // Add post-event + post_event_history_t *history = &post_event_history[neuron_index]; + const uint32_t last_post_time = history->times[history->count_minus_one]; + const post_trace_t last_post_trace = + history->traces[history->count_minus_one]; + post_events_add(time, history, timing_add_post_spike(time, last_post_time, + last_post_trace)); +} + +bool synapse_dynamics_find_neuron( + uint32_t id, synaptic_row_t row, weight_t *weight, uint16_t *delay, + uint32_t *offset, uint32_t *synapse_type) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + const synapse_row_plastic_data_t *plastic_data = (void *) + synapse_row_plastic_region(row); + const plastic_synapse_t *plastic_words = plastic_data->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + const size_t n_plastic_synapses = synapse_row_num_plastic_controls(fixed_region); + + // Loop through plastic synapses + for (size_t plastic_synapse = n_plastic_synapses; plastic_synapse > 0; + plastic_synapse--) { + // Take the weight anyway as this updates the plastic words + *weight = synapse_structure_get_weight(*plastic_words++); + + // Check if index is the one I'm looking for + uint32_t control_word = *control_words++; + if (synapse_row_sparse_index(control_word, synapse_index_mask) == id) { + *offset = n_plastic_synapses - plastic_synapse; + *delay = synapse_row_sparse_delay(control_word, + synapse_type_index_bits, synapse_delay_mask); + *synapse_type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + return true; + } + } + + return false; +} + +bool synapse_dynamics_remove_neuron(uint32_t offset, synaptic_row_t row) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + synapse_row_plastic_data_t *plastic_data = (void *) + synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = plastic_data->synapses; + + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + // Delete weight at offset + plastic_words[offset] = plastic_words[plastic_synapse - 1]; + + // Delete control word at offset + control_words[offset] = control_words[plastic_synapse - 1]; + control_words[plastic_synapse - 1] = 0; + + // Decrement FP + fixed_region->num_plastic--; + return true; +} + +bool synapse_dynamics_add_neuron(uint32_t id, synaptic_row_t row, + weight_t weight, uint32_t delay, uint32_t type) { + synapse_row_fixed_part_t *fixed_region = synapse_row_fixed_region(row); + synapse_row_plastic_data_t *plastic_data = synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = plastic_data->synapses; + plastic_synapse_t new_weight = synapse_structure_create_synapse(weight); + control_t new_control = control_conversion(id, delay, type); + + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + // Add weight at offset + plastic_words[plastic_synapse] = new_weight; + + // Add control word at offset + control_words[plastic_synapse] = new_control; + + // Increment FP + fixed_region->num_plastic++; + return true; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.c b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.c new file mode 100644 index 00000000000..a324f6af790 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "timing_mfvn_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Exponential lookup-tables +int16_lut *exp_cos_lookup; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t timing_initialise(address_t address) { + +// io_printf(IO_BUF, "timing_mfvn_initialise: starting\n"); +// io_printf(IO_BUF, "\tCerebellum MFVN rule\n"); + + // Copy LUTs from following memory + address_t lut_address = address; + exp_cos_lookup = maths_copy_int16_lut(&lut_address); + +// io_printf(IO_BUF, "Timing_mfvn_initialise: completed successfully\n"); + + return lut_address; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.h b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.h new file mode 100644 index 00000000000..c90a76253ba --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_mfvn_impl.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TIMING_MFVN_IMPL_H_ +#define _TIMING_MFVN_IMPL_H_ + +// MF-VN STDP rules as defined by e.g. Luque et al 2019 +// https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + +//--------------------------------------- +// Typedefines +//--------------------------------------- +typedef int16_t post_trace_t; +typedef int16_t pre_trace_t; + +#include +#include "timing.h" +#include + +// Include debug header for log_info etc +#include + +// Include generic plasticity maths functions +#include +#include + +//--------------------------------------- +// Macros +//--------------------------------------- +// Exponential decay lookup parameters +#define TAU_PLUS_TIME_SHIFT 0 + +//--------------------------------------- +// Timing dependence inline functions +//--------------------------------------- +static inline post_trace_t timing_get_initial_post_trace(void) { + return 0; +} + +static inline post_trace_t timing_decay_post( + uint32_t time, uint32_t last_time, post_trace_t last_trace) { + extern int16_lut *exp_cos_lookup; + // Get time since last spike + uint32_t delta_time = time - last_time; + + // Decay previous o1 and o2 traces + return (post_trace_t) STDP_FIXED_MUL_16X16(last_trace, + maths_lut_exponential_decay(delta_time, exp_cos_lookup)); +} + +//--------------------------------------- +static inline post_trace_t timing_add_post_spike( + uint32_t time, uint32_t last_time, post_trace_t last_trace) { + use(time); + use(last_time); + use(&last_trace); + +// if (print_plasticity){ +// io_printf(IO_BUF, "Adding pre spike to event history (from vestibular nuclei)\n"); +// } + + // Add energy caused by new spike to trace + // **NOTE** o2 trace is pre-multiplied by a3_plus +// int32_t new_o1_trace = 0; //decayed_o1_trace + STDP_FIXED_POINT_ONE; + + // Return new pre- synaptic event with decayed trace values with energy + // for new spike added + return (post_trace_t) 0; +} + +//--------------------------------------- +static inline pre_trace_t timing_add_pre_spike( + uint32_t time, uint32_t last_time, pre_trace_t last_trace) { + use(time); + use(last_time); + use(&last_trace); + + return (pre_trace_t) 0; //new_r1_trace; +} + +//--------------------------------------- +static inline update_state_t timing_apply_pre_spike( + uint32_t time, pre_trace_t trace, uint32_t last_pre_time, + pre_trace_t last_pre_trace, uint32_t last_post_time, + post_trace_t last_post_trace, update_state_t previous_state) { + use(time); + use(&trace); + use(last_pre_time); + use(last_post_time); + use(&last_pre_trace); + use(&last_post_trace); + +// // Here we will potentiate by the fixed amount alpha +// if (print_plasticity){ +// io_printf(IO_BUF, "\n############ Phase 3 #############"); +// io_printf(IO_BUF, "\n Now do potentiation\n"); +// } + + return weight_one_term_apply_potentiation(previous_state, 0); +} + +//--------------------------------------- +static inline update_state_t timing_apply_post_spike( + uint32_t time, post_trace_t trace, uint32_t last_pre_time, + pre_trace_t last_pre_trace, uint32_t last_post_time, + post_trace_t last_post_trace, update_state_t previous_state) { + use(time); + use(&trace); + use(last_pre_time); + use(last_post_time); + use(&last_pre_trace); + use(&last_post_trace); + extern int16_lut *exp_cos_lookup; + + // This is where we lookup the value of e^(-bx) * cos(x)^2 + + // Get time of event relative to last pre-synaptic event + uint32_t time_since_last_pre = last_pre_time; //time - last_pre_time; + +// if (print_plasticity){ +// io_printf(IO_BUF, " delta t = %u, ", time_since_last_pre); +// } + + if (time_since_last_pre < 255){ + int32_t multiplier = maths_lut_exponential_decay_time_shifted( + time_since_last_pre, TAU_PLUS_TIME_SHIFT, exp_cos_lookup); + +// if (print_plasticity){ +// io_printf(IO_BUF, "multiplier: %k (fixed = %u)\n", multiplier << 4, multiplier); +// } + + return weight_one_term_apply_depression(previous_state, multiplier); + } + +// if (print_plasticity){ +// io_printf(IO_BUF, " delta t = %u, ", time_since_last_pre); +// io_printf(IO_BUF, " out of LUT range - do nothing"); +// } + + return previous_state; +} + +#endif // _TIMING_MFVN_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.c b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.c new file mode 100644 index 00000000000..a09b986876c --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "timing_pfpc_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Exponential lookup-tables +int16_lut *exp_sin_lookup; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t timing_initialise(address_t address) { + + io_printf(IO_BUF, "timing_pfpc_initialise: starting\n"); + io_printf(IO_BUF, "\tCerebellum PFPC rule\n"); + + // Copy LUTs from following memory + address_t lut_address = address; + exp_sin_lookup = maths_copy_int16_lut(&lut_address); + + io_printf(IO_BUF, "timing_pfpc_initialise: completed successfully\n"); + + return lut_address; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.h b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.h new file mode 100644 index 00000000000..6a8b43b663b --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_pfpc_impl.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _TIMING_PFPC_IMPL_H_ +#define _TIMING_PFPC_IMPL_H_ + +// PF-PC STDP rules as defined by e.g. Luque et al 2019 +// https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + +//--------------------------------------- +// Typedefines +//--------------------------------------- +typedef int16_t post_trace_t; +typedef int16_t pre_trace_t; + +#include +#include "timing.h" +#include + +// Include debug header for log_info etc +#include + +// Include generic plasticity maths functions +#include +#include + +//--------------------------------------- +// Macros +//--------------------------------------- +// Exponential decay lookup parameters +#define TAU_PLUS_TIME_SHIFT 0 + +//--------------------------------------- +// Timing dependence inline functions +//--------------------------------------- +static inline post_trace_t timing_get_initial_post_trace(void) { + return 0; +} + +static inline post_trace_t timing_decay_post( + uint32_t time, uint32_t last_time, post_trace_t last_trace) { + extern int16_lut *exp_sin_lookup; + // Get time since last spike + uint32_t delta_time = time - last_time; + + // Decay previous o1 and o2 traces + return (post_trace_t) STDP_FIXED_MUL_16X16(last_trace, + maths_lut_exponential_decay(delta_time, exp_sin_lookup)); +} + +//--------------------------------------- +static inline post_trace_t timing_add_post_spike( + uint32_t time, uint32_t last_time, post_trace_t last_trace) { + use(time); + use(last_time); + use(&last_trace); + +// if (print_plasticity){ +// io_printf(IO_BUF, +// "Adding climbing fibre spike to post-event history (stored on neuron\n"); +// } + + // Add energy caused by new spike to trace + // **NOTE** o2 trace is pre-multiplied by a3_plus + int32_t new_o1_trace = 0; //decayed_o1_trace + STDP_FIXED_POINT_ONE; + + // Return new pre- synaptic event with decayed trace values with energy + // for new spike added + return (post_trace_t) new_o1_trace; +} + +//--------------------------------------- +static inline pre_trace_t timing_add_pre_spike( + uint32_t time, uint32_t last_time, pre_trace_t last_trace) { + use(time); + use(last_time); + use(&last_trace); + + return (pre_trace_t) 0; //new_r1_trace; +} + +//--------------------------------------- +static inline update_state_t timing_apply_pre_spike( + uint32_t time, pre_trace_t trace, uint32_t last_pre_time, + pre_trace_t last_pre_trace, uint32_t last_post_time, + post_trace_t last_post_trace, update_state_t previous_state) { + use(time); + use(&trace); + use(last_pre_time); + use(last_post_time); + use(&last_pre_trace); + use(&last_post_trace); + + // Here we will potentiate by the fixed amount alpha +// if (print_plasticity){ +// io_printf(IO_BUF, " This is where we'll do potentiation\n"); +// } + + return weight_one_term_apply_potentiation(previous_state, 0); +} + +//--------------------------------------- +static inline update_state_t timing_apply_post_spike( + uint32_t time, post_trace_t trace, uint32_t last_pre_time, + pre_trace_t last_pre_trace, uint32_t last_post_time, + post_trace_t last_post_trace, update_state_t previous_state) { + use(time); + use(&trace); + use(last_pre_time); + use(last_post_time); + use(&last_pre_trace); + use(&last_post_trace); + extern int16_lut *exp_sin_lookup; + + // This is where we lookup the value of e^(-x) * sin(x)^20 + + // Get time of event relative to last pre-synaptic event + uint32_t time_since_last_pre = last_pre_time; //time - last_pre_time; + +// if (print_plasticity){ +// io_printf(IO_BUF, " delta t = %u, ", time_since_last_pre); +// } + + if (time_since_last_pre < 255){ + int32_t multiplier = maths_lut_exponential_decay_time_shifted( + time_since_last_pre, TAU_PLUS_TIME_SHIFT, exp_sin_lookup); + +// if (print_plasticity){ +// io_printf(IO_BUF, "multiplier: %k (fixed = %u)\n", multiplier << 4, multiplier); +// } + + return weight_one_term_apply_depression(previous_state, multiplier); + } + +// if (print_plasticity){ +// io_printf(IO_BUF, " delta t = %u, ", time_since_last_pre); +// io_printf(IO_BUF, " out of LUT range (do nothing)"); +// } + + return previous_state; +} + +#endif // _TIMING_PFPC_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_one_term_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_one_term_impl.h index fd58a786276..fa28bdf275f 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_one_term_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_one_term_impl.h @@ -24,7 +24,7 @@ #include #include -#include +//#include //--------------------------------------- // Structures diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_two_term_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_two_term_impl.h index 6e865b96d01..feea7c9ded0 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_two_term_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_additive_two_term_impl.h @@ -24,7 +24,7 @@ #include #include -#include +//#include //--------------------------------------- // Structures diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.c b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.c new file mode 100644 index 00000000000..d12e6a8f440 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "weight_mfvn_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Global plasticity parameter data +plasticity_weight_region_data_t *plasticity_weight_region_data; +uint32_t *weight_shift; + +typedef struct { + accum min_weight; + accum max_weight; + accum a2_plus; + accum a2_minus; +} mfvn_config_t; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t weight_initialise( + address_t address, uint32_t n_synapse_types, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + +// io_printf(IO_BUF, "mfvn weight_initialise: starting\n"); +// io_printf(IO_BUF, "\tSTDP mfvn weight dependence\n"); + + // Copy plasticity region data from address + // **NOTE** this seems somewhat safer than relying on sizeof + plasticity_weight_region_data_t *dtcm_copy = plasticity_weight_region_data = + spin1_malloc(sizeof(plasticity_weight_region_data_t) * n_synapse_types); + + if (plasticity_weight_region_data == NULL) { + log_error("Could not initialise weight region data"); + return NULL; + } + + weight_shift = spin1_malloc(sizeof(uint32_t) * n_synapse_types); + + if (weight_shift == NULL) { + log_error("Could not initialise weight region data"); + return NULL; + } + + mfvn_config_t *config = (mfvn_config_t *) address; + for (uint32_t s = 0; s < n_synapse_types; s++) { + // Copy parameters + dtcm_copy[s].min_weight = config->min_weight; + dtcm_copy[s].max_weight = config->max_weight; + dtcm_copy[s].a2_plus = config->a2_plus; + dtcm_copy[s].a2_minus = config->a2_minus; + + // Get the weight shift for switching from int16 to accum + weight_shift[s] = ring_buffer_to_input_buffer_left_shifts[s]; + +// io_printf(IO_BUF, +// "\tSynapse type %u: Min weight:%d, Max weight:%d, A2+:%d, A2-:%d," +// " Weight multiply right shift:%u\n", +// s, dtcm_copy[s].min_weight, dtcm_copy[s].max_weight, +// dtcm_copy[s].a2_plus, dtcm_copy[s].a2_minus, weight_shift[s]); + } + +// io_printf(IO_BUF, "mfvn weight initialisation: completed successfully\n"); + + // Return end address of region + return (address_t) config; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.h new file mode 100644 index 00000000000..cb71c95e7d5 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_mfvn_impl.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WEIGHT_MFVN_IMPL_H_ +#define _WEIGHT_MFVN_IMPL_H_ + +// MF-VN STDP rules as defined by e.g. Luque et al 2019 +// https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + +// Include generic plasticity maths functions +#include +#include +#include + +#include + +//--------------------------------------- +// Structures +//--------------------------------------- +typedef struct { + accum min_weight; + accum max_weight; + + accum a2_plus; // Note: this value is pot_alpha from the python side + accum a2_minus; +} plasticity_weight_region_data_t; + +typedef struct { + accum weight; + + uint32_t weight_shift; + const plasticity_weight_region_data_t *weight_region; +} weight_state_t; + +#include "weight_one_term.h" + + +//--------------------------------------- +// Weight dependance functions +//--------------------------------------- +static inline weight_state_t weight_get_initial(weight_t weight, + index_t synapse_type) { + //--------------------------------------- + // Externals + //--------------------------------------- + extern plasticity_weight_region_data_t *plasticity_weight_region_data; + extern uint32_t *weight_shift; + + accum s1615_weight = kbits(weight << weight_shift[synapse_type]); + + return (weight_state_t ) { + .weight = s1615_weight, + .weight_shift = weight_shift[synapse_type], + .weight_region = &plasticity_weight_region_data[synapse_type] + }; +} + +//--------------------------------------- +static inline weight_state_t weight_one_term_apply_depression( + weight_state_t state, int32_t depression_multiplier) { +// if (print_plasticity){ +// io_printf(IO_BUF, "\n Do Depression\n"); +// io_printf(IO_BUF, " Weight prior to depression: %u\n", state.weight); +// } + + // Multiply by depression and subtract + state.weight -= mul_accum_fixed(state.weight, depression_multiplier); + state.weight = kbits(MAX(bitsk(state.weight), bitsk(state.weight_region->min_weight))); + +// if (print_plasticity){ +// io_printf(IO_BUF, " Weight after depression: %u\n\n", +// state.weight); +// } + + return state; +} +//--------------------------------------- +static inline weight_state_t weight_one_term_apply_potentiation( + weight_state_t state, int32_t a2_plus) { + + // add fixed amount +// if (print_plasticity){ +// io_printf(IO_BUF, " Adding fixed coontribution: %k (int %u)\n", +// state.weight_region->a2_plus << 4, +// state.weight_region->a2_plus); +// } + + state.weight += state.weight_region->a2_plus; + state.weight = kbits(MIN(bitsk(state.weight), bitsk(state.weight_region->max_weight))); + + return state; + +} +//--------------------------------------- +static inline weight_t weight_get_final(weight_state_t new_state) { + +// log_debug("\tnew_weight:%d\n", new_state.weight); + + return (weight_t) (bitsk(new_state.weight) >> new_state.weight_shift); +} + +static inline void weight_decay(weight_state_t *state, int32_t decay) { + state->weight = mul_accum_fixed(state->weight, decay); +} + +static inline accum weight_get_update(weight_state_t state) { + return state.weight; +} + +#endif // _WEIGHT_MFVN_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.c b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.c new file mode 100644 index 00000000000..ca7eabde27b --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "weight_pfpc_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Global plasticity parameter data +plasticity_weight_region_data_t *plasticity_weight_region_data; +uint32_t *weight_shift; + +typedef struct { + accum min_weight; + accum max_weight; + accum a2_plus; + accum a2_minus; +} pfpc_config_t; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t weight_initialise( + address_t address, uint32_t n_synapse_types, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + + io_printf(IO_BUF, "PFPC weight_initialise: starting\n"); + io_printf(IO_BUF, "\tSTDP multiplicative weight dependence\n"); + + // Copy plasticity region data from address + // **NOTE** this seems somewhat safer than relying on sizeof + plasticity_weight_region_data_t *dtcm_copy = plasticity_weight_region_data = + spin1_malloc(sizeof(plasticity_weight_region_data_t) * n_synapse_types); + + if (dtcm_copy == NULL) { + log_error("Could not initialise weight region data"); + return NULL; + } + weight_shift = spin1_malloc(sizeof(uint32_t) * n_synapse_types); + if (weight_shift == NULL) { + log_error("Could not initialise weight region data"); + return NULL; + } + + pfpc_config_t *config = (pfpc_config_t *) address; + for (uint32_t s = 0; s < n_synapse_types; s++) { + // Copy parameters + dtcm_copy[s].min_weight = config->min_weight; + dtcm_copy[s].max_weight = config->max_weight; + dtcm_copy[s].a2_plus = config->a2_plus; + dtcm_copy[s].a2_minus = config->a2_minus; + + // Get the weight shift for switching from int16 to accum + weight_shift[s] = ring_buffer_to_input_buffer_left_shifts[s]; + + io_printf(IO_BUF, + "\tSynapse type %u: Min weight:%d, Max weight:%d, A2+:%d, A2-:%d," + " Weight multiply right shift:%u\n", + s, dtcm_copy[s].min_weight, dtcm_copy[s].max_weight, + dtcm_copy[s].a2_plus, dtcm_copy[s].a2_minus, weight_shift[s]); + } + + io_printf(IO_BUF, "PFPC weight_initialise: completed successfully"); + + // Return end address of region + return (address_t) config; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.h new file mode 100644 index 00000000000..524779bb96d --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_pfpc_impl.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017-2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _WEIGHT_PFPC_IMPL_H_ +#define _WEIGHT_PFPC_IMPL_H_ + +// PF-PC STDP rules as defined by e.g. Luque et al 2019 +// https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1006298 + +// Include generic plasticity maths functions +#include +#include +#include + +#include + +//--------------------------------------- +// Structures +//--------------------------------------- +typedef struct { + accum min_weight; + accum max_weight; + + accum a2_plus; // Note: this value is pot_alpha from the python side + accum a2_minus; +} plasticity_weight_region_data_t; + +typedef struct { + accum weight; + + uint32_t weight_shift; + const plasticity_weight_region_data_t *weight_region; +} weight_state_t; + +#include "weight_one_term.h" + + +//--------------------------------------- +// Weight dependance functions +//--------------------------------------- +static inline weight_state_t weight_get_initial(weight_t weight, + index_t synapse_type) { + extern plasticity_weight_region_data_t *plasticity_weight_region_data; + extern uint32_t *weight_shift; + + accum s1615_weight = kbits(weight << weight_shift[synapse_type]); + + return (weight_state_t ) { + .weight = s1615_weight, + .weight_shift = weight_shift[synapse_type], + .weight_region = &plasticity_weight_region_data[synapse_type] + }; +} + +//--------------------------------------- +static inline weight_state_t weight_one_term_apply_depression( + weight_state_t state, int32_t depression_multiplier) { + +// if (print_plasticity){ +// io_printf(IO_BUF, "\n Do Depression\n"); +// io_printf(IO_BUF, " Weight prior to depression: %u\n", state.weight); +// } + + // Multiply by depression and subtract + // **NOTE** using standard STDP fixed-point format handles format conversion + state.weight -= mul_accum_fixed(state.weight, depression_multiplier); + state.weight = kbits(MAX(bitsk(state.weight), bitsk(state.weight_region->min_weight))); + +// if (print_plasticity){ +// io_printf(IO_BUF, " Weight after depression: %u\n\n", state.weight); +// } + + return state; +} +//--------------------------------------- +static inline weight_state_t weight_one_term_apply_potentiation( + weight_state_t state, int32_t potentiation) { + use(potentiation); + // add fixed amount + state.weight += state.weight_region->a2_plus; + state.weight = kbits(MIN(bitsk(state.weight), bitsk(state.weight_region->max_weight))); + + return state; + +} +//--------------------------------------- +static inline weight_t weight_get_final(weight_state_t new_state) { + log_debug("\tnew_weight:%d\n", new_state.weight); + + return (weight_t) (bitsk(new_state.weight) >> new_state.weight_shift); + +} + +static inline void weight_decay(weight_state_t *state, int32_t decay) { + state->weight = mul_accum_fixed(state->weight, decay); +} + +static inline accum weight_get_update(weight_state_t state) { + return state.weight; +} + +#endif // _WEIGHT_PFPC_IMPL_H_ diff --git a/neural_modelling/src/neuron/spike_processing.c b/neural_modelling/src/neuron/spike_processing.c index c32b61f6323..fb16b416adb 100644 --- a/neural_modelling/src/neuron/spike_processing.c +++ b/neural_modelling/src/neuron/spike_processing.c @@ -441,14 +441,14 @@ bool spike_processing_initialise( // EXPORTED } } dma_busy = false; - clear_input_buffers_of_late_packets = - clear_input_buffers_of_late_packets_init; + clear_input_buffers_of_late_packets = clear_input_buffers_of_late_packets_init; next_buffer_to_fill = 0; buffer_being_read = N_DMA_BUFFERS; p_per_ts_region = packets_per_timestep_region; // Allocate incoming spike buffer if (!in_spikes_initialize_spike_buffer(incoming_spike_buffer_size)) { + log_error("Spike buffer failed to initialise - insufficient DTCM \n"); return false; } diff --git a/neural_modelling/src/neuron/spike_processing_fast.c b/neural_modelling/src/neuron/spike_processing_fast.c index 14e134f463e..c04c6960cbd 100644 --- a/neural_modelling/src/neuron/spike_processing_fast.c +++ b/neural_modelling/src/neuron/spike_processing_fast.c @@ -26,6 +26,8 @@ #include #include +extern bool timer_callback_active; + //! DMA buffer structure combines the row read from SDRAM with information //! about the read. typedef struct dma_buffer { @@ -114,6 +116,21 @@ static uint32_t earliest_spike_received_time = 0; //! The maximum number of spikes left at the end of a time step static uint32_t max_spikes_overflow = 0; +static uint32_t max_spikes_in_a_tick; +static uint32_t max_dmas_in_a_tick; +//static uint32_t dma_complete_count; +static uint32_t max_pipeline_restarts; +static uint32_t timer_callback_completed = 0; +static uint32_t spikes_this_time_step = 0; +static uint32_t dmas_this_time_step = 0; +static uint32_t pipeline_restarts = 0; + +//#if LOG_LEVEL >= LOG_DEBUG +static uint32_t spike_pipeline_deactivation_time = 0; +static uint32_t max_flushed_spikes = 0; +static uint32_t total_flushed_spikes = 0; +//#endif + //! The number of packets received this time step for recording static struct { uint32_t time; @@ -522,6 +539,26 @@ void spike_processing_fast_time_step_loop(uint32_t time, uint32_t n_rewires) { // wait_for_interrupt(); } +#if LOG_LEVEL >= LOG_DEBUG + // NOTE: this code is left over from previous testing; it's left + // here in case there's any interest in testing again using it + // If timer is getting low, don't do next DMA and instead flush spike buffer + // originally 6657 clock cycles from the end of the interval was used + if (tc[T1_COUNT] < 6657) { + uint cpsr = spin1_int_disable(); + uint32_t spikes_remaining = in_spikes_flush_buffer(); + timer_callback_active = true; + spin1_mode_restore(cpsr); + if (spikes_remaining > 0){ + total_flushed_spikes += spikes_remaining; + + if (spikes_remaining > max_flushed_spikes){ + max_flushed_spikes = spikes_remaining; + } + } + } +#endif + // If the timer has gone off, that takes precedence if (is_end_of_time_step()) { clear_end_of_time_step(); @@ -550,6 +587,7 @@ void spike_processing_fast_time_step_loop(uint32_t time, uint32_t n_rewires) { break; } dma_complete_count++; + dmas_this_time_step++; if (dma_in_progress) { read_synaptic_row(spike, &result); } @@ -557,6 +595,9 @@ void spike_processing_fast_time_step_loop(uint32_t time, uint32_t n_rewires) { // Process the row we already have while the DMA progresses process_current_row(time, dma_in_progress); + // Increment counter for spike processing pipeline restarts + pipeline_restarts++; + } } @@ -578,6 +619,7 @@ static inline void check_times(void) { void multicast_packet_received_callback(uint key, UNUSED uint unused) { log_debug("Received spike %x", key); p_per_ts_struct.packets_this_time_step++; + spikes_this_time_step += 1; in_spikes_add_spike(key); check_times(); } @@ -588,6 +630,7 @@ void multicast_packet_received_callback(uint key, UNUSED uint unused) { void multicast_packet_pl_received_callback(uint key, uint payload) { log_debug("Received spike %x with payload %d", key, payload); p_per_ts_struct.packets_this_time_step++; + spikes_this_time_step += 1; // cycle through the packet insertion for (uint count = payload; count > 0; count--) { @@ -656,4 +699,46 @@ void spike_processing_fast_store_provenance( prov->earliest_receive = earliest_spike_received_time; prov->latest_receive = latest_spike_received_time; prov->max_spikes_overflow = max_spikes_overflow; + prov->max_spikes_in_a_tick = max_spikes_in_a_tick; + prov->max_dmas_in_a_tick = max_dmas_in_a_tick; + prov->max_pipeline_restarts = max_pipeline_restarts; + prov->timer_callback_completed = timer_callback_completed; + prov->spike_pipeline_deactivated = spike_pipeline_deactivation_time; + prov->max_flushed_spikes = max_flushed_spikes; + prov->total_flushed_spikes = total_flushed_spikes; +} + +// Custom provenance from SpiNNCer +void spike_processing_get_and_reset_spikes_this_tick(void ) { + if (spikes_this_time_step > max_spikes_in_a_tick) { + max_spikes_in_a_tick = spikes_this_time_step; + } + spikes_this_time_step = 0; +} + +void spike_processing_get_and_reset_dmas_this_tick(void) { + if (dmas_this_time_step > max_dmas_in_a_tick){ + max_dmas_in_a_tick = dmas_this_time_step; + } + dmas_this_time_step = 0; +} + +void spike_processing_get_and_reset_pipeline_restarts_this_tick(void) { + if (pipeline_restarts > max_pipeline_restarts) { + max_pipeline_restarts = pipeline_restarts; + } + pipeline_restarts = 0; +} + +uint32_t spike_processing_get_pipeline_deactivation_time(void) { + return spike_pipeline_deactivation_time; +} + +// FLUSHED SPIKES +uint32_t spike_processing_get_total_flushed_spikes(void) { + return total_flushed_spikes; +} + +uint32_t spike_processing_get_max_flushed_spikes(void) { + return max_flushed_spikes; } diff --git a/neural_modelling/src/neuron/spike_processing_fast.h b/neural_modelling/src/neuron/spike_processing_fast.h index 16f656eb80e..38d79255b17 100644 --- a/neural_modelling/src/neuron/spike_processing_fast.h +++ b/neural_modelling/src/neuron/spike_processing_fast.h @@ -79,6 +79,21 @@ struct spike_processing_fast_provenance { uint32_t latest_receive; //! The most spikes left at the end of any time step uint32_t max_spikes_overflow; + //! SpiNNCer-related provenance + //! The maximum number of spikes in a tick + uint32_t max_spikes_in_a_tick; + //! The maximum number of DMAs in a tick + uint32_t max_dmas_in_a_tick; + //! The maximum number of pipeline restarts + uint32_t max_pipeline_restarts; + //! Was the timer callback completed? + uint32_t timer_callback_completed; + //! Was the spike pipeline deactivated? + uint32_t spike_pipeline_deactivated; + //! The maximum number of flushed spikes in one step + uint32_t max_flushed_spikes; + //! Thet total number of flushed spikes + uint32_t total_flushed_spikes; }; //! \brief Set up spike processing @@ -112,4 +127,32 @@ void spike_processing_fast_time_step_loop(uint32_t time, uint32_t n_rewires); void spike_processing_fast_store_provenance( struct spike_processing_fast_provenance *prov); +// Custom provenance from SpiNNCer + +//! \brief get number of spikes received since last timer event +//! \return uint32_t number of spikes +void spike_processing_get_and_reset_spikes_this_tick(void); + +//! \brief get number of dmas completed since last timer event +//! \return uint32_t number of DMAs +void spike_processing_get_and_reset_dmas_this_tick(void); + +//! \brief get number of time pipeline was restarted since last timer event +//! \return uint32_t number of pipeline restarts +void spike_processing_get_and_reset_pipeline_restarts_this_tick(void); + +//! \brief get time from T1 clock at which spike pipeline completed +//! \return uint32_t pipeline deactivation time +uint32_t spike_processing_get_pipeline_deactivation_time(); + +// FLUSH SPIKES +//! \brief returns the total unprocessed spikes from a simulation +//! \return total unprocessed spikes +uint32_t spike_processing_get_total_flushed_spikes(); + +//! \brief returns the maximum unprocessed spikes from a single +//! simulation timestep. +//! \return maximum unprocessed spikes from a single timestep. +uint32_t spike_processing_get_max_flushed_spikes(); + #endif // _SPIKE_PROCESSING_FAST_H_ diff --git a/neural_modelling/src/neuron/spike_profiling.h b/neural_modelling/src/neuron/spike_profiling.h new file mode 100644 index 00000000000..07be6eaf77c --- /dev/null +++ b/neural_modelling/src/neuron/spike_profiling.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _SPIKE_PROFILING_H_ +#define _SPIKE_PROFILING_H_ + +#include + +typedef struct spike_holder_t { + uint8_t spikes_a; + uint8_t spikes_b; + uint8_t spikes_c; + uint8_t spikes_d; +} spike_holder_t; + +static inline void spike_profiling_cache_and_flush_spike_holder( + struct spike_holder_t* counter_spikes, + struct spike_holder_t* cache_levels) { + + cache_levels->spikes_a = counter_spikes->spikes_a; + cache_levels->spikes_b = counter_spikes->spikes_b; + cache_levels->spikes_c = counter_spikes->spikes_c; + cache_levels->spikes_d = counter_spikes->spikes_d; + + // zero counters + counter_spikes->spikes_a = 0; + counter_spikes->spikes_b = 0; + counter_spikes->spikes_c = 0; + counter_spikes->spikes_d = 0; +} + +static inline void spike_profiling_add_count(uint32_t row_length, + struct spike_holder_t* spike_counter) { + + uint32_t a = 0; + uint32_t b = 1; + uint32_t c = 5; + + if (row_length <= a) { + spike_counter->spikes_a++; + } else if (row_length > a && row_length <= b) { + spike_counter->spikes_b++; + } else if (row_length > b && row_length <= c) { + spike_counter->spikes_c++; + } else if (row_length > c) { + spike_counter->spikes_d++; + } +} + +static inline int32_t spike_profiling_get_spike_holder_as_int( + struct spike_holder_t spikes) { + + union { + int32_t inty; + struct spike_holder_t sh; + } x; + + x.sh = spikes; + + return x.inty; +} + +static inline accum spike_profiling_get_spike_holder_as_accum( + struct spike_holder_t spikes) { + union { + accum acc; + struct spike_holder_t sh; + } x; + x.sh = spikes; + + return x.acc; +} + +#if LOG_LEVEL >= LOG_DEBUG +static inline void spike_profiling_print_spikes_from_spike_holder( + struct spike_holder_t spikes_orig) { + io_printf(IO_BUF, "Spikes from input: a %u, b %u, c %u, d %u \n", + spikes_orig.spikes_a, spikes_orig.spikes_b, spikes_orig.spikes_c, + spikes_orig.spikes_d); +} + +static inline void spike_profiling_print_spikes_from_int(int32_t output) { + io_printf(IO_BUF, "Spikes from output: a %d, b %d, c %d, d %d \n", + (output & 0xFF), (output >> 8 & 0xFF), (output >> 16 & 0xFF), + (output >> 24 & 0xFF)); +} +#endif + +#endif // _SPIKE_PROFILING_H_ diff --git a/neural_modelling/src/neuron/synapse_types/synapse_types_dual_excitatory_exponential_impl.h b/neural_modelling/src/neuron/synapse_types/synapse_types_dual_excitatory_exponential_impl.h index 3e76b5b4573..5155a60fa33 100644 --- a/neural_modelling/src/neuron/synapse_types/synapse_types_dual_excitatory_exponential_impl.h +++ b/neural_modelling/src/neuron/synapse_types/synapse_types_dual_excitatory_exponential_impl.h @@ -171,7 +171,7 @@ static inline const char *synapse_types_get_type_char( //! \param[in] parameters: the pointer to the parameters to use static inline void synapse_types_print_input( synapse_types_t *parameters) { - io_printf(IO_BUF, "%12.6k + %12.6k - %12.6k", + log_debug("%12.6k + %12.6k - %12.6k", parameters->exc.synaptic_input_value, parameters->exc2.synaptic_input_value, parameters->inh.synaptic_input_value); @@ -181,17 +181,17 @@ static inline void synapse_types_print_input( //! \param[in] parameters: the pointer to the parameters to print static inline void synapse_types_print_parameters( synapse_types_t *parameters) { - log_info("exc_decay = %11.4k\n", parameters->exc.decay); - log_info("exc_init = %11.4k\n", parameters->exc.init); - log_info("exc2_decay = %11.4k\n", parameters->exc2.decay); - log_info("exc2_init = %11.4k\n", parameters->exc2.init); - log_info("inh_decay = %11.4k\n", parameters->inh.decay); - log_info("inh_init = %11.4k\n", parameters->inh.init); - log_info("gsyn_excitatory_initial_value = %11.4k\n", + log_debug("exc_decay = %11.4k\n", parameters->exc.decay); + log_debug("exc_init = %11.4k\n", parameters->exc.init); + log_debug("exc2_decay = %11.4k\n", parameters->exc2.decay); + log_debug("exc2_init = %11.4k\n", parameters->exc2.init); + log_debug("inh_decay = %11.4k\n", parameters->inh.decay); + log_debug("inh_init = %11.4k\n", parameters->inh.init); + log_debug("gsyn_excitatory_initial_value = %11.4k\n", parameters->exc.synaptic_input_value); - log_info("gsyn_excitatory2_initial_value = %11.4k\n", + log_debug("gsyn_excitatory2_initial_value = %11.4k\n", parameters->exc2.synaptic_input_value); - log_info("gsyn_inhibitory_initial_value = %11.4k\n", + log_debug("gsyn_inhibitory_initial_value = %11.4k\n", parameters->inh.synaptic_input_value); } diff --git a/neural_modelling/src/neuron/synapse_types/synapse_types_semd_impl.h b/neural_modelling/src/neuron/synapse_types/synapse_types_semd_impl.h index 3d01a5c040e..8b152148e29 100644 --- a/neural_modelling/src/neuron/synapse_types/synapse_types_semd_impl.h +++ b/neural_modelling/src/neuron/synapse_types/synapse_types_semd_impl.h @@ -199,30 +199,30 @@ static inline const char *synapse_types_get_type_char( //! are controlled from the synapses.c print_inputs() method. //! \param[in] parameters: the parameters to print static inline void synapse_types_print_input(synapse_types_t *parameters) { - log_info("%12.6k + %12.6k - %12.6k", + log_debug("%12.6k + %12.6k - %12.6k", parameters->exc.synaptic_input_value, parameters->exc2.synaptic_input_value, parameters->inh.synaptic_input_value); - log_info("multiplicator = %11.4k\n", parameters->multiplicator); - log_info("exc2_old = %11.4k\n", parameters->exc2_old); + log_debug("multiplicator = %11.4k\n", parameters->multiplicator); + log_debug("exc2_old = %11.4k\n", parameters->exc2_old); } //! \brief printer call //! \param[in] parameters: the pointer to the parameters to print static inline void synapse_types_print_parameters(synapse_types_t *parameters) { - log_info("exc_decay = %11.4k\n", parameters->exc.decay); - log_info("exc_init = %11.4k\n", parameters->exc.init); - log_info("exc2_decay = %11.4k\n", parameters->exc2.decay); - log_info("exc2_init = %11.4k\n", parameters->exc2.init); - log_info("inh_decay = %11.4k\n", parameters->inh.decay); - log_info("inh_init = %11.4k\n", parameters->inh.init); - log_info("gsyn_excitatory_initial_value = %11.4k\n", + log_debug("exc_decay = %11.4k\n", parameters->exc.decay); + log_debug("exc_init = %11.4k\n", parameters->exc.init); + log_debug("exc2_decay = %11.4k\n", parameters->exc2.decay); + log_debug("exc2_init = %11.4k\n", parameters->exc2.init); + log_debug("inh_decay = %11.4k\n", parameters->inh.decay); + log_debug("inh_init = %11.4k\n", parameters->inh.init); + log_debug("gsyn_excitatory_initial_value = %11.4k\n", parameters->exc.synaptic_input_value); - log_info("gsyn_excitatory2_initial_value = %11.4k\n", + log_debug("gsyn_excitatory2_initial_value = %11.4k\n", parameters->exc2.synaptic_input_value); - log_info("gsyn_inhibitory_initial_value = %11.4k\n", + log_debug("gsyn_inhibitory_initial_value = %11.4k\n", parameters->inh.synaptic_input_value); - log_info("scaling_factor = %11.4k\n", parameters->scaling_factor); + log_debug("scaling_factor = %11.4k\n", parameters->scaling_factor); } #endif // _SYNAPSE_TYPES_SEMD_IMPL_H_ diff --git a/neural_modelling/src/neuron/synapses.h b/neural_modelling/src/neuron/synapses.h index f98fdda6534..e007ad5462b 100644 --- a/neural_modelling/src/neuron/synapses.h +++ b/neural_modelling/src/neuron/synapses.h @@ -67,7 +67,7 @@ extern uint32_t max_late_spike; //! \param[in] left_shift: the shift to use when decoding static inline void synapses_print_weight( weight_t weight, uint32_t left_shift) { - if (weight != 0) { + if (weight != 0) { io_printf(IO_BUF, "%12.6k", synapse_row_convert_weight_to_input(weight, left_shift)); } else { diff --git a/neural_modelling/src/spike_source/poisson/spike_source_poisson.c b/neural_modelling/src/spike_source/poisson/spike_source_poisson.c index c0704b4b501..323adffe5a3 100644 --- a/neural_modelling/src/spike_source/poisson/spike_source_poisson.c +++ b/neural_modelling/src/spike_source/poisson/spike_source_poisson.c @@ -768,7 +768,7 @@ static bool initialize(void) { // Allocate buffer to allow rate change (2 ints) per source rate_change_buffer = circular_buffer_initialize( - ssp_params.n_spike_sources * 2); + (ssp_params.n_spike_sources * 2) + 1); if (rate_change_buffer == NULL) { log_error("Could not allocate rate change buffer!"); return false; @@ -969,7 +969,7 @@ static void timer_callback(UNUSED uint timer_count, UNUSED uint unused) { colour = time & colour_mask; // Do any rate changes - while (circular_buffer_size(rate_change_buffer) >= 2) { + while (circular_buffer_size(rate_change_buffer) > 0) { uint32_t id = 0; REAL rate = 0.0k; circular_buffer_get_next(rate_change_buffer, &id); diff --git a/spynnaker/pyNN/external_devices_models/external_device_lif_control.py b/spynnaker/pyNN/external_devices_models/external_device_lif_control.py index c338e7881a1..f772370c43a 100644 --- a/spynnaker/pyNN/external_devices_models/external_device_lif_control.py +++ b/spynnaker/pyNN/external_devices_models/external_device_lif_control.py @@ -99,7 +99,7 @@ def create_vertex( self, n_neurons, label, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, n_steps_per_timestep, drop_late_spikes, splitter, seed, - n_colour_bits): + n_colour_bits, rb_left_shifts): if n_neurons != len(self._devices): raise ConfigurationException( "Number of neurons does not match number of " @@ -110,4 +110,4 @@ def create_vertex( self._devices, self._create_edges, max_atoms, self._model, self, self._translator, spikes_per_second, label, ring_buffer_sigma, incoming_spike_buffer_size, drop_late_spikes, splitter, seed, - n_colour_bits) + n_colour_bits, rb_left_shifts) diff --git a/spynnaker/pyNN/external_devices_models/external_device_lif_control_vertex.py b/spynnaker/pyNN/external_devices_models/external_device_lif_control_vertex.py index aa5220f1948..01b88122b92 100644 --- a/spynnaker/pyNN/external_devices_models/external_device_lif_control_vertex.py +++ b/spynnaker/pyNN/external_devices_models/external_device_lif_control_vertex.py @@ -44,7 +44,7 @@ def __init__( pynn_model, translator=None, spikes_per_second=None, label=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, drop_late_spikes=None, splitter=None, seed=None, - n_colour_bits=None): + n_colour_bits=None, rb_left_shifts=None): """ :param list(AbstractMulticastControllableDevice) devices: The AbstractMulticastControllableDevice instances to be controlled @@ -67,13 +67,14 @@ def __init__( :type splitter: ~pacman.model.partitioner_splitters.AbstractSplitterCommon or None :param int n_colour_bits: The number of colour bits to use + :param int rb_left_shifts: The left shifts to use (or None to calc) """ # pylint: disable=too-many-arguments super().__init__( len(devices), label, max_atoms_per_core, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, neuron_impl, pynn_model, drop_late_spikes, splitter, seed, - n_colour_bits) + n_colour_bits, rb_left_shifts) if not devices: raise ConfigurationException("No devices specified") diff --git a/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_fixed.py b/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_fixed.py index f4488e70713..9bc46beae25 100644 --- a/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_fixed.py +++ b/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_fixed.py @@ -82,7 +82,23 @@ def create_machine_vertices(self, chip_counter): max_atoms_per_core = min( app_vertex.get_max_atoms_per_core(), app_vertex.n_atoms) - ring_buffer_shifts = app_vertex.get_ring_buffer_shifts() + app_vertex = self._governed_app_vertex + ring_buffer_shifts = None + if (hasattr(app_vertex, "rb_left_shifts") and + app_vertex.rb_left_shifts is not None): + print("=" * 80) + print("Using given values for RB left shifts.") + ring_buffer_shifts = app_vertex.rb_left_shifts + print("RB left shifts for {:20}".format(app_vertex.label), + "=", ring_buffer_shifts) + print("-" * 80) + else: + print("=" * 80) + print("Computing RB left shifts for", app_vertex.label) + ring_buffer_shifts = app_vertex.get_ring_buffer_shifts() + print("RB left shifts for {:20}".format(app_vertex.label), + "=", ring_buffer_shifts) + weight_scales = app_vertex.get_weight_scales(ring_buffer_shifts) all_syn_block_sz = app_vertex.get_synapses_size( max_atoms_per_core) diff --git a/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_neurons_synapses.py b/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_neurons_synapses.py index f0b54bbcba1..90748d0e820 100644 --- a/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_neurons_synapses.py +++ b/spynnaker/pyNN/extra_algorithms/splitter_components/splitter_abstract_pop_vertex_neurons_synapses.py @@ -209,7 +209,22 @@ def create_machine_vertices(self, chip_counter): app_vertex.get_max_atoms_per_core(), app_vertex.n_atoms) # Work out the ring buffer shifts based on all incoming things - rb_shifts = app_vertex.get_ring_buffer_shifts() + rb_shifts = None + if (hasattr(app_vertex, "rb_left_shifts") and + app_vertex.rb_left_shifts is not None): + print("=" * 80) + print("Using given values for RB left shifts.") + rb_shifts = app_vertex.rb_left_shifts + print("RB left shifts for {:20}".format(app_vertex.label), + "=", rb_shifts) + print("-" * 80) + else: + print("=" * 80) + print("Computing RB left shifts for", app_vertex.label) + rb_shifts = app_vertex.get_ring_buffer_shifts() + print("RB left shifts for {:20}".format(app_vertex.label), + "=", rb_shifts) + weight_scales = app_vertex.get_weight_scales(rb_shifts) # We add the SDRAM edge SDRAM to the neuron resources so it is diff --git a/spynnaker/pyNN/extra_models/__init__.py b/spynnaker/pyNN/extra_models/__init__.py index 99df600db3f..b52c4296875 100644 --- a/spynnaker/pyNN/extra_models/__init__.py +++ b/spynnaker/pyNN/extra_models/__init__.py @@ -33,6 +33,16 @@ # Variable rate poisson from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVariable +# ICub VOR imports - Cerebellum Plasticity +from spynnaker.pyNN.models.neuron.plasticity.stdp.timing_dependence import ( + TimingDependencePFPC as TimingDependencePFPC) +from spynnaker.pyNN.models.neuron.plasticity.stdp.timing_dependence import ( + TimingDependenceMFVN as TimingDependenceMFVN) +from spynnaker.pyNN.models.neuron.plasticity.stdp.weight_dependence import ( + WeightDependenceMFVN as WeightDependenceMFVN) +from spynnaker.pyNN.models.neuron.plasticity.stdp.weight_dependence import ( + WeightDependencePFPC as WeightDependencePFPC) + __all__ = [ # sPyNNaker models not currently part of full pyNN 'IFCurDelta', 'IFCurrExpCa2Adaptive', 'IFCondExpStoc', @@ -47,6 +57,8 @@ 'PfisterSpikeTriplet', 'SpikeNearestPairRule', 'RecurrentRule', 'Vogels2011Rule', + "TimingDependencePFPC", "WeightDependencePFPC", # ICub VOR + 'TimingDependenceMFVN', 'WeightDependenceMFVN', # ICub VOR # Variable rate Poisson 'SpikeSourcePoissonVariable'] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/from_list_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/from_list_connector.py index ac117ac7ccc..fd847b687bf 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/from_list_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/from_list_connector.py @@ -13,6 +13,8 @@ # limitations under the License. import numpy +from pyNN.random import RandomDistribution + from spinn_utilities.overrides import overrides from spynnaker.pyNN.data import SpynnakerDataView from spynnaker.pyNN.exceptions import InvalidParameterType @@ -87,6 +89,9 @@ def __init__(self, conn_list, safe=True, verbose=False, column_names=None, @overrides(AbstractConnector.get_delay_maximum) def get_delay_maximum(self, synapse_info): if self.__delays is None: + # TODO: is this still allowed? + if isinstance(synapse_info.delays, RandomDistribution): + return synapse_info.delays.parameters['high'] if hasattr(synapse_info.delays, "__len__"): return numpy.max(synapse_info.delays) return self._get_delay_maximum( diff --git a/spynnaker/pyNN/models/neural_projections/projection_application_edge.py b/spynnaker/pyNN/models/neural_projections/projection_application_edge.py index 2904b914733..a37540de1e2 100644 --- a/spynnaker/pyNN/models/neural_projections/projection_application_edge.py +++ b/spynnaker/pyNN/models/neural_projections/projection_application_edge.py @@ -143,3 +143,5 @@ def n_delay_stages(self): def get_local_provenance_data(self): for synapse_info in self.synapse_information: synapse_info.connector.get_provenance_data(synapse_info) + if are_dynamics_stdp(synapse_info.synapse_dynamics): + synapse_info.synapse_dynamics.get_provenance_data(synapse_info) diff --git a/spynnaker/pyNN/models/neuron/abstract_population_vertex.py b/spynnaker/pyNN/models/neuron/abstract_population_vertex.py index 9ada2fa3567..6547ed2ca71 100644 --- a/spynnaker/pyNN/models/neuron/abstract_population_vertex.py +++ b/spynnaker/pyNN/models/neuron/abstract_population_vertex.py @@ -165,6 +165,7 @@ class AbstractPopulationVertex( "__ring_buffer_sigma", "__spikes_per_second", "__drop_late_spikes", + "__rb_left_shifts", "__incoming_projections", "__incoming_poisson_projections", "__synapse_dynamics", @@ -205,7 +206,7 @@ def __init__( self, n_neurons, label, max_atoms_per_core, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, neuron_impl, pynn_model, drop_late_spikes, splitter, seed, - n_colour_bits): + n_colour_bits, rb_left_shifts): """ :param int n_neurons: The number of neurons in the population :param str label: The label on the population @@ -232,6 +233,7 @@ def __init__( The Population seed, used to ensure the same random generation on each run. :param int n_colour_bits: The number of colour bits to use + :param int rb_left_shifts: The left shifts to use """ # pylint: disable=too-many-arguments super().__init__(label, max_atoms_per_core, splitter) @@ -260,6 +262,8 @@ def __init__( self.__drop_late_spikes = get_config_bool( "Simulation", "drop_late_spikes") + self.__rb_left_shifts = rb_left_shifts + self.__neuron_impl = neuron_impl self.__pynn_model = pynn_model self.__parameters = RangeDictionary(n_neurons) @@ -556,6 +560,17 @@ def drop_late_spikes(self): """ return self.__drop_late_spikes + @property + def rb_left_shifts(self): + """ ring buffer left shifts (for use by user) + + :rtype: bool + """ + return self.__rb_left_shifts + + def set_rb_left_shifts(self, rb_left_shifts): + self.__rb_left_shifts = rb_left_shifts + def get_sdram_usage_for_core_neuron_params(self, n_atoms): """ :param int n_atoms: The number of atoms per core diff --git a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model.py b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model.py index 79d7b417419..375d2fa219e 100644 --- a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model.py +++ b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model.py @@ -24,7 +24,8 @@ _population_parameters = { "spikes_per_second": None, "ring_buffer_sigma": None, "incoming_spike_buffer_size": None, "drop_late_spikes": None, - "splitter": None, "seed": None, "n_colour_bits": None + "splitter": None, "seed": None, "n_colour_bits": None, + "rb_left_shifts": None } @@ -48,7 +49,7 @@ def _model(self): def create_vertex( self, n_neurons, label, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, drop_late_spikes, splitter, seed, - n_colour_bits): + n_colour_bits, rb_left_shifts): """ :param float spikes_per_second: :param float ring_buffer_sigma: @@ -59,13 +60,14 @@ def create_vertex( ~pacman.model.partitioner_splitters.AbstractSplitterCommon or None :param float seed: :param int n_colour_bits: + :param rb_left_shifts: """ # pylint: disable=arguments-differ max_atoms = self.get_model_max_atoms_per_dimension_per_core() return AbstractPopulationVertex( n_neurons, label, max_atoms, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, self.__model, self, drop_late_spikes, - splitter, seed, n_colour_bits) + splitter, seed, n_colour_bits, rb_left_shifts) @property @overrides(AbstractPyNNModel.name) diff --git a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py index bb64a9691d0..0c26342a767 100644 --- a/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py +++ b/spynnaker/pyNN/models/neuron/abstract_pynn_neuron_model_standard.py @@ -58,7 +58,7 @@ def create_vertex( self, n_neurons, label, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, n_steps_per_timestep, drop_late_spikes, splitter, seed, - n_colour_bits): + n_colour_bits, rb_left_shifts): """ :param int n_steps_per_timestep: """ @@ -67,4 +67,4 @@ def create_vertex( return super().create_vertex( n_neurons, label, spikes_per_second, ring_buffer_sigma, incoming_spike_buffer_size, drop_late_spikes, - splitter, seed, n_colour_bits) + splitter, seed, n_colour_bits, rb_left_shifts) diff --git a/spynnaker/pyNN/models/neuron/neuron_models/__init__.py b/spynnaker/pyNN/models/neuron/neuron_models/__init__.py index 3da5f9c9665..64e4785ab1b 100644 --- a/spynnaker/pyNN/models/neuron/neuron_models/__init__.py +++ b/spynnaker/pyNN/models/neuron/neuron_models/__init__.py @@ -15,6 +15,9 @@ from .neuron_model_izh import NeuronModelIzh from .neuron_model_leaky_integrate_and_fire import ( NeuronModelLeakyIntegrateAndFire) +from .neuron_model_leaky_integrate_and_fire_t_last \ + import NeuronModelLeakyIntegrateAndFireTLast __all__ = ["NeuronModelIzh", - "NeuronModelLeakyIntegrateAndFire"] + "NeuronModelLeakyIntegrateAndFire", + "NeuronModelLeakyIntegrateAndFireTLast"] diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire_t_last.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire_t_last.py new file mode 100644 index 00000000000..1948848b6d9 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_leaky_integrate_and_fire_t_last.py @@ -0,0 +1,172 @@ +# Copyright (c) 2017-2021 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from spinn_utilities.overrides import overrides +from spinn_front_end_common.interface.ds import DataType +from spynnaker.pyNN.models.neuron.implementations import ( + AbstractStandardNeuronComponent) +from spynnaker.pyNN.utilities.struct import Struct +from spynnaker.pyNN.data import SpynnakerDataView + +V = "v" +V_REST = "v_rest" +TAU_M = "tau_m" +CM = "cm" +I_OFFSET = "i_offset" +V_RESET = "v_reset" +TAU_REFRAC = "tau_refrac" +TIMESTEP = "timestep" +REFRACT_TIMER = "refract_timer" +T_LAST = "t_last" + + +class NeuronModelLeakyIntegrateAndFireTLast(AbstractStandardNeuronComponent): + """ Classic leaky integrate and fire neuron model, including last T. + """ + __slots__ = [ + "__v_init", + "__v_rest", + "__tau_m", + "__cm", + "__i_offset", + "__v_reset", + "__tau_refrac"] + + def __init__( + self, v_init, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac): + r""" + :param v_init: :math:`V_{init}` + :type v_init: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param v_rest: :math:`V_{rest}` + :type v_rest: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param tau_m: :math:`\tau_{m}` + :type tau_m: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param cm: :math:`C_m` + :type cm: float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param i_offset: :math:`I_{offset}` + :type i_offset: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param v_reset: :math:`V_{reset}` + :type v_reset: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + :param tau_refrac: :math:`\tau_{refrac}` + :type tau_refrac: + float, iterable(float), ~pyNN.random.RandomDistribution or + (mapping) function + """ + super().__init__( + [Struct([ + (DataType.S1615, V), + (DataType.S1615, V_REST), + (DataType.S1615, CM), + (DataType.S1615, TAU_M), + (DataType.S1615, I_OFFSET), + (DataType.S1615, V_RESET), + (DataType.S1615, TAU_REFRAC), + (DataType.INT32, T_LAST), + (DataType.INT32, REFRACT_TIMER), + (DataType.S1615, TIMESTEP)])], + {V: 'mV', V_REST: 'mV', TAU_M: 'ms', CM: 'nF', I_OFFSET: 'nA', + V_RESET: 'mV', TAU_REFRAC: 'ms'}) + + if v_init is None: + v_init = v_rest + self.__v_init = v_init + self.__v_rest = v_rest + self.__tau_m = tau_m + self.__cm = cm + self.__i_offset = i_offset + self.__v_reset = v_reset + self.__tau_refrac = tau_refrac + + @overrides(AbstractStandardNeuronComponent.add_parameters) + def add_parameters(self, parameters): + parameters[V_REST] = self.__v_rest + parameters[TAU_M] = self.__tau_m + parameters[CM] = self.__cm + parameters[I_OFFSET] = self.__i_offset + parameters[V_RESET] = self.__v_reset + parameters[TAU_REFRAC] = self.__tau_refrac + parameters[TIMESTEP] = SpynnakerDataView.get_simulation_time_step_ms() + + @overrides(AbstractStandardNeuronComponent.add_state_variables) + def add_state_variables(self, state_variables): + state_variables[V] = self.__v_init + state_variables[REFRACT_TIMER] = 0 + state_variables[T_LAST] = 0 + + @property + def v_init(self): + """ Settable model parameter: :math:`V_{init}` + + :rtype: float + """ + return self.__v_init + + @property + def v_rest(self): + """ Settable model parameter: :math:`V_{rest}` + + :rtype: float + """ + return self.__v_rest + + @property + def tau_m(self): + r""" Settable model parameter: :math:`\tau_{m}` + + :rtype: float + """ + return self.__tau_m + + @property + def cm(self): + """ Settable model parameter: :math:`C_m` + + :rtype: float + """ + return self.__cm + + @property + def i_offset(self): + """ Settable model parameter: :math:`I_{offset}` + + :rtype: float + """ + return self.__i_offset + + @property + def v_reset(self): + """ Settable model parameter: :math:`V_{reset}` + + :rtype: float + """ + return self.__v_reset + + @property + def tau_refrac(self): + r""" Settable model parameter: :math:`\tau_{refrac}` + + :rtype: float + """ + return self.__tau_refrac diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/common.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/common.py index 7adb0567bc9..4912a1c4dcb 100644 --- a/spynnaker/pyNN/models/neuron/plasticity/stdp/common.py +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/common.py @@ -50,3 +50,145 @@ def get_exp_lut_array(time_step, time_constant, shift=0): # Concatenate with the header header = numpy.array([len(a), shift], dtype="uint16") return numpy.concatenate((header, a.astype("uint16"))).view("uint32") + + +def write_pfpc_lut(spec, peak_time, lut_size, shift, time_probe, + kernel_scaling=1.0): + sin_pwr = 20 + + # Calculate required time constant + time_constant = peak_time / math.atan(sin_pwr) + inv_tau = (1.0 / float(time_constant)) # * (machine_time_step / 1000.0) + + # calculate time of peak (from differentiating kernel and setting to zero) + # kernel_peak_time = math.atan(20) / inv_tau + + # evaluate peak value of kernel to normalise LUT + kernel_peak_value = (math.exp(-peak_time * inv_tau) * + math.sin(peak_time * inv_tau) ** sin_pwr) + + # Generate LUT + out_float = [] + out_fixed = [] + + final_exp_fix = [] + + for i in range(0, lut_size): # note that i corresponds to 1 timestep! + + # Multiply by inverse of time constant + value = float(i) * inv_tau + + # Only take first peak from kernel + if (value > math.pi): + exp_float = 0 + else: + # Evaluate kernel + exp_float = (math.exp(-value) * math.sin(value) ** + sin_pwr / kernel_peak_value) * kernel_scaling + + # Convert to fixed-point + exp_fix = float_to_fixed(exp_float) + + if spec is None: # in testing mode so print + out_float.append(exp_float) + out_fixed.append(exp_fix) + if i == time_probe: + print("dt = {}, kernel value = {} (fixed-point = {})".format( + time_probe, exp_float, exp_fix)) + + else: # at runtime, so write to spec + final_exp_fix.append(exp_fix) + # spec.write_value(data=exp_fix, data_type=DataType.INT16) + + if spec is None: + print("peak: time {}, value {}".format(peak_time, kernel_peak_value)) + t = numpy.arange(0, lut_size) + out_fixed = numpy.array(out_fixed) + out_float = numpy.array(out_float) + + compare_t_values = numpy.array([15, 20, 30, 35, 45, 47, + 99, 115, 135, 140, 150]) + print("LUT VALUES TO COMPARE TO SPINNAKER:") + print("TIME DELTAS | FIXED MULTIPLIERS | FLOAT MULTIPLIERS") + for x, y, z, in zip(compare_t_values, out_fixed[compare_t_values], + out_float[compare_t_values]): + print("{:8} | {:8} | {:8.4f}".format(x, y, z)) + + return compare_t_values, out_float, out_fixed, t + else: + header = numpy.array([len(final_exp_fix), shift], dtype="uint16") + array_to_write = numpy.concatenate(( + header, + numpy.array(final_exp_fix).astype("uint16"))).view("uint32") + # + spec.write_array(array_to_write) + return array_to_write + + +def write_mfvn_lut(spec, sigma, beta, lut_size, shift, time_probe, + kernel_scaling=1.0): + cos_pwr = 2 + + # Calculate required time constant + inv_sigma = (1.0 / float(sigma)) # * (machine_time_step / 1000.0) + peak_time = 0 + + # evaluate peak value of kernel to normalise LUT + kernel_peak_value = (math.exp(-abs(peak_time * inv_sigma * beta)) * + math.cos(peak_time * inv_sigma) ** cos_pwr) + + # Generate LUT + out_float = [] + out_fixed = [] + plot_times = [] + + final_exp_fix = [] + + for i in range(0, lut_size): # note that i corresponds to 1 timestep! + + # Multiply by inverse of time constant + value = float(i) * inv_sigma + + # Only take first peak from kernel + if (value > math.pi / 2): + exp_float = 0 + else: + # Evaluate kernel + exp_float = (math.exp(-abs(value * beta)) * math.cos(value) ** + cos_pwr / kernel_peak_value) * kernel_scaling + + # Convert to fixed-point + exp_fix = float_to_fixed(exp_float) + + if spec is None: # in testing mode so print + out_float.append(exp_float) + out_fixed.append(exp_fix) + plot_times.append(i) + if i == time_probe: + print("dt = {}, kernel value = {} (fixed-point = {})".format( + time_probe, exp_float, exp_fix)) + + else: # at runtime, so write to spec + final_exp_fix.append(exp_fix) + + if spec is None: + print("peak: time {}, value {}".format(peak_time, kernel_peak_value)) + out_fixed = numpy.array(out_fixed) + out_float = numpy.array(out_float) + + compare_t_values = numpy.array([15, 20, 30, 35, 45, 47, + 99, 115, 135, 140, 150]) + print("LUT VALUES TO COMPARE TO SPINNAKER:") + print("TIME DELTAS | FIXED MULTIPLIERS | FLOAT MULTIPLIERS") + for x, y, z, in zip(compare_t_values, out_fixed[compare_t_values], + out_float[compare_t_values]): + print("{:8} | {:8} | {:8.4f}".format(x, y, z)) + # plt.show() + return compare_t_values, out_float, plot_times + else: + header = numpy.array([len(final_exp_fix), shift], dtype="uint16") + array_to_write = numpy.concatenate(( + header, + numpy.array(final_exp_fix).astype("uint16"))).view("uint32") + spec.write_array(array_to_write) + return array_to_write diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/__init__.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/__init__.py index 2d5b08082c2..efacff33d75 100644 --- a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/__init__.py +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/__init__.py @@ -20,9 +20,12 @@ from .timing_dependence_spike_nearest_pair import ( TimingDependenceSpikeNearestPair) from .timing_dependence_vogels_2011 import TimingDependenceVogels2011 +from .timing_dependence_pfpc import TimingDependencePFPC +from .timing_dependence_mfvn import TimingDependenceMFVN __all__ = [ "AbstractTimingDependence", "TimingDependenceSpikePair", "TimingDependencePfisterSpikeTriplet", "TimingDependenceRecurrent", - "TimingDependenceSpikeNearestPair", "TimingDependenceVogels2011" -] + "TimingDependenceSpikeNearestPair", "TimingDependenceVogels2011", + "TimingDependencePFPC", "TimingDependenceMFVN" + ] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/abstract_timing_dependence.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/abstract_timing_dependence.py index bf0a3af9f7b..249add5ab02 100644 --- a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/abstract_timing_dependence.py +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/abstract_timing_dependence.py @@ -91,3 +91,9 @@ def get_parameter_names(self): :rtype: iterable(str) """ + + def get_provenance_data(self, synapse_info): + """ Get any provenance data + :param SynapseInformation synapse_info + """ + # pylint: disable=unused-argument diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_mfvn.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_mfvn.py new file mode 100644 index 00000000000..1b5643f325a --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_mfvn.py @@ -0,0 +1,198 @@ +# Copyright (c) 2017-2019 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from spinn_utilities.overrides import overrides +from spinn_front_end_common.interface.provenance import ProvenanceWriter +from spynnaker.pyNN.data import SpynnakerDataView +from spinn_front_end_common.utilities.constants import BYTES_PER_WORD +from spynnaker.pyNN.models.neuron.plasticity.stdp.common \ + import write_mfvn_lut +from .abstract_timing_dependence import AbstractTimingDependence +from spynnaker.pyNN.models.neuron.plasticity.stdp.synapse_structure\ + import SynapseStructureWeightOnly + + +import logging +logger = logging.getLogger(__name__) + +LUT_SIZE = 256 + + +class TimingDependenceMFVN(AbstractTimingDependence): + __slots__ = [ + "_synapse_structure", + "_tau_minus", + "_tau_minus_data", + "_tau_plus", + "_tau_plus_data", + "__a_plus", + "__a_minus", + "_beta", + "_sigma", + "_alpha" + ] + + def __init__(self, tau_plus=20.0, tau_minus=20.0, A_plus=0.01, + A_minus=0.01, beta=10, sigma=200, alpha=1.0): + self._tau_plus = tau_plus + self._tau_minus = tau_minus + + self.__a_plus = A_plus + self.__a_minus = A_minus + + self._alpha = alpha + + self._synapse_structure = SynapseStructureWeightOnly() + + self._beta = beta + self._sigma = sigma + + # provenance data + self._tau_plus_data = None + self._tau_minus_data = None + + @property + def tau_plus(self): + return self._tau_plus + + @property + def tau_minus(self): + return self._tau_minus + + @property + def A_plus(self): + r""" :math:`A^+` + + :rtype: float + """ + return self.__a_plus + + @A_plus.setter + def A_plus(self, new_value): + self.__a_plus = new_value + + @property + def A_minus(self): + r""" :math:`A^-` + + :rtype: float + """ + return self.__a_minus + + @A_minus.setter + def A_minus(self, new_value): + self.__a_minus = new_value + + @property + def beta(self): + return self._beta + + @property + def sigma(self): + return self._sigma + + @overrides(AbstractTimingDependence.is_same_as) + def is_same_as(self, timing_dependence): + if not isinstance(timing_dependence, TimingDependenceMFVN): + return False + return (self.tau_plus == timing_dependence.tau_plus and + self.tau_minus == timing_dependence.tau_minus) + + @property + def vertex_executable_suffix(self): + return "mfvn" + + @property + def pre_trace_n_bytes(self): + # Here we will record the last 16 spikes, + # these will be 32-bit quantities, + # 16 4-byte entries, plus one counter for the number of spikes + return (16 * 4) + (2 * 16) + + @overrides(AbstractTimingDependence.get_parameters_sdram_usage_in_bytes) + def get_parameters_sdram_usage_in_bytes(self): + return BYTES_PER_WORD * LUT_SIZE + + @property + def n_weight_terms(self): + return 1 + + @overrides(AbstractTimingDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales): + # Check timestep is valid + time_step = SpynnakerDataView.get_simulation_time_step_ms() + if time_step != 1: + raise NotImplementedError( + "exp cos LUT generation currently only supports 1ms timesteps") + + # Write exp_sin lookup table + self._tau_plus_data = write_mfvn_lut( + spec, + sigma=self._sigma, + beta=self._beta, + time_probe=None, + lut_size=LUT_SIZE, + shift=0, + kernel_scaling=self._alpha) + + @property + def synaptic_structure(self): + return self._synapse_structure + + @overrides(AbstractTimingDependence.get_provenance_data) + def get_provenance_data(self, synapse_info): + tauplus = 0 + if self._tau_plus_data is not None: + tauplus = self._tau_plus_data[-1] + tauminus = 0 + if self._tau_minus_data is not None: + tauminus = self._tau_minus_data[-1] + with ProvenanceWriter() as db: + db.insert_lut( + synapse_info.pre_population.label, + synapse_info.post_population.label, + self.__class__.__name__, "tau_plus last_entry", + tauplus) + if tauplus > 0: + db.insert_report( + f"The last entry in the STDP exponential lookup table " + f"for the tau_plus parameter of the" + f"{self.__class__.__name__} between " + f"{synapse_info.pre_population.label} and " + f"{synapse_info.post_population.label} was {tauplus} " + f"rather than 0, indicating that the lookup table was " + f"not big enough at this timestep and value. Try " + f"reducing the parameter value, or increasing the " + f"timestep.") + db.insert_lut( + synapse_info.pre_population.label, + synapse_info.post_population.label, + self.__class__.__name__, "tau_minus last_entry", + tauminus) + if tauminus > 0: + db.insert_report( + f"The last entry in the STDP exponential lookup table " + f"for the tau_minus parameter of the " + f"{self.__class__.__name__} between " + f"{synapse_info.pre_population.label} and " + f"{synapse_info.post_population.label} was {tauminus} " + f"rather than 0, indicating that the lookup table was " + f"not big enough at this timestep and value. Try " + f"reducing the parameter value, or increasing the " + f"timestep.") + + @overrides(AbstractTimingDependence.get_parameter_names) + def get_parameter_names(self): + return ['tau_plus', 'tau_minus', 'beta', 'sigma'] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_pfpc.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_pfpc.py new file mode 100644 index 00000000000..3073a7b1a69 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_pfpc.py @@ -0,0 +1,186 @@ +# Copyright (c) 2017-2019 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from spinn_utilities.overrides import overrides +from spinn_front_end_common.interface.provenance import ProvenanceWriter +from spynnaker.pyNN.data import SpynnakerDataView +from spinn_front_end_common.utilities.constants import BYTES_PER_WORD +from spynnaker.pyNN.models.neuron.plasticity.stdp.common \ + import write_pfpc_lut +from .abstract_timing_dependence import AbstractTimingDependence +from spynnaker.pyNN.models.neuron.plasticity.stdp.synapse_structure \ + import SynapseStructureWeightOnly + +import logging + +logger = logging.getLogger(__name__) + +LUT_SIZE = 256 + + +class TimingDependencePFPC(AbstractTimingDependence): + __slots__ = [ + "_synapse_structure", + "_tau_minus", + "_tau_minus_data", + "_tau_plus", + "_tau_plus_data", + "__a_plus", + "__a_minus", + "_t_peak", + "_alpha"] + + def __init__(self, tau_plus=20.0, tau_minus=20.0, A_plus=0.01, + A_minus=0.01, t_peak=100, alpha=1.0): + self._tau_plus = tau_plus + self._tau_minus = tau_minus + + self.__a_plus = A_plus + self.__a_minus = A_minus + + self._t_peak = t_peak + self._alpha = alpha + + self._synapse_structure = SynapseStructureWeightOnly() + + self._tau_plus_data = None + self._tau_minus_data = None + + @property + def tau_plus(self): + return self._tau_plus + + @property + def tau_minus(self): + return self._tau_minus + + @property + def A_plus(self): + r""" :math:`A^+` + + :rtype: float + """ + return self.__a_plus + + @A_plus.setter + def A_plus(self, new_value): + self.__a_plus = new_value + + @property + def A_minus(self): + r""" :math:`A^-` + + :rtype: float + """ + return self.__a_minus + + @A_minus.setter + def A_minus(self, new_value): + self.__a_minus = new_value + + @property + def t_peak(self): + return self._t_peak + + @overrides(AbstractTimingDependence.is_same_as) + def is_same_as(self, timing_dependence): + if not isinstance(timing_dependence, TimingDependencePFPC): + return False + return (self.tau_plus == timing_dependence.tau_plus and + self.tau_minus == timing_dependence.tau_minus) + + @property + def vertex_executable_suffix(self): + return "pfpc" + + @property + def pre_trace_n_bytes(self): + + # Here we will record the last 16 spikes: + # these will be 32-bit quantities, 16 4-byte entries, + # plus one counter for the number of spikes + return (16 * 4) + (2 * 16) + + @overrides(AbstractTimingDependence.get_parameters_sdram_usage_in_bytes) + def get_parameters_sdram_usage_in_bytes(self): + return BYTES_PER_WORD * LUT_SIZE + + @property + def n_weight_terms(self): + return 1 + + @overrides(AbstractTimingDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales): + # Check timestep is valid + time_step = SpynnakerDataView.get_simulation_time_step_ms() + if time_step != 1: + raise NotImplementedError( + "exp_sin LUT generation currently only supports 1ms timesteps") + + # Write exp_sin lookup table + self._tau_plus_data = write_pfpc_lut( + spec, peak_time=self._t_peak, + time_probe=None, lut_size=LUT_SIZE, + shift=0, kernel_scaling=self._alpha) + + @property + def synaptic_structure(self): + return self._synapse_structure + + @overrides(AbstractTimingDependence.get_provenance_data) + def get_provenance_data(self, synapse_info): + tauplus = 0 + if self._tau_plus_data is not None: + tauplus = self._tau_plus_data[-1] + tauminus = 0 + if self._tau_minus_data is not None: + tauminus = self._tau_minus_data[-1] + with ProvenanceWriter() as db: + db.insert_lut( + synapse_info.pre_population.label, + synapse_info.post_population.label, + self.__class__.__name__, "tau_plus_last_entry", + tauplus) + if tauplus > 0: + db.insert_report( + f"The last entry in the STDP exponential lookup table " + f"for the tau_plus parameter of the" + f"{self.__class__.__name__} between " + f"{synapse_info.pre_population.label} and " + f"{synapse_info.post_population.label} was {tauplus} " + f"rather than 0, indicating that the lookup table was " + f"not big enough at this timestep and value. Try " + f"reducing the parameter value, or increasing the " + f"timestep.") + db.insert_lut( + synapse_info.pre_population.label, + synapse_info.post_population.label, + self.__class__.__name__, "tau_minus_last_entry", + tauminus) + if tauminus > 0: + db.insert_report( + f"The last entry in the STDP exponential lookup table " + f"for the tau_minus parameter of the " + f"{self.__class__.__name__} between " + f"{synapse_info.pre_population.label} and " + f"{synapse_info.post_population.label} was {tauminus} " + f"rather than 0, indicating that the lookup table was " + f"not big enough at this timestep and value. Try " + f"reducing the parameter value, or increasing the " + f"timestep.") + + @overrides(AbstractTimingDependence.get_parameter_names) + def get_parameter_names(self): + return ['tau_plus', 'tau_minus'] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/__init__.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/__init__.py index 5cad1ded70c..9b5a0447801 100644 --- a/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/__init__.py +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/__init__.py @@ -17,7 +17,10 @@ from .weight_dependence_additive import WeightDependenceAdditive from .weight_dependence_multiplicative import WeightDependenceMultiplicative from .weight_dependence_additive_triplet import WeightDependenceAdditiveTriplet +from .weight_dependence_pfpc import WeightDependencePFPC +from .weight_dependence_mfvn import WeightDependenceMFVN __all__ = ["AbstractHasAPlusAMinus", "AbstractWeightDependence", "WeightDependenceAdditive", "WeightDependenceMultiplicative", - "WeightDependenceAdditiveTriplet"] + "WeightDependenceAdditiveTriplet", "WeightDependencePFPC", + "WeightDependenceMFVN"] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_mfvn.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_mfvn.py new file mode 100644 index 00000000000..5c1b41cca7c --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_mfvn.py @@ -0,0 +1,109 @@ +# Copyright (c) 2017-2021 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from spinn_utilities.overrides import overrides +from spinn_front_end_common.interface.ds import DataType +from .abstract_has_a_plus_a_minus import AbstractHasAPlusAMinus +from .abstract_weight_dependence import AbstractWeightDependence + + +class WeightDependenceMFVN( + AbstractHasAPlusAMinus, AbstractWeightDependence): + __slots__ = [ + "__w_max", + "__w_min", + "__pot_alpha" + ] + + # noinspection PyPep8Naming + def __init__(self, w_min=0.0, w_max=1.0, pot_alpha=0.01): + super(WeightDependenceMFVN, self).__init__() + self.__w_min = w_min + self.__w_max = w_max + self.__pot_alpha = pot_alpha + + @property + def w_min(self): + return self.__w_min + + @property + def w_max(self): + return self.__w_max + + @property + def pot_alpha(self): + return self.__pot_alpha + + @pot_alpha.setter + def pot_alpha(self, new_value): + self.__pot_alpha = new_value + + @overrides(AbstractWeightDependence.is_same_as) + def is_same_as(self, weight_dependence): + if not isinstance(weight_dependence, WeightDependenceMFVN): + return False + return ( + (self.__w_min == weight_dependence.w_min) and + (self.__w_max == weight_dependence.w_max) and + (self.A_plus == weight_dependence.A_plus) and + (self.A_minus == weight_dependence.A_minus)) + + @property + def vertex_executable_suffix(self): + return "mfvn" + + @overrides(AbstractWeightDependence.get_parameters_sdram_usage_in_bytes) + def get_parameters_sdram_usage_in_bytes( + self, n_synapse_types, n_weight_terms): + if n_weight_terms != 1: + raise NotImplementedError( + "MFVN weight dependence only supports one term") + return (4 * 4) * n_synapse_types + + @overrides(AbstractWeightDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales, + n_weight_terms): + # Loop through each synapse type's weight scale + for _ in synapse_weight_scales: + + # Scale the weights + spec.write_value( + data=self.__w_min * global_weight_scale, + data_type=DataType.S1615) + spec.write_value( + data=self.__w_max * global_weight_scale, + data_type=DataType.S1615) + + # Pre-multiply weight parameters by Wmax? + # I don't know what's going on here, this is weird + # If this works the parameter needs renaming on the C side + # otherwise it's just confusing + spec.write_value( + data=self.__pot_alpha * global_weight_scale, + data_type=DataType.S1615) + + # This parameter is actually currently unused + # (I'm not convinced that's true...) + spec.write_value( + data=self.A_minus * global_weight_scale, + data_type=DataType.S1615) + + @property + def weight_maximum(self): + return self.__w_max + + @overrides(AbstractWeightDependence.get_parameter_names) + def get_parameter_names(self): + return ['w_min', 'w_max', 'A_plus', 'A_minus', 'pot_alpha'] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_pfpc.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_pfpc.py new file mode 100644 index 00000000000..d09b074bd25 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_pfpc.py @@ -0,0 +1,109 @@ +# Copyright (c) 2017-2021 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from spinn_utilities.overrides import overrides +from spinn_front_end_common.interface.ds import DataType +from .abstract_has_a_plus_a_minus import AbstractHasAPlusAMinus +from .abstract_weight_dependence import AbstractWeightDependence + + +class WeightDependencePFPC( + AbstractHasAPlusAMinus, AbstractWeightDependence): + __slots__ = [ + "__w_max", + "__w_min", + "__pot_alpha" + ] + + # noinspection PyPep8Naming + def __init__(self, w_min=0.0, w_max=1.0, pot_alpha=0.01): + super(WeightDependencePFPC, self).__init__() + self.__w_min = w_min + self.__w_max = w_max + self.__pot_alpha = pot_alpha + + @property + def w_min(self): + return self.__w_min + + @property + def w_max(self): + return self.__w_max + + @property + def pot_alpha(self): + return self.__pot_alpha + + @pot_alpha.setter + def pot_alpha(self, new_value): + self.__pot_alpha = new_value + + @overrides(AbstractWeightDependence.is_same_as) + def is_same_as(self, weight_dependence): + if not isinstance(weight_dependence, WeightDependencePFPC): + return False + return ( + (self.__w_min == weight_dependence.w_min) and + (self.__w_max == weight_dependence.w_max) and + (self.A_plus == weight_dependence.A_plus) and + (self.A_minus == weight_dependence.A_minus)) + + @property + def vertex_executable_suffix(self): + return "pfpc" + + @overrides(AbstractWeightDependence.get_parameters_sdram_usage_in_bytes) + def get_parameters_sdram_usage_in_bytes( + self, n_synapse_types, n_weight_terms): + if n_weight_terms != 1: + raise NotImplementedError( + "PFPC weight dependence only supports one term") + return (4 * 4) * n_synapse_types + + @overrides(AbstractWeightDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales, + n_weight_terms): + # Loop through each synapse type's weight scale + for _ in synapse_weight_scales: + + # Scale the weights + spec.write_value( + data=self.__w_min * global_weight_scale, + data_type=DataType.S1615) + spec.write_value( + data=self.__w_max * global_weight_scale, + data_type=DataType.S1615) + + # Pre-multiply weight parameters by Wmax? + # I don't know what's going on here, this is weird + # If this works the parameter needs renaming on the C side + # otherwise it's just confusing + spec.write_value( + data=self.__pot_alpha * global_weight_scale, + data_type=DataType.S1615) + + # This parameter is actually currently unused + # (I'm not convinced that's true...) + spec.write_value( + data=self.A_minus * global_weight_scale, + data_type=DataType.S1615) + + @property + def weight_maximum(self): + return self.__w_max + + @overrides(AbstractWeightDependence.get_parameter_names) + def get_parameter_names(self): + return ['w_min', 'w_max', 'A_plus', 'A_minus', "pot_alpha"] diff --git a/spynnaker/pyNN/models/neuron/population_synapses_machine_vertex_common.py b/spynnaker/pyNN/models/neuron/population_synapses_machine_vertex_common.py index 10aa0238234..567f4924d76 100644 --- a/spynnaker/pyNN/models/neuron/population_synapses_machine_vertex_common.py +++ b/spynnaker/pyNN/models/neuron/population_synapses_machine_vertex_common.py @@ -67,7 +67,21 @@ class SpikeProcessingFastProvenance(ctypes.LittleEndianStructure): # The latest time a spike was received ("latest_receive", ctypes.c_uint32), # The maximum overflow of spikes in a time step - ("max_spikes_overflow", ctypes.c_uint32) + ("max_spikes_overflow", ctypes.c_uint32), + # Custom provenance from SpiNNCer - max spikes in a tick + ("max_spikes_in_a_tick", ctypes.c_uint32), + # max dmas in a tick + ("max_dmas_in_a_tick", ctypes.c_uint32), + # max pipeline restarts + ("max_pipeline_restarts", ctypes.c_uint32), + # timer callback completed? + ("timer_callback_completed", ctypes.c_uint32), + # spikes pipeline activated? + ("spikes_pipeline_activated", ctypes.c_uint32), + # Max flushed spikes in a timestep + ("max_flushed_spikes", ctypes.c_uint32), + # Total flushed spikes + ("total_flushed_spikes", ctypes.c_uint32) ] N_ITEMS = len(_fields_) @@ -95,6 +109,16 @@ class PopulationSynapsesMachineVertexCommon( LATEST_RECEIVE = "Latest_receive_time" MAX_SPIKE_OVERFLOW = "Max_spike_overflow_in_time_step" + # Custom provenance from SpiNNCer + MAX_SPIKES_IN_A_TICK = "Maximum number of spikes in a timer tick" + MAX_DMAS_IN_A_TICK = "Maximum number of DMAs in a timer tick" + MAX_PIPELINE_RESTARTS = "Maximum pipeline restarts" + TIMER_CALLBACK_COMPLETED = "Was the timer callback completed?" + SPIKES_PIPELINE_ACTIVATED = "Was the spikes pipeline activated?" + # Flushed spikes + MAX_FLUSHED_SPIKES = "Maximum number of spikes flushed in a timer tick" + TOTAL_FLUSHED_SPIKES = "Total number of spikes flushed" + __slots__ = [ "__sdram_partition", "__neuron_vertex", @@ -374,3 +398,47 @@ def _parse_spike_processing_fast_provenance( x, y, p, self.LATEST_RECEIVE, prov.latest_receive) db.insert_core( x, y, p, self.MAX_SPIKE_OVERFLOW, prov.max_spikes_overflow) + + # SpiNNCer + db.insert_core( + x, y, p, self.MAX_SPIKES_IN_A_TICK, + prov.max_spikes_in_a_tick) + if prov.max_spikes_in_a_tick > 200: + db.insert_report( + f"Max number of spikes for {label} was " + f"{prov.max_spikes_in_a_tick}. Empirically, we " + f"can deal with ~200 for real time performance using a " + f"1.0 ms timestep.") + + db.insert_core( + x, y, p, self.MAX_DMAS_IN_A_TICK, + prov.max_dmas_in_a_tick) + + db.insert_core( + x, y, p, self.MAX_PIPELINE_RESTARTS, + prov.max_pipeline_restarts) + + db.insert_core( + x, y, p, self.TIMER_CALLBACK_COMPLETED, + prov.timer_callback_completed) + + db.insert_core( + x, y, p, self.SPIKES_PIPELINE_ACTIVATED, + prov.spikes_pipeline_activated) + + # FLUSHED SPIKES + db.insert_core( + x, y, p, self.MAX_FLUSHED_SPIKES, + prov.max_flushed_spikes) + if prov.max_flushed_spikes > 0: + db.insert_report( + f"Max number of flushed spikes for {label} was " + f"was {prov.max_flushed_spikes}.") + + db.insert_core( + x, y, p, self.TOTAL_FLUSHED_SPIKES, + prov.total_flushed_spikes) + if prov.total_flushed_spikes > 0: + db.insert_report( + f"Total number of flushed spikes for {label} was " + f"{prov.total_flushed_spikes}.") diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py index 96037035219..d44c7169986 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/abstract_synapse_dynamics.py @@ -190,12 +190,10 @@ def get_weight_variance(self, connector, weights, synapse_info): """ return connector.get_weight_variance(weights, synapse_info) - def get_provenance_data(self, pre_population_label, post_population_label): - """ - Get the provenance data from this synapse dynamics object. + def get_provenance_data(self, synapse_info): + """ Get the provenance data from this synapse dynamics object - :param str pre_population_label: - :param str post_population_label: + :param SynapseInformation synapse_info: :rtype: iterable(~spinn_front_end_common.utilities.utility_objs.ProvenanceDataItem) """ diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py index f837436132e..4fd30685106 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py @@ -592,9 +592,17 @@ def delay(self): @property @overrides(AbstractPlasticSynapseDynamics.is_combined_core_capable) def is_combined_core_capable(self): - return self.__neuromodulation is None + timing_suffix = self.__timing_dependence.vertex_executable_suffix + weight_suffix = self.__weight_dependence.vertex_executable_suffix + + return ((self.__neuromodulation is None) and + (timing_suffix != "mfvn") and (timing_suffix != "pfpc") and + (weight_suffix != "mfvn") and (weight_suffix != "pfpc")) @property @overrides(AbstractPlasticSynapseDynamics.pad_to_length) def pad_to_length(self): return self.__pad_to_length + + def get_provenance_data(self, synapse_info): + self.__timing_dependence.get_provenance_data(synapse_info) diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_utils.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_utils.py index 42eafcf1c36..27d3c66b6d1 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_utils.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_utils.py @@ -66,7 +66,7 @@ def calculate_spike_pair_additive_stdp_weight( (depression_times / tau_minus)) print("Potentiations: ", potentiation_times, potentiations) - print("Depressions:", depression_times, depressions) + print("Depressions: ", depression_times, depressions) return initial_weight + numpy.sum(potentiations) - numpy.sum(depressions) diff --git a/spynnaker/pyNN/models/populations/population.py b/spynnaker/pyNN/models/populations/population.py index 754d28eb853..c3b95eb66a3 100644 --- a/spynnaker/pyNN/models/populations/population.py +++ b/spynnaker/pyNN/models/populations/population.py @@ -827,6 +827,17 @@ def __create_vertex( "Model must be either an AbstractPyNNModel or a" " PopulationApplicationVertex") + # Getting custom RB LS + if (additional_parameters is not None and + "rb_left_shifts" in additional_parameters.keys()): + rb_left_shifts = additional_parameters['rb_left_shifts'] + else: + rb_left_shifts = None + + # Setting custom RB LS + if rb_left_shifts is not None: + self.__vertex.set_rb_left_shifts(rb_left_shifts) + @staticmethod def create(cellclass, cellparams=None, n=1): """ diff --git a/spynnaker/pyNN/spinnaker.py b/spynnaker/pyNN/spinnaker.py index d6c2faf73b5..58564233dc4 100644 --- a/spynnaker/pyNN/spinnaker.py +++ b/spynnaker/pyNN/spinnaker.py @@ -97,6 +97,9 @@ def __init__( # pynn demanded objects self.__recorders = set([]) + # Structured provenance_items + self.structured_provenance_filename = None + # main pynn interface inheritance pynn_control.BaseState.__init__(self) diff --git a/spynnaker/pyNN/spynnaker_external_device_plugin_manager.py b/spynnaker/pyNN/spynnaker_external_device_plugin_manager.py index 064c63b4e12..987b30b3090 100644 --- a/spynnaker/pyNN/spynnaker_external_device_plugin_manager.py +++ b/spynnaker/pyNN/spynnaker_external_device_plugin_manager.py @@ -24,6 +24,7 @@ from spynnaker.pyNN.utilities.constants import ( LIVE_POISSON_CONTROL_PARTITION_ID, SPIKE_PARTITION_ID) from spynnaker.pyNN.models.populations import Population +from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVertex class SpynnakerExternalDevicePluginManager(object): @@ -185,8 +186,10 @@ def activate_live_output_to( # pylint: disable=protected-access if isinstance(device, Population): device_vertex = device._vertex - SpynnakerExternalDevicePluginManager.add_edge( + edge = SpynnakerExternalDevicePluginManager.add_edge( population._vertex, device_vertex, partition_id) + if isinstance(device_vertex, SpikeSourcePoissonVertex): + device_vertex.set_live_poisson_control_edge(edge) @staticmethod def update_live_packet_gather_tracker( diff --git a/unittests/model_tests/neuron/test_plastic_lut.py b/unittests/model_tests/neuron/test_plastic_lut.py new file mode 100644 index 00000000000..4104b1064d6 --- /dev/null +++ b/unittests/model_tests/neuron/test_plastic_lut.py @@ -0,0 +1,52 @@ +# Copyright (c) 2017-2019 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +import unittest +from spynnaker.pyNN.config_setup import unittest_setup +from spynnaker.pyNN.models.neuron.plasticity.stdp.common import ( + write_mfvn_lut, write_pfpc_lut) + + +class TestPlasticLUT(unittest.TestCase): + + def setUp(self): + unittest_setup() + + def test_plastic_mfvn_lut(self): + t_mfvn, out_float_mfvn, _plot_times = write_mfvn_lut( + spec=None, sigma=200, beta=10, lut_size=256, shift=0, + time_probe=22) + + t_compare = [15, 20, 30, 35, 45, 47, 99, 115, 135, 140, 150] + mfvn_float = [0.4697, 0.3642, 0.2181, 0.1685, 0.1002, 0.0902, 0.0055, + 0.0022, 0.0007, 0.0005, 0.0003] + print(t_mfvn, out_float_mfvn[t_mfvn], mfvn_float) + assert list(t_mfvn) == t_compare + self.assertTrue(numpy.allclose(out_float_mfvn[t_mfvn], mfvn_float, + atol=0.0001)) + + def test_plastic_pfpc_lut(self): + t_pfpc, out_float_pfpc, out_fixed_pfpc, _plot_times = write_pfpc_lut( + spec=None, peak_time=100, lut_size=256, shift=0, time_probe=100) + + t_compare = [15, 20, 30, 35, 45, 47, 99, 115, 135, 140, 150] + pfpc_fixed = [0, 0, 0, 0, 1, 1, 2043, 1215, 109, 42, 4] + pfpc_float = [0.0, 0.0, 0.0, 0.0, 0.0002, 0.0005, + 0.9977, 0.5932, 0.0534, 0.0207, 0.0019] + print(t_pfpc, out_float_pfpc[t_pfpc], out_fixed_pfpc[t_pfpc]) + assert list(t_pfpc) == t_compare + assert list(out_fixed_pfpc[t_pfpc]) == pfpc_fixed + self.assertTrue(numpy.allclose(out_float_pfpc[t_pfpc], pfpc_float, + atol=0.0001)) diff --git a/unittests/model_tests/neuron/test_synaptic_manager.py b/unittests/model_tests/neuron/test_synaptic_manager.py index 441abb62b02..f1eb1dbc671 100644 --- a/unittests/model_tests/neuron/test_synaptic_manager.py +++ b/unittests/model_tests/neuron/test_synaptic_manager.py @@ -231,7 +231,7 @@ def test_set_synapse_dynamics(): n_neurons=10, label="post", spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None, - seed=None, n_colour_bits=None) + seed=None, n_colour_bits=None, rb_left_shifts=None) static = SynapseDynamicsStatic() stdp = SynapseDynamicsSTDP( @@ -342,7 +342,7 @@ def test_set_synapse_dynamics(): n_neurons=10, label="post", spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None, - seed=None, n_colour_bits=None) + seed=None, n_colour_bits=None, rb_left_shifts=None) # STDP followed by structural STDP should result in Structural STDP post_app_vertex.synapse_dynamics = stdp @@ -365,7 +365,7 @@ def test_set_synapse_dynamics(): n_neurons=10, label="post", spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None, - seed=None, n_colour_bits=None) + seed=None, n_colour_bits=None, rb_left_shifts=None) # Static followed by static structural should result in static # structural @@ -403,7 +403,7 @@ def test_set_synapse_dynamics(): n_neurons=10, label="post", spikes_per_second=None, ring_buffer_sigma=None, incoming_spike_buffer_size=None, n_steps_per_timestep=1, drop_late_spikes=True, splitter=None, - seed=None, n_colour_bits=None) + seed=None, n_colour_bits=None, rb_left_shifts=None) post_app_vertex.synapse_dynamics = static_struct post_app_vertex.synapse_dynamics = stdp_struct diff --git a/unittests/test_populations/test_vertex.py b/unittests/test_populations/test_vertex.py index fde6e56eec0..41c8e3eb50b 100644 --- a/unittests/test_populations/test_vertex.py +++ b/unittests/test_populations/test_vertex.py @@ -106,7 +106,7 @@ def __init__(self): ring_buffer_sigma=None, incoming_spike_buffer_size=None, neuron_impl=foo_bar.model, pynn_model=foo_bar, drop_late_spikes=True, splitter=None, seed=None, - n_colour_bits=None) + n_colour_bits=None, rb_left_shifts=None) def test_initializable():