diff --git a/neural_modelling/makefiles/neuron/Makefile b/neural_modelling/makefiles/neuron/Makefile index 94fa2fc37fd..978b944348b 100644 --- a/neural_modelling/makefiles/neuron/Makefile +++ b/neural_modelling/makefiles/neuron/Makefile @@ -43,7 +43,12 @@ ifneq ($(SPYNNAKER_DEBUG), DEBUG) IF_curr_delta_stdp_mad_pair_additive \ IF_curr_delta_stdp_mad_nearest_pair_additive \ IF_curr_exp_stdp_mad_recurrent_pre_stochastic_multiplicative \ - IF_curr_exp_stdp_mad_pfister_triplet_additive + IF_curr_exp_stdp_mad_pfister_triplet_additive \ + eprop_adaptive \ + eprop_adaptive_stdp_mad_eprop_reg \ + sinusoid_readout \ + sinusoid_readout_stdp_mad_eprop_reg \ + left_right_readout_stdp_mad_eprop_reg endif all: diff --git a/neural_modelling/makefiles/neuron/eprop_adaptive/Makefile b/neural_modelling/makefiles/neuron/eprop_adaptive/Makefile new file mode 100644 index 00000000000..6b0485e786a --- /dev/null +++ b/neural_modelling/makefiles/neuron/eprop_adaptive/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) 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 +# +# https://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_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_eprop_adaptive.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/synapse_dynamics_static_impl.c + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/neuron/eprop_adaptive_stdp_mad_eprop_reg/Makefile b/neural_modelling/makefiles/neuron/eprop_adaptive_stdp_mad_eprop_reg/Makefile new file mode 100644 index 00000000000..df5d8d7e0f7 --- /dev/null +++ b/neural_modelling/makefiles/neuron/eprop_adaptive_stdp_mad_eprop_reg/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) 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 +# +# https://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_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_eprop_adaptive.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_eprop_adaptive_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/neuron/left_right_readout_stdp_mad_eprop_reg/Makefile b/neural_modelling/makefiles/neuron/left_right_readout_stdp_mad_eprop_reg/Makefile new file mode 100644 index 00000000000..e4e26a7e930 --- /dev/null +++ b/neural_modelling/makefiles/neuron/left_right_readout_stdp_mad_eprop_reg/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) 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 +# +# https://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_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_left_right_readout.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_left_right_readout_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/neuron/sinusoid_readout/Makefile b/neural_modelling/makefiles/neuron/sinusoid_readout/Makefile new file mode 100644 index 00000000000..f001d2cdd86 --- /dev/null +++ b/neural_modelling/makefiles/neuron/sinusoid_readout/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) 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 +# +# https://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_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_sinusoid_readout.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/synapse_dynamics_static_impl.c + +include ../neural_build.mk diff --git a/neural_modelling/makefiles/neuron/sinusoid_readout_stdp_mad_eprop_reg/Makefile b/neural_modelling/makefiles/neuron/sinusoid_readout_stdp_mad_eprop_reg/Makefile new file mode 100644 index 00000000000..c17c28711be --- /dev/null +++ b/neural_modelling/makefiles/neuron/sinusoid_readout_stdp_mad_eprop_reg/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) 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 +# +# https://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_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_sinusoid_readout.h +SYNAPSE_DYNAMICS = $(NEURON_DIR)/neuron/plasticity/stdp/synapse_dynamics_sinusoid_readout_impl.c +TIMING_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c +TIMING_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h +WEIGHT_DEPENDENCE = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c +WEIGHT_DEPENDENCE_H = $(NEURON_DIR)/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h + +include ../neural_build.mk diff --git a/neural_modelling/src/neuron/c_main.c b/neural_modelling/src/neuron/c_main.c index 40a98fe8085..4a8e2d94d41 100644 --- a/neural_modelling/src/neuron/c_main.c +++ b/neural_modelling/src/neuron/c_main.c @@ -150,6 +150,8 @@ void resume_callback(void) { static inline void process_ring_buffers(void) { uint32_t first_index = synapse_row_get_first_ring_buffer_index( time, synapse_type_index_bits, synapse_delay_mask); +// log_info("process_ring_buffers, synapse_delay_mask is %u first_index %u ring_buffer %d", +// synapse_delay_mask, first_index, ring_buffers[first_index]); neuron_transfer(&ring_buffers[first_index]); // Print the neuron inputs. diff --git a/neural_modelling/src/neuron/current_sources/current_source.h b/neural_modelling/src/neuron/current_sources/current_source.h index f1fd058dd5c..3d60ccdaaed 100644 --- a/neural_modelling/src/neuron/current_sources/current_source.h +++ b/neural_modelling/src/neuron/current_sources/current_source.h @@ -150,7 +150,7 @@ static bool current_source_load_parameters(address_t cs_address) { // Avoid the loops if no current sources #if !defined(_CURRENT_SOURCE_DC_H_) && !defined(_CURRENT_SOURCE_AC_H) && \ !defined(_CURRENT_SOURCE_STEP_H_) && !defined(_CURRENT_SOURCE_NOISY_H_) - io_printf(IO_BUF, "no current sources defined \n"); +// io_printf(IO_BUF, "no current sources defined \n"); return true; #else diff --git a/neural_modelling/src/neuron/implementations/neuron_impl_eprop_adaptive.h b/neural_modelling/src/neuron/implementations/neuron_impl_eprop_adaptive.h new file mode 100644 index 00000000000..2150b2744a5 --- /dev/null +++ b/neural_modelling/src/neuron/implementations/neuron_impl_eprop_adaptive.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_IMPL_EPROP_ADAPTIVE_H_ +#define _NEURON_IMPL_EPROP_ADAPTIVE_H_ + +#include "neuron_impl.h" + +// Includes for model parts used in this implementation +#include +#include +#include +#include +#include + +#include + +// Further includes +#include +#include + +//! Indices for recording of words +enum word_recording_indices { + //! V (somatic potential) recording index + V_RECORDING_INDEX = 0, + //! Gsyn_exc (excitatory synaptic conductance/current) recording index + GSYN_EXC_RECORDING_INDEX = 1, + //! Gsyn_inh (excitatory synaptic conductance/current) recording index + GSYN_INH_RECORDING_INDEX = 2, + //! Number of recorded word-sized state variables + N_RECORDED_VARS = 3 +}; + +//! Indices for recording of bitfields +enum bitfield_recording_indices { + //! Spike event recording index + SPIKE_RECORDING_BITFIELD = 0, + //! Number of recorded bitfields + N_BITFIELD_VARS = 1 +}; + +#ifndef NUM_EXCITATORY_RECEPTORS +#define NUM_EXCITATORY_RECEPTORS 1 +#error NUM_EXCITATORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#ifndef NUM_INHIBITORY_RECEPTORS +#define NUM_INHIBITORY_RECEPTORS 1 +#error NUM_INHIBITORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#include + +extern REAL learning_signal; +uint32_t neuron_impl_neurons_in_partition; + +//! Array of neuron states +neuron_t *neuron_array; + +//! Input states array +static input_type_t *input_type_array; + +//! Additional input array +static additional_input_t *additional_input_array; + +//! Threshold states array +static threshold_type_t *threshold_type_array; + +//! The synapse shaping parameters +static synapse_types_t *synapse_types_array; + +//! The number of steps to run per timestep (not set in this impl yet) +static uint n_steps_per_timestep; + +static bool neuron_impl_initialise(uint32_t n_neurons) { + + // Allocate DTCM for neuron array + if (sizeof(neuron_t)) { + neuron_array = spin1_malloc(n_neurons * sizeof(neuron_t)); + if (neuron_array == NULL) { + log_error("Unable to allocate neuron array - Out of DTCM %u %u", + n_neurons, sizeof(neuron_t)); + return false; + } + } + + // Allocate DTCM for input type array and copy block of data + if (sizeof(input_type_t)) { + input_type_array = spin1_malloc(n_neurons * sizeof(input_type_t)); + if (input_type_array == NULL) { + log_error("Unable to allocate input type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for additional input array and copy block of data + if (sizeof(additional_input_t)) { + additional_input_array = + spin1_malloc(n_neurons * sizeof(additional_input_t)); + if (additional_input_array == NULL) { + log_error("Unable to allocate additional input array" + " - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for threshold type array and copy block of data + if (sizeof(threshold_type_t)) { + threshold_type_array = + spin1_malloc(n_neurons * sizeof(threshold_type_t)); + if (threshold_type_array == NULL) { + log_error("Unable to allocate threshold type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for synapse shaping parameters + if (sizeof(synapse_types_t)) { + synapse_types_array = + spin1_malloc(n_neurons * sizeof(synapse_types_t)); + if (synapse_types_array == NULL) { + log_error("Unable to allocate synapse types array - Out of DTCM"); + return false; + } + } + + return true; +} + +static void neuron_impl_add_inputs( + index_t synapse_type_index, index_t neuron_index, + input_t weights_this_timestep) { + // simple wrapper to synapse type input function + synapse_types_t *parameters = &synapse_types_array[neuron_index]; + synapse_types_add_neuron_input(synapse_type_index, + parameters, weights_this_timestep); +} + +static uint32_t n_words_needed(uint32_t size) { + return (size + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); +} + +static void neuron_impl_load_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons, + address_t save_initial_state) { + log_debug("reading parameters, next is %u, n_neurons is %u ", + next, n_neurons); + + // get number of neurons running on this core for use during execution + neuron_impl_neurons_in_partition = n_neurons; + + // Read the number of steps per timestep + n_steps_per_timestep = address[next++]; + if (n_steps_per_timestep == 0) { + log_error("bad number of steps per timestep: 0"); + rt_error(RTE_SWERR); + } + + if (sizeof(neuron_t)) { + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_initialise(&neuron_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_initialise(&input_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_initialise(&threshold_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_initialise(&synapse_types_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_initialise(&additional_input_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } + + // If we are to save the initial state, copy the whole of the parameters + // to the initial state + if (save_initial_state) { + spin1_memcpy(save_initial_state, address, next * sizeof(uint32_t)); + } + + log_info("neuron_impl_load_neuron_parameters n_neurons %u", n_neurons); + log_info("local_eta %k", neuron_array[0].eta); + log_info("core pop rate %k", neuron_array[0].core_pop_rate); + log_info("core target rate %k", neuron_array[0].core_target_rate); + log_info("rate exp TC %k", neuron_array[0].rate_exp_TC); + for (index_t n = 0; n < n_neurons; n++) { + log_info("neuron index %u voltage %k", n, neuron_array[n].V_membrane); + log_info("neuron index %u core_pop_rate %k", n, neuron_array[n].core_pop_rate); + } + +#if LOG_LEVEL >= LOG_DEBUG + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + neuron_model_print_parameters(&neuron_array[n]); + neuron_model_print_state_variables(&neuron_array[n]); + } + log_debug("-------------------------------------\n"); +#endif // LOG_LEVEL >= LOG_DEBUG +} + +static void neuron_impl_do_timestep_update( + uint32_t timer_count, uint32_t time, uint32_t n_neurons) { + + // Decay the "global" rate trace (done once per timestep) + for (uint32_t n_ind=0; n_ind < n_neurons; n_ind++) { + neuron_t *global_neuron = &neuron_array[n_ind]; + global_neuron->core_pop_rate = + global_neuron->core_pop_rate * global_neuron->rate_exp_TC; + } + + for (uint32_t neuron_index = 0; neuron_index < n_neurons; neuron_index++) { + + // Get the neuron itself + neuron_t *neuron = &neuron_array[neuron_index]; + + // Get the input_type parameters and voltage for this neuron + input_type_t *input_type = &input_type_array[neuron_index]; + + // Get threshold and additional input parameters for this neuron + // TODO: for some reason threshold is incorporated into neuron_model + // for this neuron; investigate if it can be separated +// threshold_type_pointer_t threshold_type = +// &threshold_type_array[neuron_index]; + additional_input_t *additional_input = + &additional_input_array[neuron_index]; + synapse_types_t *synapse_type = + &synapse_types_array[neuron_index]; + + // TODO: This would be where a steps per timestep loop begins if desired + + // Get the voltage + state_t voltage = neuron_model_get_membrane_voltage(neuron); + state_t B_t = neuron->B; // cache last timestep threshold level + state_t z_t = neuron->z; + + // Get the exc and inh values from the synapses + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t* exc_syn_values = synapse_types_get_excitatory_input( + exc_values, synapse_type); + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t* inh_syn_values = synapse_types_get_inhibitory_input( + inh_values, synapse_type); + + // Call functions to obtain exc_input and inh_input + input_t* exc_input_values = input_type_get_input_value( + exc_syn_values, input_type, NUM_EXCITATORY_RECEPTORS); + input_t* inh_input_values = input_type_get_input_value( + inh_syn_values, input_type, NUM_INHIBITORY_RECEPTORS); + + // Call functions to convert exc_input and inh_input to current + input_type_convert_excitatory_input_to_current( + exc_input_values, input_type, voltage); + input_type_convert_inhibitory_input_to_current( + inh_input_values, input_type, voltage); + + // Get current offset + REAL current_offset = current_source_get_offset(time, neuron_index); + + // Get any additional bias + input_t external_bias = additional_input_get_input_value_as_current( + additional_input, voltage); + + // determine if a spike should occur + threshold_type_update_threshold(neuron->z, neuron); + + // Record different synapse delta_w for different indices + if ((neuron_index == 0) || (neuron_index == 1) || (neuron_index == 2)) { + neuron_recording_record_accum( + GSYN_INH_RECORDING_INDEX, neuron_index, + neuron->syn_state[10+neuron_index].delta_w); + } + else { + neuron_recording_record_accum( + GSYN_INH_RECORDING_INDEX, neuron_index, + neuron->syn_state[0+neuron_index].delta_w); + } + + // update neuron parameters + state_t result = neuron_model_state_update( + NUM_EXCITATORY_RECEPTORS, exc_input_values, + NUM_INHIBITORY_RECEPTORS, inh_input_values, + external_bias, current_offset, neuron, B_t); + + // Calculate and record regularised learning signal + REAL reg_learning_signal = kdivui( + neuron->core_pop_rate, neuron_impl_neurons_in_partition) - neuron->core_target_rate; + neuron_recording_record_accum( + GSYN_EXC_RECORDING_INDEX, neuron_index, reg_learning_signal); + + // Can't replace this (yet) with kdivk as that only works for positive accums + state_t nu = (voltage - neuron->B)/neuron->B; + // The below works but doesn't save ITCM +// state_t nu = kdivi(bitsk(voltage-neuron->B), bitsk(neuron->B)); + + // Also update Z (including using refractory period information) + if (nu > ZERO){ + neuron->z = 1.0k * neuron->A; // implements refractory period + } + + bool spike = z_t; + + // Record updated state + neuron_recording_record_accum(V_RECORDING_INDEX, neuron_index, voltage); + + // If spike occurs, communicate to relevant parts of model + if (spike) { + // Call relevant model-based functions + // Tell the neuron model + neuron_model_has_spiked(neuron); + + // Tell the additional input + additional_input_has_spiked(additional_input); + + // Add contribution from this neuron's spike to global rate trace + // Make sure it's the same across all neurons + for (uint32_t n_ind=0; n_ind < n_neurons; n_ind++) { + neuron_t *global_neuron = &neuron_array[n_ind]; + global_neuron->core_pop_rate += 1.0k; + } + + // Record spike + neuron_recording_record_bit(SPIKE_RECORDING_BITFIELD, neuron_index); + + // Send spike + send_spike(timer_count, time, neuron_index); + } + + // Shape the existing input according to the included rule + synapse_types_shape_input(synapse_type); + + // The steps per timestep loop would end here if it was used + +#if LOG_LEVEL >= LOG_DEBUG + neuron_model_print_state_variables(neuron); +#endif // LOG_LEVEL >= LOG_DEBUG + + } +} + +//! \brief stores neuron parameter back into sdram +//! \param[in] address: the address in sdram to start the store +static void neuron_impl_store_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons) { + log_debug("writing parameters"); + + // Skip steps per timestep + next += 1; + + if (sizeof(neuron_t)) { + log_debug("writing neuron local parameters"); + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_save_state(&neuron_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + log_debug("writing input type parameters"); + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_save_state(&input_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + log_debug("writing threshold type parameters"); + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_save_state(&threshold_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + log_debug("writing synapse parameters"); + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_save_state(&synapse_types_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + log_debug("writing additional input type parameters"); + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_save_state(&additional_input_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } + + // Not completely sure this next loop for printing is necessary + log_debug("****** STORING ******"); + for (index_t n = 0; n < n_neurons; n++) { + neuron_model_print_parameters(&neuron_array[n]); + log_debug("Neuron id %u", n); + neuron_model_print_state_variables(&neuron_array[n]); + } + log_debug("****** STORING COMPLETE ******"); + + log_info("neuron 0 'global' parameters, core_target_rate, core_pop_rate %k %k", + &neuron_array[0].core_target_rate, &neuron_array[0].core_pop_rate); +} + +#if LOG_LEVEL >= LOG_DEBUG +void neuron_impl_print_inputs(uint32_t n_neurons) { + bool empty = true; + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + empty = empty && (0 == bitsk( + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[0])); + } + + if (!empty) { + log_debug("-------------------------------------\n"); + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t input = + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[1]; + if (bitsk(input) != 0) { + log_debug("%3u: %12.6k (= ", i, input); + synapse_types_print_input(params); + log_debug(")\n"); + } + } + log_debug("-------------------------------------\n"); + } +} + +void neuron_impl_print_synapse_parameters(uint32_t n_neurons) { + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + synapse_types_print_parameters(&synapse_types_array[n]); + } + log_debug("-------------------------------------\n"); +} + +const char *neuron_impl_get_synapse_type_char(uint32_t synapse_type) { + return synapse_types_get_type_char(synapse_type); +} +#endif // LOG_LEVEL >= LOG_DEBUG + +#endif // _NEURON_IMPL_STANDARD_H_ diff --git a/neural_modelling/src/neuron/implementations/neuron_impl_external_devices.h b/neural_modelling/src/neuron/implementations/neuron_impl_external_devices.h index 5c10b1a927e..a3c65a1dd29 100644 --- a/neural_modelling/src/neuron/implementations/neuron_impl_external_devices.h +++ b/neural_modelling/src/neuron/implementations/neuron_impl_external_devices.h @@ -312,7 +312,7 @@ static void neuron_impl_do_timestep_update( state_t result = neuron_model_state_update( NUM_EXCITATORY_RECEPTORS, exc_input_values, NUM_INHIBITORY_RECEPTORS, inh_input_values, - 0, current_offset, this_neuron); + 0, current_offset, this_neuron, 0.0k); // determine if a packet should fly will_fire = _test_will_fire(the_packet_firing); diff --git a/neural_modelling/src/neuron/implementations/neuron_impl_left_right_readout.h b/neural_modelling/src/neuron/implementations/neuron_impl_left_right_readout.h new file mode 100644 index 00000000000..f9bf028a456 --- /dev/null +++ b/neural_modelling/src/neuron/implementations/neuron_impl_left_right_readout.h @@ -0,0 +1,636 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_IMPL_LEFT_RIGHT_READOUT_H_ +#define _NEURON_IMPL_LEFT_RIGHT_READOUT_H_ + +#include "neuron_impl.h" + +// Includes for model parts used in this implementation +#include +#include +#include +#include +#include + +#include + +// Further includes +#include +#include +#include +#include +#include + +//! Indices for recording of words +enum word_recording_indices { + //! V (somatic potential) recording index + V_RECORDING_INDEX = 0, + //! Gsyn_exc (excitatory synaptic conductance/current) recording index + GSYN_EXC_RECORDING_INDEX = 1, + //! Gsyn_inh (excitatory synaptic conductance/current) recording index + GSYN_INH_RECORDING_INDEX = 2, + //! Number of recorded word-sized state variables + N_RECORDED_VARS = 3 +}; + +//! Indices for recording of bitfields +enum bitfield_recording_indices { + //! Spike event recording index + SPIKE_RECORDING_BITFIELD = 0, + //! Number of recorded bitfields + N_BITFIELD_VARS = 1 +}; + +#ifndef NUM_EXCITATORY_RECEPTORS +#define NUM_EXCITATORY_RECEPTORS 1 +#error NUM_EXCITATORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#ifndef NUM_INHIBITORY_RECEPTORS +#define NUM_INHIBITORY_RECEPTORS 1 +#error NUM_INHIBITORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#include + +//! Array of neuron states +neuron_t *neuron_array; + +//! Input states array +static input_type_t *input_type_array; + +//! Additional input array +static additional_input_t *additional_input_array; + +//! Threshold states array +static threshold_type_t *threshold_type_array; + +// The synapse shaping parameters +static synapse_types_t *synapse_types_array; + +//! The number of steps to run per timestep +static uint n_steps_per_timestep; + +//! Whether key is set, from neuron.c +extern bool use_key; + +extern uint32_t *neuron_keys; +extern REAL learning_signal; + +// recording params (TODO: check these aren't needed?) +//uint32_t is_it_right = 0; +//uint32_t choice = 0; + +// Left right state parameters +typedef enum { + STATE_CUE, + STATE_WAITING, + STATE_PROMPT, +} left_right_state_t; + +// Left right parameters +// TODO: should these be set as parameters elsewhere or remain as constants +// that are just in the C code? +// (Conversely, should any current parameters become constants?) +left_right_state_t current_state = 0; +uint32_t current_time = 0; +uint32_t cue_number = 0; +uint32_t current_cue_direction = 2; // 0 = left, 1 = right +uint32_t accumulative_direction = 0; // if > total_cues / 2 = right +uint32_t wait_between_cues = 50; // ms +uint32_t duration_of_cue = 100; // ms +uint32_t wait_before_result = 1000; // ms but should be a range between 500-1500 +uint32_t prompt_duration = 150; //ms +bool start_prompt = false; +REAL softmax_0 = 0k; +REAL softmax_1 = 0k; +bool completed_broadcast = true; + + +static bool neuron_impl_initialise(uint32_t n_neurons) { + + // Allocate DTCM for neuron array + if (sizeof(neuron_t) != 0) { + neuron_array = spin1_malloc(n_neurons * sizeof(neuron_t)); + if (neuron_array == NULL) { + log_error("Unable to allocate neuron array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for input type array and copy block of data + if (sizeof(input_type_t) != 0) { + input_type_array = spin1_malloc(n_neurons * sizeof(input_type_t)); + if (input_type_array == NULL) { + log_error("Unable to allocate input type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for additional input array and copy block of data + if (sizeof(additional_input_t) != 0) { + additional_input_array = spin1_malloc( + n_neurons * sizeof(additional_input_t)); + if (additional_input_array == NULL) { + log_error("Unable to allocate additional input array" + " - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for threshold type array and copy block of data + if (sizeof(threshold_type_t) != 0) { + threshold_type_array = spin1_malloc( + n_neurons * sizeof(threshold_type_t)); + if (threshold_type_array == NULL) { + log_error("Unable to allocate threshold type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for synapse shaping parameters + if (sizeof(synapse_types_t) != 0) { + synapse_types_array = spin1_malloc( + n_neurons * sizeof(synapse_types_t)); + if (synapse_types_array == NULL) { + log_error("Unable to allocate synapse parameters array" + " - Out of DTCM"); + return false; + } + } + + // Seed the random input + validate_mars_kiss64_seed(neuron_array->kiss_seed); + + return true; +} + +static void neuron_impl_add_inputs( + index_t synapse_type_index, index_t neuron_index, + input_t weights_this_timestep) { + // simple wrapper to synapse type input function + synapse_types_t *parameters = + &(synapse_types_array[neuron_index]); + synapse_types_add_neuron_input(synapse_type_index, + parameters, weights_this_timestep); +} + +static uint32_t n_words_needed(uint32_t size) { + return (size + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); +} + +static void neuron_impl_load_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons, + address_t save_initial_state) { + log_debug("reading parameters, next is %u, n_neurons is %u ", + next, n_neurons); + + // Number of steps per timestep + n_steps_per_timestep = address[next++]; + if (n_steps_per_timestep == 0) { + log_error("bad number of steps per timestep: 0"); + rt_error(RTE_SWERR); + } + + if (sizeof(neuron_t)) { + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_initialise(&neuron_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_initialise(&input_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_initialise(&threshold_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_initialise(&synapse_types_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_initialise(&additional_input_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } + + // If we are to save the initial state, copy the whole of the parameters + // to the initial state + if (save_initial_state) { + spin1_memcpy(save_initial_state, address, next * sizeof(uint32_t)); + } + + #if LOG_LEVEL >= LOG_DEBUG + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + neuron_model_print_parameters(&neuron_array[n]); + } + log_debug("-------------------------------------\n"); + //} + #endif // LOG_LEVEL >= LOG_DEBUG +} + +static void neuron_impl_do_timestep_update( + uint32_t timer_count, uint32_t time, uint32_t n_neurons) { + + for (uint32_t neuron_index = 0; neuron_index < n_neurons; neuron_index++) { + + // Get the neuron itself + neuron_t *neuron = &neuron_array[neuron_index]; + + // Get the input_type parameters and voltage for this neuron + input_type_t *input_type = &input_type_array[neuron_index]; + + // Get threshold and additional input parameters for this neuron + threshold_type_t *threshold_type = + &threshold_type_array[neuron_index]; + additional_input_t *additional_input = + &additional_input_array[neuron_index]; + synapse_types_t *synapse_type = + &synapse_types_array[neuron_index]; + + // Get the voltage + state_t voltage = neuron_model_get_membrane_voltage(neuron); + + // Get the exc and inh values from the synapses + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t* exc_syn_values = synapse_types_get_excitatory_input( + exc_values, synapse_type); + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t* inh_syn_values = synapse_types_get_inhibitory_input( + inh_values, synapse_type); + + // Call functions to obtain exc_input and inh_input + input_t* exc_input_values = input_type_get_input_value( + exc_syn_values, input_type, NUM_EXCITATORY_RECEPTORS); + input_t* inh_input_values = input_type_get_input_value( + inh_syn_values, input_type, NUM_INHIBITORY_RECEPTORS); + + // Call functions to convert exc_input and inh_input to current + input_type_convert_excitatory_input_to_current( + exc_input_values, input_type, voltage); + input_type_convert_inhibitory_input_to_current( + inh_input_values, input_type, voltage); + + REAL current_offset = current_source_get_offset(time, neuron_index); + + input_t external_bias = additional_input_get_input_value_as_current( + additional_input, voltage); + + if (neuron_index == 0){ + // update neuron parameters + state_t result = neuron_model_state_update( + NUM_EXCITATORY_RECEPTORS, exc_input_values, + NUM_INHIBITORY_RECEPTORS, inh_input_values, + external_bias, current_offset, neuron, -50k); + // Finally, set global membrane potential to updated value + for (uint32_t glob_n = 0; glob_n < n_neurons; glob_n++) { + // Get the neuron itself + neuron_t *glob_neuron = &neuron_array[glob_n]; + glob_neuron->readout_V_0 = result; + } + } else if (neuron_index == 1){ + // update neuron parameters + state_t result = neuron_model_state_update( + NUM_EXCITATORY_RECEPTORS, exc_input_values, + NUM_INHIBITORY_RECEPTORS, inh_input_values, + external_bias, current_offset, neuron, -50k); + + // Finally, set global membrane potential to updated value + for (uint32_t glob_n = 0; glob_n < n_neurons; glob_n++) { + // Get the neuron itself + neuron_t *glob_neuron = &neuron_array[glob_n]; + glob_neuron->readout_V_1 = result; + } + } + + if (cue_number == 0 && completed_broadcast){ // reset start of new test + completed_broadcast = false; + current_time = time; + current_state = STATE_CUE; + accumulative_direction = 0; + // error params + neuron->cross_entropy = 0.k; + learning_signal = 0.k; + neuron->mean_0 = 0.k; + neuron->mean_1 = 0.k; + softmax_0 = 0k; + softmax_1 = 0k; + if (use_key) { + // This sends a "completed" signal + send_spike_mc_payload( + neuron_keys[neuron_index], bitsk(neuron->cross_entropy)); + } + } + + // In this state the environment is giving the left/right cues to the agent + if (current_state == STATE_CUE) { + if (neuron_index == 0) { + // if it's currently in the waiting time between cues do nothing + + // Otherwise, begin sending left/right cue + if ((time - current_time) % + (wait_between_cues + duration_of_cue) >= wait_between_cues) { + // pick broadcast if just entered + if ((time - current_time) % + (wait_between_cues + duration_of_cue) == wait_between_cues){ + // pick new value and broadcast +// REAL random_value = ( +// REAL)(mars_kiss64_seed(neuron->kiss_seed) / (REAL)0xffffffff); // 0-1 + // The above does not actually give a REAL between 0 and 1 + // since (REAL)0xffffffff = -1.0k; however it's pretty much + // what we're looking for as it converts an uint32_t to a + // (signed) REAL, so the test just needs to be positive vs + // negative, removing the need for any dividing + REAL random_value = kbits( + mars_kiss64_seed(neuron->kiss_seed)); + if (random_value < ZERO) { + current_cue_direction = 0; + } + else{ + current_cue_direction = 1; + } + accumulative_direction += current_cue_direction; + REAL payload; + payload = neuron->rate_on; + for (int j = current_cue_direction*neuron->p_pop_size; + j < current_cue_direction*neuron->p_pop_size + neuron->p_pop_size; j++){ + send_spike_mc_payload(neuron->p_key | j, bitsk(payload)); + } + } + } + // turn off and reset if finished + else if ((time - current_time) % (wait_between_cues + duration_of_cue) == 0 && + (time - current_time) > 0) {//(wait_between_cues + duration_of_cue) - 1){ + cue_number += 1; + REAL payload; + payload = neuron->rate_off; + for (int j = current_cue_direction*neuron->p_pop_size; + j < current_cue_direction*neuron->p_pop_size + neuron->p_pop_size; j++) { + send_spike_mc_payload(neuron->p_key | j, bitsk(payload)); + } + if (cue_number >= neuron->number_of_cues) { + current_state = (current_state + 1) % 3; + } + } + } + } + else if (current_state == STATE_WAITING){ + // waiting for prompt, all things ok + if (cue_number >= neuron->number_of_cues) { + current_time = time; + cue_number = 0; + } + if ((time - current_time) >= wait_before_result) { + current_state = (current_state + 1) % 3; + start_prompt = true; + } + } + else if (current_state == STATE_PROMPT){ + if (start_prompt && neuron_index == 1){ + current_time = time; + // send packets to the variable poissons with the updated states + for (int i = 0; i < 4; i++){ + REAL payload; + payload = neuron->rate_on; + for (int j = 2*neuron->p_pop_size; + j < 2*neuron->p_pop_size + neuron->p_pop_size; j++){ + send_spike_mc_payload(neuron->p_key | j, bitsk(payload)); + } + } + } + + // This is the error source + if (neuron_index == 2) { + // Switched to always broadcasting error but with packet + start_prompt = false; + REAL exp_0 = expk(neuron->readout_V_0);// * 0.1k); + REAL exp_1 = expk(neuron->readout_V_1);// * 0.1k); + + // Set up softmax calculation + if (exp_0 == 0k && exp_1 == 0k) { + if (neuron->readout_V_0 > neuron->readout_V_1) { + softmax_0 = 1k; + softmax_1 = 0k; + } + else { + softmax_0 = 0k; + softmax_1 = 1k; + } + } + else { + softmax_0 = exp_0 / (exp_1 + exp_0); + softmax_1 = exp_1 / (exp_1 + exp_0); + // These divides are okay in kdivk because exp is always positive +// softmax_0 = kdivk(exp_0, (exp_1 + exp_0)); +// softmax_1 = kdivk(exp_1, (exp_1 + exp_0)); + } + + // What to do if log(0)? + if (accumulative_direction > neuron->number_of_cues >> 1){ + for (uint32_t glob_n = 0; glob_n < n_neurons; glob_n++) { + // Get the neuron itself + neuron_t *glob_neuron = &neuron_array[glob_n]; + glob_neuron->cross_entropy = -logk(softmax_1); + } + learning_signal = softmax_0; + } + else{ + for (uint32_t glob_n = 0; glob_n < n_neurons; glob_n++) { + // Get the neuron itself + neuron_t *glob_neuron = &neuron_array[glob_n]; + glob_neuron->cross_entropy = -logk(softmax_0); + } + learning_signal = softmax_0 - 1.k; + } + if (use_key) { + send_spike_mc_payload(neuron_keys[neuron_index], bitsk(learning_signal)); + } + } + + // The current broadcast may have completed so check + if ((time - current_time) >= prompt_duration && neuron_index == 0){ + current_state = 0; + completed_broadcast = true; + for (int i = 0; i < 4; i++){ + REAL payload; + payload = neuron->rate_off; + for (int j = 2*neuron->p_pop_size; + j < 2*neuron->p_pop_size + neuron->p_pop_size; j++){ + send_spike_mc_payload(neuron->p_key | j, payload); + } + } + } + } + + // Record learning signal and voltage + neuron_recording_record_accum( + GSYN_INH_RECORDING_INDEX, neuron_index, learning_signal); + neuron_recording_record_accum( + V_RECORDING_INDEX, neuron_index, voltage); + + // Record delta_w from different synapse states depending on neuron index + if (neuron_index == 2) { + neuron_recording_record_accum( + GSYN_EXC_RECORDING_INDEX, neuron_index, + neuron->syn_state[50].delta_w); + } + else if (neuron_index == 1) { + neuron_recording_record_accum( + GSYN_EXC_RECORDING_INDEX, neuron_index, + neuron->syn_state[40].delta_w); + } + else { + neuron_recording_record_accum( + GSYN_EXC_RECORDING_INDEX, neuron_index, + neuron->syn_state[0].delta_w); + } + + // Shape the existing input according to the included rule + synapse_types_shape_input(synapse_type); + + #if LOG_LEVEL >= LOG_DEBUG + neuron_model_print_state_variables(neuron); + #endif // LOG_LEVEL >= LOG_DEBUG + } +} + +//! \brief stores neuron parameter back into sdram +//! \param[in] address: the address in sdram to start the store +static void neuron_impl_store_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons) { + log_debug("writing parameters"); + + // Skip steps per timestep + next += 1; + + if (sizeof(neuron_t)) { + log_debug("writing neuron local parameters"); + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_save_state(&neuron_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + log_debug("writing input type parameters"); + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_save_state(&input_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + log_debug("writing threshold type parameters"); + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_save_state(&threshold_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + log_debug("writing synapse parameters"); + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_save_state(&synapse_types_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + log_debug("writing additional input type parameters"); + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_save_state(&additional_input_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } +} + +#if LOG_LEVEL >= LOG_DEBUG +void neuron_impl_print_inputs(uint32_t n_neurons) { + bool empty = true; + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + empty = empty && (0 == bitsk( + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[0])); + } + + if (!empty) { + log_debug("-------------------------------------\n"); + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t input = + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[1]; + if (bitsk(input) != 0) { + log_debug("%3u: %12.6k (= ", i, input); + synapse_types_print_input(params); + log_debug(")\n"); + } + } + log_debug("-------------------------------------\n"); + } +} + +void neuron_impl_print_synapse_parameters(uint32_t n_neurons) { + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + synapse_types_print_parameters(&(neuron_synapse_shaping_params[n])); + } + log_debug("-------------------------------------\n"); +} + +const char *neuron_impl_get_synapse_type_char(uint32_t synapse_type) { + return synapse_types_get_type_char(synapse_type); +} +#endif // LOG_LEVEL >= LOG_DEBUG + +#endif // _NEURON_IMPL_LEFT_RIGHT_READOUT_H_ diff --git a/neural_modelling/src/neuron/implementations/neuron_impl_sinusoid_readout.h b/neural_modelling/src/neuron/implementations/neuron_impl_sinusoid_readout.h new file mode 100644 index 00000000000..0a0805d9b39 --- /dev/null +++ b/neural_modelling/src/neuron/implementations/neuron_impl_sinusoid_readout.h @@ -0,0 +1,447 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_IMPL_SINUSOID_READOUT_H_ +#define _NEURON_IMPL_SINUSOID_READOUT_H_ + +#include "neuron_impl.h" + +// Includes for model parts used in this implementation +#include +#include +#include +#include +#include + +#include + +// Further includes +#include +#include +#include +#include +#include // TODO: maybe not needed? + +//! Indices for recording of words +enum word_recording_indices { + //! V (somatic potential) recording index + V_RECORDING_INDEX = 0, + //! Gsyn_exc (excitatory synaptic conductance/current) recording index + GSYN_EXC_RECORDING_INDEX = 1, + //! Gsyn_inh (excitatory synaptic conductance/current) recording index + GSYN_INH_RECORDING_INDEX = 2, + //! Number of recorded word-sized state variables + N_RECORDED_VARS = 3 +}; + +//! Indices for recording of bitfields +enum bitfield_recording_indices { + //! Spike event recording index + SPIKE_RECORDING_BITFIELD = 0, + //! Number of recorded bitfields + N_BITFIELD_VARS = 1 +}; + +#ifndef NUM_EXCITATORY_RECEPTORS +#define NUM_EXCITATORY_RECEPTORS 1 +#error NUM_EXCITATORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#ifndef NUM_INHIBITORY_RECEPTORS +#define NUM_INHIBITORY_RECEPTORS 1 +#error NUM_INHIBITORY_RECEPTORS was undefined. It should be defined by a synapse\ + shaping include +#endif + +#include + +//! Array of neuron states +neuron_t *neuron_array; + +//! Input states array +static input_type_t *input_type_array; + +//! Additional input array +static additional_input_t *additional_input_array; + +//! Threshold states array +static threshold_type_t *threshold_type_array; + +// The synapse shaping parameters +static synapse_types_t *synapse_types_array; + +//! The number of steps to run per timestep +static uint n_steps_per_timestep; + +//! Whether key is set, from neuron.c +extern bool use_key; + +// TODO: check if these other parameters are needed +//static REAL next_spike_time = 0; +extern uint32_t time; +extern uint32_t *neuron_keys; +extern REAL learning_signal; +static uint32_t target_ind = 0; + +static bool neuron_impl_initialise(uint32_t n_neurons) { + + // Allocate DTCM for neuron array + if (sizeof(neuron_t)) { + neuron_array = spin1_malloc(n_neurons * sizeof(neuron_t)); + if (neuron_array == NULL) { + log_error("Unable to allocate neuron array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for input type array and copy block of data + if (sizeof(input_type_t)) { + input_type_array = spin1_malloc(n_neurons * sizeof(input_type_t)); + if (input_type_array == NULL) { + log_error("Unable to allocate input type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for additional input array and copy block of data + if (sizeof(additional_input_t)) { + additional_input_array = + spin1_malloc(n_neurons * sizeof(additional_input_t)); + if (additional_input_array == NULL) { + log_error("Unable to allocate additional input array" + " - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for threshold type array and copy block of data + if (sizeof(threshold_type_t)) { + threshold_type_array = + spin1_malloc(n_neurons * sizeof(threshold_type_t)); + if (threshold_type_array == NULL) { + log_error("Unable to allocate threshold type array - Out of DTCM"); + return false; + } + } + + // Allocate DTCM for synapse shaping parameters + if (sizeof(synapse_types_t) != 0) { + synapse_types_array = + spin1_malloc(n_neurons * sizeof(synapse_types_t)); + if (synapse_types_array == NULL) { + log_error("Unable to allocate synapse parameters array" + " - Out of DTCM"); + return false; + } + } + + return true; +} + +static void neuron_impl_add_inputs( + index_t synapse_type_index, index_t neuron_index, + input_t weights_this_timestep) { + // simple wrapper to synapse type input function + synapse_types_t *parameters = + &(synapse_types_array[neuron_index]); + synapse_types_add_neuron_input(synapse_type_index, + parameters, weights_this_timestep); +} + +static uint32_t n_words_needed(uint32_t size) { + return (size + (sizeof(uint32_t) - 1)) / sizeof(uint32_t); +} + +static void neuron_impl_load_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons, + address_t save_initial_state) { + log_debug("reading parameters, next is %u, n_neurons is %u ", + next, n_neurons); + + // Number of steps per timestep + n_steps_per_timestep = address[next++]; + if (n_steps_per_timestep == 0) { + log_error("bad number of steps per timestep: 0"); + rt_error(RTE_SWERR); + } + + if (sizeof(neuron_t)) { + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_initialise(&neuron_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_initialise(&input_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_initialise(&threshold_type_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_initialise(&synapse_types_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_initialise(&additional_input_array[i], ¶ms[i], + n_steps_per_timestep); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } + + // If we are to save the initial state, copy the whole of the parameters + // to the initial state + if (save_initial_state) { + spin1_memcpy(save_initial_state, address, next * sizeof(uint32_t)); + } + + #if LOG_LEVEL >= LOG_DEBUG + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + neuron_model_print_parameters(&neuron_array[n]); + } + log_debug("-------------------------------------\n"); + //} + #endif // LOG_LEVEL >= LOG_DEBUG +} + +static void neuron_impl_do_timestep_update( + uint32_t timer_count, uint32_t time, uint32_t n_neurons) { + + for (uint32_t neuron_index = 0; neuron_index < n_neurons; neuron_index++) { + // Get the neuron itself + neuron_t *neuron = &neuron_array[neuron_index]; + bool spike = false; // TODO: don't think this is needed + + target_ind = time & 0x3ff; // repeats on a cycle of 1024 entries in array + + // Get the input_type parameters and voltage for this neuron + input_type_t *input_type = &input_type_array[neuron_index]; + + // Get threshold and additional input parameters for this neuron + threshold_type_t *threshold_type = + &threshold_type_array[neuron_index]; + additional_input_t *additional_input = + &additional_input_array[neuron_index]; + synapse_types_t *synapse_type = + &synapse_types_array[neuron_index]; + + // Get the voltage + state_t voltage = neuron_model_get_membrane_voltage(neuron); + + // Get the exc and inh values from the synapses + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t* exc_syn_values = synapse_types_get_excitatory_input( + exc_values, synapse_type); + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t* inh_syn_values = synapse_types_get_inhibitory_input( + inh_values, synapse_type); + + // Call functions to obtain exc_input and inh_input + input_t* exc_input_values = input_type_get_input_value( + exc_syn_values, input_type, NUM_EXCITATORY_RECEPTORS); + input_t* inh_input_values = input_type_get_input_value( + inh_syn_values, input_type, NUM_INHIBITORY_RECEPTORS); + + // Call functions to convert exc_input and inh_input to current + input_type_convert_excitatory_input_to_current( + exc_input_values, input_type, voltage); + input_type_convert_inhibitory_input_to_current( + inh_input_values, input_type, voltage); + + REAL current_offset = current_source_get_offset(time, neuron_index); + input_t external_bias = additional_input_get_input_value_as_current( + additional_input, voltage); + + // Update neuron only on index 0 + if (neuron_index == 0){ + state_t result = neuron_model_state_update( + NUM_EXCITATORY_RECEPTORS, exc_input_values, + NUM_INHIBITORY_RECEPTORS, inh_input_values, + external_bias, current_offset, neuron, 0.0k); + + // Calculate error + REAL error = result - neuron->target_V[target_ind]; + learning_signal = error; + + // Record readout + neuron_recording_record_accum(V_RECORDING_INDEX, neuron_index, result); + // Send error (learning signal) as packet with payload +// send_spike_mc_payload(key, bitsk(error)); + if (use_key) { + send_spike_mc_payload(neuron_keys[neuron_index], bitsk(error)); + } + } + else{ + // Record 'Error' + neuron_recording_record_accum( + V_RECORDING_INDEX, neuron_index, + neuron->target_V[target_ind]); + } + + // Record z_bar + neuron_recording_record_accum( + GSYN_INH_RECORDING_INDEX, neuron_index, + neuron->syn_state[neuron_index*20].z_bar); + // Record target delta_w + neuron_recording_record_accum( + GSYN_EXC_RECORDING_INDEX, neuron_index, + neuron->syn_state[neuron_index*20].delta_w); + + // If spike occurs, communicate to relevant parts of model + // TODO I don't know why this is here since this can (currently) never happen + if (spike) { + // Call relevant model-based functions + // Tell the neuron model + // neuron_model_has_spiked(neuron); + + // Tell the additional input + additional_input_has_spiked(additional_input); + } + + // Shape the existing input according to the included rule + synapse_types_shape_input(synapse_type); + + #if LOG_LEVEL >= LOG_DEBUG + neuron_model_print_state_variables(neuron); + #endif // LOG_LEVEL >= LOG_DEBUG + + } +} + + +//! \brief stores neuron parameter back into sdram +//! \param[in] address: the address in sdram to start the store +static void neuron_impl_store_neuron_parameters( + address_t address, uint32_t next, uint32_t n_neurons) { + log_debug("writing parameters"); + + // Skip steps per timestep + next += 1; + + if (sizeof(neuron_t)) { + log_debug("writing neuron local parameters"); + neuron_params_t *params = (neuron_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + neuron_model_save_state(&neuron_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(neuron_params_t)); + } + + if (sizeof(input_type_t)) { + log_debug("writing input type parameters"); + input_type_params_t *params = (input_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + input_type_save_state(&input_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(input_type_params_t)); + } + + if (sizeof(threshold_type_t)) { + log_debug("writing threshold type parameters"); + threshold_type_params_t *params = (threshold_type_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + threshold_type_save_state(&threshold_type_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(threshold_type_params_t)); + } + + if (sizeof(synapse_types_t)) { + log_debug("writing synapse parameters"); + synapse_types_params_t *params = (synapse_types_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + synapse_types_save_state(&synapse_types_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(synapse_types_params_t)); + } + + if (sizeof(additional_input_t)) { + log_debug("writing additional input type parameters"); + additional_input_params_t *params = (additional_input_params_t *) &address[next]; + for (uint32_t i = 0; i < n_neurons; i++) { + additional_input_save_state(&additional_input_array[i], ¶ms[i]); + } + next += n_words_needed(n_neurons * sizeof(additional_input_params_t)); + } +} + +#if LOG_LEVEL >= LOG_DEBUG +void neuron_impl_print_inputs(uint32_t n_neurons) { + bool empty = true; + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + empty = empty && (0 == bitsk( + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[0])); + } + + if (!empty) { + log_debug("-------------------------------------\n"); + for (index_t i = 0; i < n_neurons; i++) { + synapse_types_t *params = &synapse_types_array[i]; + input_t exc_values[NUM_EXCITATORY_RECEPTORS]; + input_t inh_values[NUM_INHIBITORY_RECEPTORS]; + input_t input = + synapse_types_get_excitatory_input(exc_values, params)[0] + - synapse_types_get_inhibitory_input(inh_values, params)[1]; + if (bitsk(input) != 0) { + log_debug("%3u: %12.6k (= ", i, input); + synapse_types_print_input(params); + log_debug(")\n"); + } + } + log_debug("-------------------------------------\n"); + } +} + +void neuron_impl_print_synapse_parameters(uint32_t n_neurons) { + log_debug("-------------------------------------\n"); + for (index_t n = 0; n < n_neurons; n++) { + synapse_types_print_parameters(&(synapse_types_array[n])); + } + log_debug("-------------------------------------\n"); +} + +const char *neuron_impl_get_synapse_type_char(uint32_t synapse_type) { + return synapse_types_get_type_char(synapse_type); +} +#endif // LOG_LEVEL >= LOG_DEBUG + +#endif // _NEURON_IMPL_SINUSOID_READOUT_H_ diff --git a/neural_modelling/src/neuron/implementations/neuron_impl_standard.h b/neural_modelling/src/neuron/implementations/neuron_impl_standard.h index c94bd250277..ce59a02a7b6 100644 --- a/neural_modelling/src/neuron/implementations/neuron_impl_standard.h +++ b/neural_modelling/src/neuron/implementations/neuron_impl_standard.h @@ -310,7 +310,7 @@ static void neuron_impl_do_timestep_update( state_t result = neuron_model_state_update( NUM_EXCITATORY_RECEPTORS, exc_input_values, NUM_INHIBITORY_RECEPTORS, inh_input_values, - external_bias, current_offset, this_neuron); + external_bias, current_offset, this_neuron, 0.0k); // determine if a spike should occur bool spike_now = diff --git a/neural_modelling/src/neuron/models/neuron_model.h b/neural_modelling/src/neuron/models/neuron_model.h index 50450186b53..47f3bff4e7d 100644 --- a/neural_modelling/src/neuron/models/neuron_model.h +++ b/neural_modelling/src/neuron/models/neuron_model.h @@ -65,14 +65,15 @@ SOMETIMES_UNUSED // Marked unused as only used sometimes //! terms of stimulation. //! \param[in] external_bias: This is the intrinsic plasticity which could be //! used for ac, noisy input etc etc. (general purpose input) -//! \param[in,out] neuron: the pointer to a neuron parameter struct which -//! contains all the parameters for a specific neuron -//! \return the value to be compared with a threshold value to determine if the -//! neuron has spiked +//! \param[in] neuron the pointer to a neuron parameter struct which contains +//! all the parameters for a specific neuron +//! \return state_t which is the value to be compared with a threshold value +//! to determine if the neuron has spiked static state_t neuron_model_state_update( - uint16_t num_excitatory_inputs, const input_t *exc_input, - uint16_t num_inhibitory_inputs, const input_t *inh_input, - input_t external_bias, REAL current_offset, neuron_t *restrict neuron); + uint16_t num_excitatory_inputs, const input_t* exc_input, + uint16_t num_inhibitory_inputs, const input_t* inh_input, + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t); SOMETIMES_UNUSED // Marked unused as only used sometimes //! \brief Indicates that the neuron has spiked diff --git a/neural_modelling/src/neuron/models/neuron_model_eprop_adaptive_impl.h b/neural_modelling/src/neuron/models/neuron_model_eprop_adaptive_impl.h new file mode 100644 index 00000000000..7a9f8d34a7c --- /dev/null +++ b/neural_modelling/src/neuron/models/neuron_model_eprop_adaptive_impl.h @@ -0,0 +1,540 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_MODEL_EPROP_ADAPTIVE_IMPL_H_ +#define _NEURON_MODEL_EPROP_ADAPTIVE_IMPL_H_ + +#include "neuron_model.h" +#include + +// TODO: Can this be set up somehow as a value that's passed in? +#define SYNAPSES_PER_NEURON 250 + +static bool printed_value = false; +extern REAL learning_signal; +extern uint32_t time; // this is probably unnecessary +extern uint32_t neuron_impl_neurons_in_partition; + +typedef struct eprop_syn_state_t { + REAL delta_w; // weight change to apply + REAL z_bar_inp; + REAL z_bar; // low-pass filtered spike train + REAL el_a; // adaptive component of eligibility vector + REAL e_bar; // low-pass filtered eligibility trace + int32_t update_ready; // counter to enable batch update (i.e. don't perform on every spike). +} eprop_syn_state_t; + +// eprop adaptive neuron parameters +struct neuron_params_t { + // membrane voltage [mV] + REAL V_init; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane capacitance [nF] + REAL c_m; + + // membrane decay time constant + REAL tau_m; + + // offset current (nA) + REAL I_offset; + + // post-spike reset membrane voltage (mV) + REAL V_reset; + + // refractory time of neuron [ms] + REAL T_refract_ms; + + // initial refractory timer value (saved) + int32_t refract_timer_init; + + // The time step in milliseconds + REAL time_step; + + // Neuron spike train + REAL z; + + // refractory multiplier - to allow evolution of neuronal dynamics during + // refractory period + REAL A; + + // pseudo derivative + REAL psi; + + // Threshold paramters + REAL B; // Capital B(t) + REAL b; // b(t) + REAL b_0; // small b^0 + uint32_t tau_a; + REAL beta; + REAL scalar; + + REAL L; // learning signal + REAL w_fb; // feedback weight + uint32_t window_size; + uint32_t number_of_cues; + + REAL pop_rate; + REAL target_rate; + REAL tau_err; + REAL eta; // learning rate + + // array of synaptic states - peak fan-in of 250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; +}; + +//! eprop neuron state +struct neuron_t { + // membrane voltage [mV] + REAL V_membrane; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane resistance [MOhm] + REAL R_membrane; + + // 'fixed' computation parameter - time constant multiplier for + // closed-form solution + // exp(-(machine time step in ms)/(R * C)) [.] + REAL exp_TC; + + // offset current [nA] + REAL I_offset; + + // countdown to end of next refractory period [timesteps] + int32_t refract_timer; + + // post-spike reset membrane voltage [mV] + REAL V_reset; + + // refractory time of neuron [timesteps] + int32_t T_refract; + + // Neuron spike train + REAL z; + + // refractory multiplier - to allow evolution of neuronal dynamics during + // refractory period + REAL A; + + // pseudo derivative + REAL psi; + + // Threshold paramters + REAL B; // Capital B(t) + REAL b; // b(t) + REAL b_0; // small b^0 + decay_t e_to_dt_on_tau_a; // rho + REAL beta; + decay_t adpt; // (1-rho) + REAL scalar; + + REAL L; // learning signal + REAL w_fb; // feedback weight + uint32_t window_size; + uint32_t number_of_cues; + + // Former "global" parameters + REAL core_pop_rate; + REAL core_target_rate; + REAL rate_exp_TC; + REAL eta; // learning rate + + // array of synaptic states - peak fan-in of 250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; +}; + +// TODO: Can we use the threshold type for this instead? +static inline void threshold_type_update_threshold(state_t z, + neuron_t *threshold_type){ + + // TODO: Better names for these variables + s1615 temp1 = decay_s1615(threshold_type->b, threshold_type->e_to_dt_on_tau_a); + s1615 temp2 = decay_s1615(threshold_type->scalar, threshold_type->adpt) * z; + + threshold_type->b = temp1 + temp2; + + // Update large B + threshold_type->B = threshold_type->b_0 + + threshold_type->beta*threshold_type->b; +} + +//! \brief Performs a ceil operation on an accum +//! \param[in] value The value to ceil +//! \return The ceil of the value +static inline int32_t lif_ceil_accum(REAL value) { + int32_t bits = bitsk(value); + int32_t integer = bits >> 15; + int32_t fraction = bits & 0x7FFF; + if (fraction > 0) { + return integer + 1; + } + return integer; +} + +static inline void neuron_model_initialise( + neuron_t *state, neuron_params_t *params, uint32_t n_steps_per_timestep) { + REAL ts = kdivui(params->time_step, n_steps_per_timestep); + state->V_membrane = params->V_init; + state->V_rest = params->V_rest; + state->R_membrane = kdivk(params->tau_m, params->c_m); + state->exp_TC = expk(-kdivk(ts, params->tau_m)); + state->I_offset = params->I_offset; + state->refract_timer = params->refract_timer_init; + state->V_reset = params->V_reset; + state->T_refract = lif_ceil_accum(kdivk(params->T_refract_ms, ts)); + + log_info("V_membrane %k V_rest %k R_membrane %k exp_TC %k I_offset %k" + "refract_timer %k V_reset %k T_refract_ms %k T_refract %d", + state->V_membrane, state->V_rest, state->R_membrane, state->exp_TC, + state->I_offset, state->refract_timer, state->V_reset, + params->T_refract_ms, state->T_refract); + + // for everything else just copy across for now + state->z = params->z; + state->A = params->A; + state->psi = params->psi; + state->B = params->B; + state->b = params->b; + state->b_0 = params->b_0; + REAL exp_tau_a = expk(-kdivk(ts, params->tau_a)); + state->e_to_dt_on_tau_a = (UFRACT) exp_tau_a; + state->beta = params->beta; + state->adpt = (UFRACT) (1.0k - exp_tau_a); + state->scalar = params->scalar; + state->L = params->L; + state->w_fb = params->w_fb; + state->window_size = params->window_size; + state->number_of_cues = params->number_of_cues; + + log_info("Check: z %k A %k psi %k B %k b %k b_0 %k window_size %u", + state->z, state->A, state->psi, state->B, state->b, state->b_0, + state->window_size); + + state->core_pop_rate = params->pop_rate; + state->core_target_rate = params->target_rate; + state->rate_exp_TC = expk(-kdivk(ts, params->tau_err)); + state->eta = params->eta; + + log_info("Check: core_pop_rate %k core_target_rate %k rate_exp_TC %k eta %k", + state->core_pop_rate, state->core_target_rate, state->rate_exp_TC, + state->eta); + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + state->syn_state[n_syn] = params->syn_state[n_syn]; + } +} + +static inline void neuron_model_save_state(neuron_t *state, neuron_params_t *params) { + params->V_init = state->V_membrane; + params->refract_timer_init = state->refract_timer; + params->z = state->z; + params->A = state->A; + params->psi = state->psi; + params->B = state->B; + params->b = state->b; + params->b_0 = state->b_0; + params->beta = state->beta; + params->scalar = state->scalar; + params->L = state->L; + params->w_fb = state->w_fb; + params->window_size = state->window_size; + params->number_of_cues = state->number_of_cues; + + params->pop_rate = state->core_pop_rate; + params->target_rate = state->core_target_rate; + params->eta = state->eta; + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + params->syn_state[n_syn] = state->syn_state[n_syn]; + } +} + +// simple Leaky I&F ODE +static inline void lif_eprop_neuron_closed_form( + neuron_t *neuron, REAL V_prev, input_t input_this_timestep, + REAL B_t) { + REAL alpha = input_this_timestep * neuron->R_membrane + neuron->V_rest; + + // update membrane voltage + neuron->V_membrane = alpha - (neuron->exp_TC * (alpha - V_prev)) + - neuron->z * B_t; // this line achieves reset +} + +state_t neuron_model_state_update( + uint16_t num_excitatory_inputs, const input_t* exc_input, + uint16_t num_inhibitory_inputs, const input_t* inh_input, + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t) { + + log_debug("Exc 1: %12.6k, Exc 2: %12.6k", exc_input[0], exc_input[1]); + log_debug("Inh 1: %12.6k, Inh 2: %12.6k", inh_input[0], inh_input[1]); + + // Get the input in nA + input_t input_this_timestep = + exc_input[0] + exc_input[1] + neuron->I_offset + external_bias + current_offset; + + lif_eprop_neuron_closed_form( + neuron, neuron->V_membrane, input_this_timestep, B_t); + + // If outside of the refractory period + if (neuron->refract_timer <= 0) { + // Allow spiking again + neuron->A = 1.0k; + } else { + // Neuron cannot fire, as neuron->A=0; + // countdown refractory timer + neuron->refract_timer -= 1; + } + + // ****************************************************************** + // Update Psi (pseudo-derivative) (done once for each postsynaptic neuron) + // ****************************************************************** + REAL psi_temp1 = (neuron->V_membrane - neuron->B) * (1/neuron->b_0); + REAL psi_temp2 = ((absk(psi_temp1))); + neuron->psi = ((1.0k - psi_temp2) > 0.0k)? + (1.0k/neuron->b_0) * + 0.3k * + (1.0k - psi_temp2) : 0.0k; + neuron->psi *= neuron->A; + + // This parameter is OK to update, as the actual size of the array is set in the + // header file, which matches the Python code. This should make it possible to + // do a pause and resume cycle and have reliable unloading of data. + uint32_t total_input_synapses_per_neuron = 40; //todo should this be fixed? + uint32_t total_recurrent_synapses_per_neuron = 0; //todo should this be fixed? + uint32_t recurrent_offset = 100; + + REAL rho = decay_s1615(1.0k, neuron->e_to_dt_on_tau_a); + + // TODO: Is there a better way of doing this? + REAL accum_time = (accum)(time%neuron->window_size) * 0.001k; + if (!accum_time){ + accum_time += 1.k; + } + + // Calculate the membrane error + REAL v_mem_error; + if (neuron->V_membrane > neuron->B){ + v_mem_error = neuron->V_membrane - neuron->B; + } + else if (neuron->V_membrane < -neuron->B){ + v_mem_error = neuron->V_membrane + neuron->B; + } + else{ + v_mem_error = 0.k; + } + + // Calculate regularised learning signal + REAL reg_learning_signal = (neuron->core_pop_rate // make it work for different ts +// / ((accum)(time%1300) +// / (1.225k // 00000!!!!! + / (accum_time + * (accum)neuron_impl_neurons_in_partition)) + - neuron->core_target_rate; + + // hardcoded reset + if (time % neuron->window_size == neuron->window_size - 1 & !printed_value) { + printed_value = true; + } + if (time % neuron->window_size == 0){ + printed_value = false; + } + + // Calculate new learning signal + REAL new_learning_signal = (learning_signal * neuron->w_fb) + v_mem_error; + + // TODO: magic constants need naming at least (and passing in?) + uint32_t test_length = (150*neuron->number_of_cues)+1000+150; + if (neuron->number_of_cues == 0) { + test_length = neuron->window_size; + } + + // TODO make this relative to number of cues? + if (time % neuron->window_size > test_length * 2) { + neuron->L = new_learning_signal + (reg_learning_signal);// * 0.1k); + } + else{ + neuron->L = new_learning_signal; + } + + neuron->L = new_learning_signal; + // eta used to be a global parameter, but now just copy from neuron + REAL local_eta = neuron->eta; + + // Reset relevant parameters ahead of filtering + if ((time % test_length == 0 || time % test_length == 1) && neuron->number_of_cues) { + neuron->B = neuron->b_0; + neuron->b = 0.k; + neuron->V_membrane = neuron->V_rest; + neuron->refract_timer = 0; + neuron->z = 0.k; + } + + // All subsequent operations now need doing once per eprop synapse + for (uint32_t syn_ind=0; syn_ind < total_input_synapses_per_neuron; syn_ind++){ + if ((time % test_length == 0 || time % test_length == 1) && neuron->number_of_cues) { + neuron->syn_state[syn_ind].z_bar_inp = 0.k; + neuron->syn_state[syn_ind].z_bar = 0.k; + neuron->syn_state[syn_ind].el_a = 0.k; + neuron->syn_state[syn_ind].e_bar = 0.k; + } + // ****************************************************************** + // Low-pass filter incoming spike train + // ****************************************************************** + neuron->syn_state[syn_ind].z_bar = + neuron->syn_state[syn_ind].z_bar * neuron->exp_TC + + + (1 - neuron->exp_TC) * + neuron->syn_state[syn_ind].z_bar_inp; + // updating z_bar is problematic, if spike could come and interrupt neuron update + + + // ****************************************************************** + // Update eligibility vector + // ****************************************************************** + neuron->syn_state[syn_ind].el_a = + (neuron->psi * neuron->syn_state[syn_ind].z_bar) + + (rho - neuron->psi * neuron->beta) * + neuron->syn_state[syn_ind].el_a; + + // ****************************************************************** + // Update eligibility trace + // ****************************************************************** + REAL temp_elig_trace = neuron->psi * (neuron->syn_state[syn_ind].z_bar - + neuron->beta * neuron->syn_state[syn_ind].el_a); + + neuron->syn_state[syn_ind].e_bar = + neuron->exp_TC * neuron->syn_state[syn_ind].e_bar + + (1 - neuron->exp_TC) * temp_elig_trace; + + // ****************************************************************** + // Update cached total weight change + // ****************************************************************** + REAL this_dt_weight_change = + local_eta * neuron->L * neuron->syn_state[syn_ind].e_bar; + neuron->syn_state[syn_ind].delta_w -= this_dt_weight_change; + // -= here to enable compiler to handle previous line (can crash when + // -ve is at beginning of previous line) + + // reset input (can't have more than one spike per timestep + neuron->syn_state[syn_ind].z_bar_inp = 0; + + // decrease timestep counter preventing rapid updates + neuron->syn_state[syn_ind].update_ready -= 1; + } + + // All further operations now need doing once per recurrent eprop synapse + for (uint32_t syn_ind=recurrent_offset; + syn_ind < total_recurrent_synapses_per_neuron+recurrent_offset; syn_ind++) { + if ((time % test_length == 0 || time % test_length == 1) && neuron->number_of_cues) { + neuron->syn_state[syn_ind].z_bar_inp = 0.k; + neuron->syn_state[syn_ind].z_bar = 0.k; + neuron->syn_state[syn_ind].el_a = 0.k; + neuron->syn_state[syn_ind].e_bar = 0.k; + } + // ****************************************************************** + // Low-pass filter incoming spike train + // ****************************************************************** + // updating z_bar is problematic, if spike could come and interrupt neuron update + neuron->syn_state[syn_ind].z_bar = + neuron->syn_state[syn_ind].z_bar * neuron->exp_TC + + (1 - neuron->exp_TC) * neuron->syn_state[syn_ind].z_bar_inp; + + // ****************************************************************** + // Update eligibility vector + // ****************************************************************** + neuron->syn_state[syn_ind].el_a = + (neuron->psi * neuron->syn_state[syn_ind].z_bar) + + (rho - neuron->psi * neuron->beta) * + neuron->syn_state[syn_ind].el_a; + + // ****************************************************************** + // Update eligibility trace + // ****************************************************************** + REAL temp_elig_trace = neuron->psi * (neuron->syn_state[syn_ind].z_bar - + neuron->beta * neuron->syn_state[syn_ind].el_a); + + neuron->syn_state[syn_ind].e_bar = + neuron->exp_TC * neuron->syn_state[syn_ind].e_bar + + (1 - neuron->exp_TC) * temp_elig_trace; + + // ****************************************************************** + // Update cached total weight change + // ****************************************************************** + REAL this_dt_weight_change = + local_eta * neuron->L * neuron->syn_state[syn_ind].e_bar; + neuron->syn_state[syn_ind].delta_w -= this_dt_weight_change; + // -= here to enable compiler to handle previous line (can crash when + // -ve is at beginning of previous line) + + // reset input (can't have more than one spike per timestep + neuron->syn_state[syn_ind].z_bar_inp = 0; + + // decrease timestep counter preventing rapid updates + neuron->syn_state[syn_ind].update_ready -= 1; + } + + return neuron->V_membrane; +} + +void neuron_model_has_spiked(neuron_t *restrict neuron) { + // reset z to zero + neuron->z = 0; + // TODO: Not sure this should be commented out but I think the change + // in the lif_eprop_neuron_closed_form(...) function possibly makes it redundant +// neuron->V_membrane = neuron->V_rest; + // Set refractory timer + neuron->refract_timer = neuron->T_refract - 1; + neuron->A = 0; +} + +state_t neuron_model_get_membrane_voltage(const neuron_t *neuron) { + return neuron->V_membrane; +} + +void neuron_model_print_state_variables(const neuron_t *neuron) { + log_debug("V membrane = %11.4k mv", neuron->V_membrane); + log_debug("learning = %k ", neuron->L); + + log_debug("Printing synapse state values:"); + for (uint32_t syn_ind=0; syn_ind < 100; syn_ind++){ + log_debug("synapse number %u delta_w, z_bar, z_bar_inp, e_bar, el_a " + "%11.4k %11.4k %11.4k %11.4k %11.4k", + syn_ind, neuron->syn_state[syn_ind].delta_w, + neuron->syn_state[syn_ind].z_bar, neuron->syn_state[syn_ind].z_bar_inp, + neuron->syn_state[syn_ind].e_bar, neuron->syn_state[syn_ind].el_a); + } +} + +void neuron_model_print_parameters(const neuron_t *neuron) { + log_debug("V reset = %11.4k mv\n\n", neuron->V_reset); + log_debug("V rest = %11.4k mv\n", neuron->V_rest); + log_debug("I offset = %11.4k nA\n", neuron->I_offset); + log_debug("R membrane = %11.4k Mohm\n", neuron->R_membrane); + log_debug("exp(-ms/(RC)) = %11.4k [.]\n", neuron->exp_TC); + log_debug("T refract = %u timesteps\n", neuron->T_refract); + log_debug("learning = %k n/a\n", neuron->L); + log_debug("feedback w = %k n/a\n\n", neuron->w_fb); + log_debug("window size = %u ts\n", neuron->window_size); + log_debug("beta = %k n/a\n", neuron->beta); + log_debug("adpt = %k n/a\n", neuron->adpt); +} + +#endif // _NEURON_MODEL_EPROP_ADAPTIVE_IMPL_H_ diff --git a/neural_modelling/src/neuron/models/neuron_model_izh_impl.h b/neural_modelling/src/neuron/models/neuron_model_izh_impl.h index 5b04806179e..5ffb1edcd91 100644 --- a/neural_modelling/src/neuron/models/neuron_model_izh_impl.h +++ b/neural_modelling/src/neuron/models/neuron_model_izh_impl.h @@ -65,6 +65,11 @@ struct neuron_t { REAL reset_h; }; +// Mark a value as possibly unused while not using any instructions, guaranteed +#ifndef __use +#define __use(x) do { (void) (x); } while (0) +#endif + static inline void neuron_model_initialise(neuron_t *state, neuron_params_t *params, uint32_t n_steps_per_timestep) { state->A = params->A; @@ -166,7 +171,10 @@ static inline void rk2_kernel_midpoint( static inline state_t neuron_model_state_update( uint16_t num_excitatory_inputs, const input_t *exc_input, uint16_t num_inhibitory_inputs, const input_t *inh_input, - input_t external_bias, REAL current_offset, neuron_t *restrict neuron) { + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t) { + __use(B_t); + REAL total_exc = ZERO; REAL total_inh = ZERO; diff --git a/neural_modelling/src/neuron/models/neuron_model_left_right_readout_impl.h b/neural_modelling/src/neuron/models/neuron_model_left_right_readout_impl.h new file mode 100644 index 00000000000..3409059d99d --- /dev/null +++ b/neural_modelling/src/neuron/models/neuron_model_left_right_readout_impl.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_MODEL_LIF_CURR_LEFT_RIGHT_READOUT_IMPL_H_ +#define _NEURON_MODEL_LIF_CURR_LEFT_RIGHT_READOUT_IMPL_H_ + +#include "neuron_model.h" +#include "random.h" + +#define SYNAPSES_PER_NEURON 250 + +extern REAL learning_signal; + +typedef struct eprop_syn_state_t { + REAL delta_w; // weight change to apply + REAL z_bar_inp; + REAL z_bar; // low-pass filtered spike train + int32_t update_ready; // counter to enable batch update (i.e. don't perform on every spike). +}eprop_syn_state_t; + +///////////////////////////////////////////////////////////// +// definition for LIF neuron parameters +typedef struct neuron_params_t { + // membrane voltage [mV] + REAL V_init; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane capacitance [nF] + REAL c_m; + + // membrane decay time constant + REAL tau_m; + + // offset current [nA] + REAL I_offset; + + // post-spike reset membrane voltage [mV] + REAL V_reset; + + // refractory time of neuron [ms] + REAL T_refract_ms; + + // initial refractory timer value (saved) + int32_t refract_timer_init; + + // The time step in milliseconds + REAL time_step; + + REAL L; // learning signal + REAL w_fb; // feedback weight + uint32_t window_size; + + // globals here + mars_kiss64_seed_t kiss_seed; // array of 4 values + REAL ticks_per_second; + REAL readout_V_0; + REAL readout_V_1; + REAL rate_on; + REAL rate_off; + REAL mean_0; + REAL mean_1; + REAL cross_entropy; + uint32_t p_key; + uint32_t p_pop_size; + REAL eta; + uint32_t number_of_cues; + + // array of synaptic states - peak fan-in of >250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; +}; + +///////////////////////////////////////////////////////////// +// definition for LIF neuron parameters +typedef struct neuron_t { + // membrane voltage [mV] + REAL V_membrane; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane resistance [MOhm] + REAL R_membrane; + + // 'fixed' computation parameter - time constant multiplier for + // closed-form solution + // exp(-(machine time step in ms)/(R * C)) [.] + REAL exp_TC; + + // offset current [nA] + REAL I_offset; + + // countdown to end of next refractory period [timesteps] + int32_t refract_timer; + + // post-spike reset membrane voltage [mV] + REAL V_reset; + + // refractory time of neuron [timesteps] + int32_t T_refract; + + REAL L; // learning signal + REAL w_fb; // feedback weight + uint32_t window_size; + + // former globals + mars_kiss64_seed_t kiss_seed; // array of 4 values + REAL ticks_per_second; + REAL readout_V_0; + REAL readout_V_1; + REAL rate_on; + REAL rate_off; + REAL mean_0; + REAL mean_1; + REAL cross_entropy; + uint32_t p_key; + uint32_t p_pop_size; + REAL eta; + uint32_t number_of_cues; + + // array of synaptic states - peak fan-in of >250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; + +} neuron_t; + +//! \brief Performs a ceil operation on an accum +//! \param[in] value The value to ceil +//! \return The ceil of the value +static inline int32_t lif_ceil_accum(REAL value) { + int32_t bits = bitsk(value); + int32_t integer = bits >> 15; + int32_t fraction = bits & 0x7FFF; + if (fraction > 0) { + return integer + 1; + } + return integer; +} + +static inline void neuron_model_initialise( + neuron_t *state, neuron_params_t *params, uint32_t n_steps_per_timestep) { + REAL ts = kdivui(params->time_step, n_steps_per_timestep); + state->V_membrane = params->V_init; + state->V_rest = params->V_rest; + state->R_membrane = kdivk(params->tau_m, params->c_m); + state->exp_TC = expk(-kdivk(ts, params->tau_m)); + state->I_offset = params->I_offset; + state->refract_timer = params->refract_timer_init; + state->V_reset = params->V_reset; + state->T_refract = lif_ceil_accum(kdivk(params->T_refract_ms, ts)); + + // for everything else just copy across for now + state->L = params->L; + state->w_fb = params->w_fb; + + state->window_size = params->window_size; + + // former globals + for (uint32_t n_seed = 0; n_seed < 4; n_seed++) { + state->kiss_seed[n_seed] = params->kiss_seed[n_seed]; // array of 4 values + } + + state->ticks_per_second = params->ticks_per_second; + state->readout_V_0 = params->readout_V_0; + state->readout_V_1 = params->readout_V_1; + state->rate_on = params->rate_on; + state->rate_off = params->rate_off; + state->mean_0 = params->mean_0; + state->mean_1 = params->mean_1; + state->cross_entropy = params->cross_entropy; + state->p_key = params->p_key; + state->p_pop_size = params->p_pop_size; + state->eta = params->eta; + state->number_of_cues = params->number_of_cues; + + log_info("Check p_key %u p_pop_size %u", params->p_key, params->p_pop_size); + log_info("Check number_of_cues %u eta %k", params->number_of_cues, params->eta); + log_info("mean_0 %k mean_1 %k rate_on %k rate_off %k readout_V_0 %k readout_V_1 %k", + params->mean_0, params->mean_1, params->rate_on, params->rate_off, + params->readout_V_0, params->readout_V_1); + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + state->syn_state[n_syn] = params->syn_state[n_syn]; + } +} + +static inline void neuron_model_save_state(neuron_t *state, neuron_params_t *params) { + params->V_init = state->V_membrane; + params->refract_timer_init = state->refract_timer; + params->L = state->L; + params->w_fb = state->w_fb; + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + params->syn_state[n_syn] = state->syn_state[n_syn]; + } +} + +// simple Leaky I&F ODE +static inline void lif_neuron_closed_form( + neuron_t *neuron, REAL V_prev, input_t input_this_timestep) { + + REAL alpha = input_this_timestep * neuron->R_membrane + neuron->V_rest; + + // update membrane voltage + neuron->V_membrane = alpha - (neuron->exp_TC * (alpha - V_prev)); +} + +state_t neuron_model_state_update( + uint16_t num_excitatory_inputs, const input_t* exc_input, + uint16_t num_inhibitory_inputs, const input_t* inh_input, + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t) { + + log_debug("Exc 1: %12.6k, Exc 2: %12.6k", exc_input[0], exc_input[1]); + log_debug("Inh 1: %12.6k, Inh 2: %12.6k", inh_input[0], inh_input[1]); + use(B_t); + + // If outside of the refractory period + if (neuron->refract_timer <= 0) { + // Get the input in nA + input_t input_this_timestep = + exc_input[0] + exc_input[1] + neuron->I_offset + external_bias + current_offset; + + lif_neuron_closed_form( + neuron, neuron->V_membrane, input_this_timestep); + } else { + + // countdown refractory timer + neuron->refract_timer -= 1; + } + + uint32_t total_synapses_per_neuron = 100; //todo should this be fixed? + + neuron->L = learning_signal * neuron->w_fb; //* ((accum)syn_ind * -1.k); + REAL local_eta = neuron->eta; + + // All subsequent operations now need doing once per eprop synapse + for (uint32_t syn_ind=0; syn_ind < total_synapses_per_neuron; syn_ind++){ + // ****************************************************************** + // Low-pass filter incoming spike train + // ****************************************************************** + neuron->syn_state[syn_ind].z_bar = + neuron->syn_state[syn_ind].z_bar * neuron->exp_TC + + (1.k - neuron->exp_TC) * neuron->syn_state[syn_ind].z_bar_inp; + // updating z_bar is problematic, if spike could come and interrupt neuron update + + // ****************************************************************** + // Update cached total weight change + // ****************************************************************** + REAL this_dt_weight_change = + local_eta * neuron->L * neuron->syn_state[syn_ind].z_bar; + + neuron->syn_state[syn_ind].delta_w -= this_dt_weight_change; + + // reset input (can't have more than one spike per timestep + neuron->syn_state[syn_ind].z_bar_inp = 0; + + // decrease timestep counter preventing rapid updates + neuron->syn_state[syn_ind].update_ready -= 1; + } + + return neuron->V_membrane; +} + +void neuron_model_has_spiked(neuron_t *restrict neuron) { + + // reset membrane voltage + neuron->V_membrane = neuron->V_reset; + + // reset refractory timer + neuron->refract_timer = neuron->T_refract; +} + +state_t neuron_model_get_membrane_voltage(const neuron_t *neuron) { + return neuron->V_membrane; +} + +void neuron_model_print_state_variables(const neuron_t *neuron) { + log_debug("V membrane = %11.4k mv", neuron->V_membrane); +} + +void neuron_model_print_parameters(const neuron_t *neuron) { + log_debug("V reset = %11.4k mv\n", neuron->V_reset); + log_debug("V rest = %11.4k mv\n", neuron->V_rest); + log_debug("I offset = %11.4k nA\n", neuron->I_offset); + log_debug("R membrane = %11.4k Mohm\n", neuron->R_membrane); + log_debug("exp(-ms/(RC)) = %11.4k [.]\n", neuron->exp_TC); + log_debug("T refract = %u timesteps\n", neuron->T_refract); + log_debug("learning = %k n/a\n", neuron->L); + log_debug("feedback w = %k n/a\n", neuron->w_fb); + log_debug("window size = %u n/a\n", neuron->window_size); + log_debug("T refract = %u timesteps\n", neuron->T_refract); +} + +#endif // _NEURON_MODEL_LIF_CURR_LEFT_RIGHT_READOUT_IMPL_H_ 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..0e88bb31be6 100644 --- a/neural_modelling/src/neuron/models/neuron_model_lif_impl.h +++ b/neural_modelling/src/neuron/models/neuron_model_lif_impl.h @@ -81,6 +81,11 @@ struct neuron_t { int32_t T_refract; }; +// Mark a value as possibly unused while not using any instructions, guaranteed +#ifndef __use +#define __use(x) do { (void) (x); } while (0) +#endif + //! \brief Performs a ceil operation on an accum //! \param[in] value The value to ceil //! \return The ceil of the value @@ -142,12 +147,14 @@ static inline void lif_neuron_closed_form( static inline state_t neuron_model_state_update( uint16_t num_excitatory_inputs, const input_t *exc_input, uint16_t num_inhibitory_inputs, const input_t *inh_input, - input_t external_bias, REAL current_offset, neuron_t *restrict neuron) { + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t) { + __use(B_t); // If outside of the refractory period if (neuron->refract_timer <= 0) { - REAL total_exc = ZERO; - REAL total_inh = ZERO; + REAL total_exc = ZERO; + REAL total_inh = ZERO; for (int i=0; i < num_excitatory_inputs; i++) { total_exc += exc_input[i]; diff --git a/neural_modelling/src/neuron/models/neuron_model_sinusoid_readout_impl.h b/neural_modelling/src/neuron/models/neuron_model_sinusoid_readout_impl.h new file mode 100644 index 00000000000..9f06dd49b4d --- /dev/null +++ b/neural_modelling/src/neuron/models/neuron_model_sinusoid_readout_impl.h @@ -0,0 +1,264 @@ +/* + * Copyright (c) 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 + * + * https://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 _NEURON_MODEL_SINUSOID_READOUT_IMPL_H_ +#define _NEURON_MODEL_SINUSOID_READOUT_IMPL_H_ + +#include "neuron_model.h" +#include "random.h" +#include + +#define SYNAPSES_PER_NEURON 250 + +extern REAL learning_signal; + +typedef struct eprop_syn_state_t { + REAL delta_w; // weight change to apply + REAL z_bar_inp; + REAL z_bar; // low-pass filtered spike train + uint32_t update_ready; // counter to enable batch update (i.e. don't perform on every spike). +}eprop_syn_state_t; + +///////////////////////////////////////////////////////////// +// definition for LIF-sinusoid neuron parameters +struct neuron_params_t { + // membrane voltage [mV] + REAL V_init; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane capacitance [nF] + REAL c_m; + + // membrane decay time constant + REAL tau_m; + + // offset current (nA) + REAL I_offset; + + // post-spike reset membrane voltage (mV) + REAL V_reset; + + // refractory time of neuron [ms] + REAL T_refract_ms; + + // initial refractory timer value (saved) + int32_t refract_timer_init; + + // The time step in milliseconds + REAL time_step; + + REAL L; // learning signal + REAL w_fb; // feedback weight + + // former globals + REAL target_V[1024]; + REAL eta; + + // array of synaptic states - peak fan-in of 250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; +}; + + +typedef struct neuron_t { + // membrane voltage [mV] + REAL V_membrane; + + // membrane resting voltage [mV] + REAL V_rest; + + // membrane resistance [MOhm] + REAL R_membrane; + + // 'fixed' computation parameter - time constant multiplier for + // closed-form solution + // exp(-(machine time step in ms)/(R * C)) [.] + REAL exp_TC; + + // offset current [nA] + REAL I_offset; + + // countdown to end of next refractory period [timesteps] + int32_t refract_timer; + + // post-spike reset membrane voltage [mV] + REAL V_reset; + + // refractory time of neuron [timesteps] + int32_t T_refract; + + REAL L; // learning signal + REAL w_fb; // feedback weight + + // former globals + REAL target_V[1024]; // this could be problematic for DTCM usage + REAL eta; + + // array of synaptic states - peak fan-in of >250 for this case + eprop_syn_state_t syn_state[SYNAPSES_PER_NEURON]; + +} neuron_t; + +//! \brief Performs a ceil operation on an accum +//! \param[in] value The value to ceil +//! \return The ceil of the value +static inline int32_t lif_ceil_accum(REAL value) { + int32_t bits = bitsk(value); + int32_t integer = bits >> 15; + int32_t fraction = bits & 0x7FFF; + if (fraction > 0) { + return integer + 1; + } + return integer; +} + +static inline void neuron_model_initialise( + neuron_t *state, neuron_params_t *params, uint32_t n_steps_per_timestep) { + REAL ts = kdivui(params->time_step, n_steps_per_timestep); + state->V_membrane = params->V_init; + state->V_rest = params->V_rest; + state->R_membrane = kdivk(params->tau_m, params->c_m); + state->exp_TC = expk(-kdivk(ts, params->tau_m)); + state->I_offset = params->I_offset; + state->refract_timer = params->refract_timer_init; + state->V_reset = params->V_reset; + state->T_refract = lif_ceil_accum(kdivk(params->T_refract_ms, ts)); + + // for everything else just copy across for now + state->L = params->L; + state->w_fb = params->w_fb; + + for (uint32_t n_v = 0; n_v < 1024; n_v++) { + state->target_V[n_v] = params->target_V[n_v]; + } + state->eta = params->eta; + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + state->syn_state[n_syn] = params->syn_state[n_syn]; + } +} + +static inline void neuron_model_save_state(neuron_t *state, neuron_params_t *params) { + params->V_init = state->V_membrane; + params->refract_timer_init = state->refract_timer; + params->L = state->L; + params->w_fb = state->w_fb; + + for (uint32_t n_syn = 0; n_syn < SYNAPSES_PER_NEURON; n_syn++) { + params->syn_state[n_syn] = state->syn_state[n_syn]; + } +} + +// simple Leaky I&F ODE +static inline void lif_neuron_closed_form( + neuron_t *neuron, REAL V_prev, input_t input_this_timestep) { + + REAL alpha = input_this_timestep * neuron->R_membrane + neuron->V_rest; + + // update membrane voltage + neuron->V_membrane = alpha - (neuron->exp_TC * (alpha - V_prev)); +} + +state_t neuron_model_state_update( + uint16_t num_excitatory_inputs, const input_t* exc_input, + uint16_t num_inhibitory_inputs, const input_t* inh_input, + input_t external_bias, REAL current_offset, neuron_t *restrict neuron, + REAL B_t) { + + log_debug("Exc 1: %12.6k, Exc 2: %12.6k", exc_input[0], exc_input[1]); + log_debug("Inh 1: %12.6k, Inh 2: %12.6k", inh_input[0], inh_input[1]); + use(B_t); + + // If outside of the refractory period + if (neuron->refract_timer <= 0) { + // Get the input in nA + input_t input_this_timestep = + exc_input[0] + exc_input[1] + neuron->I_offset + external_bias + current_offset; + + lif_neuron_closed_form( + neuron, neuron->V_membrane, input_this_timestep); + } else { + + // countdown refractory timer + neuron->refract_timer -= 1; + } + + uint32_t total_synapses_per_neuron = 100; //todo should this be fixed? + + neuron->L = learning_signal * neuron->w_fb; + REAL local_eta = neuron->eta; + + // All operations now need doing once per eprop synapse + for (uint32_t syn_ind=0; syn_ind < total_synapses_per_neuron; syn_ind++){ + // ****************************************************************** + // Low-pass filter incoming spike train + // ****************************************************************** + neuron->syn_state[syn_ind].z_bar = + neuron->syn_state[syn_ind].z_bar * neuron->exp_TC + + neuron->syn_state[syn_ind].z_bar_inp; + // updating z_bar is problematic, if spike could come and interrupt neuron update + + // ****************************************************************** + // Update cached total weight change + // ****************************************************************** + REAL this_dt_weight_change = + local_eta * neuron->L * neuron->syn_state[syn_ind].z_bar; + + neuron->syn_state[syn_ind].delta_w -= this_dt_weight_change; + + // reset input (can't have more than one spike per timestep + neuron->syn_state[syn_ind].z_bar_inp = 0; + + // decrease timestep counter preventing rapid updates + if (neuron->syn_state[syn_ind].update_ready > 0){ + neuron->syn_state[syn_ind].update_ready -= 1; + } + } + + return neuron->V_membrane; +} + +void neuron_model_has_spiked(neuron_t *restrict neuron) { + + // reset membrane voltage + neuron->V_membrane = neuron->V_reset; + + // reset refractory timer + neuron->refract_timer = neuron->T_refract; +} + +state_t neuron_model_get_membrane_voltage(const neuron_t *neuron) { + return neuron->V_membrane; +} + +void neuron_model_print_state_variables(const neuron_t *neuron) { + log_debug("V membrane = %11.4k mv", neuron->V_membrane); +} + +void neuron_model_print_parameters(const neuron_t *neuron) { + log_debug("V reset = %11.4k mv\n", neuron->V_reset); + log_debug("V rest = %11.4k mv\n", neuron->V_rest); + log_debug("I offset = %11.4k nA\n", neuron->I_offset); + log_debug("R membrane = %11.4k Mohm\n", neuron->R_membrane); + log_debug("exp(-ms/(RC)) = %11.4k [.]\n", neuron->exp_TC); + log_debug("T refract = %u timesteps\n", neuron->T_refract); + log_debug("learning = %k n/a\n", neuron->L); + log_debug("feedback w = %k n/a\n\n", neuron->w_fb); + log_debug("T refract = %u timesteps\n", neuron->T_refract); +} + +#endif // _NEURON_MODEL_SINUSOID_READOUT_IMPL_H_ diff --git a/neural_modelling/src/neuron/neuron.c b/neural_modelling/src/neuron/neuron.c index 03728a76a31..ad1dea2096b 100644 --- a/neural_modelling/src/neuron/neuron.c +++ b/neural_modelling/src/neuron/neuron.c @@ -25,6 +25,16 @@ #include "plasticity/synapse_dynamics.h" #include +//// declare spin1_wfi +//extern void spin1_wfi(void); +// +//// Spin1 API ticks - to know when the timer wraps +//extern uint ticks; +// +//#define SPIKE_RECORDING_CHANNEL 0 + +//! The key to be used for this core (will be ORed with neuron ID) +//key_t key; //MADE NON STATIC!!! //! The keys to be used by the neurons (one per neuron) uint32_t *neuron_keys; @@ -209,7 +219,7 @@ void neuron_transfer(weight_t *syns) { // EXPORTED uint32_t neuron_index = 0; for (uint32_t n_i = n_neurons_peak; n_i > 0; n_i--) { weight_t value = syns[ring_buffer_index]; - if (value > 0) { + if (value != 0) { if (neuron_index > n_neurons) { log_error("Neuron index %u out of range", neuron_index); rt_error(RTE_SWERR); diff --git a/neural_modelling/src/neuron/neuron_recording.h b/neural_modelling/src/neuron/neuron_recording.h index e6a35caf6d7..33d0d16d914 100644 --- a/neural_modelling/src/neuron/neuron_recording.h +++ b/neural_modelling/src/neuron/neuron_recording.h @@ -201,6 +201,7 @@ static inline void neuron_recording_record(uint32_t time) { bf_info->count += bf_info->increment; } } + } //! \brief sets up state for next recording. diff --git a/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h b/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h index 3799b60d931..022e1aff8e9 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h +++ b/neural_modelling/src/neuron/plasticity/stdp/stdp_typedefs.h @@ -37,6 +37,7 @@ //! \return The product #define STDP_FIXED_MUL_16X16(a, b) maths_fixed_mul16(a, b, STDP_FIXED_POINT) +#define PRINT_PLASTICITY 0 //! 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_eprop_adaptive_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_eprop_adaptive_impl.c new file mode 100644 index 00000000000..75c598cea31 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_eprop_adaptive_impl.c @@ -0,0 +1,446 @@ +/* + * Copyright (c) 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 + * + * https://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. + */ + +// Spinn_common includes +#include "static-assert.h" + +// sPyNNaker neural modelling includes +#include + +// Plasticity includes +#include "maths.h" +#include "post_events.h" +#include "synapse_dynamics_stdp_common.h" + +//--------------------------------------- +// Structures +//--------------------------------------- +//! 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[]; +}; + +// TODO: make work with stdp common? (is this even really STDP?) + +#include + +extern neuron_t *neuron_array; + +extern uint32_t neuron_impl_neurons_in_partition; + +//--------------------------------------- +// Macros +//--------------------------------------- +// The plastic control words used by Morrison synapses store an axonal delay +// in the upper 3 bits. +// Assuming a maximum of 16 delay slots, this is all that is required as: +// +// 1) Dendritic + Axonal <= 15 +// 2) Dendritic >= Axonal +// +// Therefore: +// +// * Maximum value of dendritic delay is 15 (with axonal delay of 0) +// - It requires 4 bits +// * Maximum value of axonal delay is 7 (with dendritic delay of 8) +// - It requires 3 bits +// +// | Axonal delay | Dendritic delay | Type | Index | +// |---------------------------|--------------------|-------------------|--------------------| +// | SYNAPSE_AXONAL_DELAY_BITS | SYNAPSE_DELAY_BITS | SYNAPSE_TYPE_BITS | SYNAPSE_INDEX_BITS | +// | | | SYNAPSE_TYPE_INDEX_BITS | +// |---------------------------|--------------------|----------------------------------------| +#ifndef SYNAPSE_AXONAL_DELAY_BITS +#define SYNAPSE_AXONAL_DELAY_BITS 3 +#endif + +#define SYNAPSE_AXONAL_DELAY_MASK \ + ((1 << SYNAPSE_AXONAL_DELAY_BITS) - 1) + +uint32_t RECURRENT_SYNAPSE_OFFSET = 100; + +//--------------------------------------- +// 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]; +} + +void synapse_dynamics_print_plastic_synapses( + synapse_row_plastic_data_t *plastic_region_data, + synapse_row_fixed_part_t *fixed_region, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + __use(plastic_region_data); + __use(fixed_region); + __use(ring_buffer_to_input_buffer_left_shifts); + +#if LOG_LEVEL >= LOG_DEBUG + // Extract separate arrays of weights (from plastic region), + // Control words (from fixed region) and number of plastic synapses + const plastic_synapse_t *plastic_words = plastic_region_data->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + size_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + log_debug("Plastic region %u synapses\n", plastic_synapse); + + // Loop through plastic synapses + for (uint32_t i = 0; i < plastic_synapse; i++) { + // Get next control word (auto incrementing control word) + uint32_t control_word = *control_words++; + uint32_t synapse_type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + + // Get weight + update_state_t update_state = synapse_structure_get_update_state( + *plastic_words++, synapse_type); + final_state_t final_state = synapse_structure_get_final_state( + update_state); + weight_t weight = synapse_structure_get_final_weight(final_state); + + log_debug("%08x [%3d: (w: %5u (=", control_word, i, weight); + synapses_print_weight( + weight, ring_buffer_to_input_buffer_left_shifts[synapse_type]); + log_debug("nA) d: %2u, %s, n = %3u)] - {%08x %08x}\n", + synapse_row_sparse_delay( + control_word, synapse_type_index_bits, synapse_delay_mask), + synapse_types_get_type_char(synapse_type), + synapse_row_sparse_index(control_word, synapse_index_mask), + synapse_delay_mask, synapse_type_index_bits); + } +#endif // LOG_LEVEL >= LOG_DEBUG +} + +//--------------------------------------- +static inline index_t sparse_axonal_delay(uint32_t x) { +#if 1 + 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 final_state_t eprop_plasticity_update( + update_state_t current_state, REAL delta_w) { + + int32_t delta_w_int = (int32_t) roundk(delta_w, 15); + // TODO: THIS NEEDS UPDATING TO APPROPRIATE SCALING (?) + + if (delta_w){ // TODO: This should probably be delta_w_int + if (delta_w_int < 0){ + current_state = weight_one_term_apply_depression( + current_state, delta_w_int << 3); + } else { + current_state = weight_one_term_apply_potentiation( + current_state, delta_w_int << 3); + } + } + + // Calculate regularisation error + REAL reg_error = neuron_array[0].core_target_rate - ( + neuron_array[0].core_pop_rate / neuron_impl_neurons_in_partition); + // this needs swapping for an inverse multiply - too expensive to do divide + // on every spike + + // Return final synaptic word and weight + return synapse_structure_get_final_state(current_state, reg_error); +} + +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 plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + num_plastic_pre_synaptic_events += plastic_synapse; + + // Could maybe have a single z_bar for the entire synaptic row and + // update it once here for all synaptic words? + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + uint32_t control_word = *control_words++; + + // Extract control-word components + // **NOTE** cunningly, control word is just the same as lower + // 16-bits of 32-bit fixed synapse so same functions can be used + uint32_t delay = 1; + uint32_t syn_ind_from_delay = synapse_row_sparse_delay( + control_word, synapse_type_index_bits, synapse_delay_mask); + + uint32_t type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + uint32_t index = + synapse_row_sparse_index(control_word, synapse_index_mask); + uint32_t type_index = synapse_row_sparse_type_index( + control_word, synapse_type_index_mask); + + uint32_t neuron_ind = synapse_row_sparse_index( + control_word, synapse_index_mask); + + // For low pass filter of incoming spike train on this synapse + // Use postsynaptic neuron index to access neuron struct, + + if (type==1) { + // this is a recurrent synapse: add 100 to index to + // correct array location + syn_ind_from_delay += RECURRENT_SYNAPSE_OFFSET; + } + + // Create update state from the plastic synaptic word + update_state_t current_state = + synapse_structure_get_update_state(*plastic_words, type); + + neuron_t *neuron = &neuron_array[neuron_ind]; + neuron->syn_state[syn_ind_from_delay].z_bar_inp = 1024.0k; + // !!!! TODO: Check what units this is in - same as weight? !!!! + + // Perform weight update: only if batch time has elapsed + final_state_t final_state; + if (neuron->syn_state[syn_ind_from_delay].update_ready <= 0){ + // enough time has elapsed - perform weight update + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready=0\n"); + } + + // Go through typical weight update process to clip to limits + final_state = eprop_plasticity_update(current_state, + neuron->syn_state[syn_ind_from_delay].delta_w); + + // reset delta_w as weight change has now been applied + neuron->syn_state[syn_ind_from_delay].delta_w = 0.0k; + + // reset update_ready counter based on pattern cycle time + neuron->syn_state[syn_ind_from_delay].update_ready += neuron->window_size; + + } else { + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready: %u/%u - no update performed\n", + neuron->syn_state[syn_ind_from_delay].update_ready, syn_ind_from_delay); + } + // don't update weight - get update state based on state cached in SDRAM + // assume reg rate is zero to avoid + + final_state = synapse_structure_get_final_state(current_state, 0); + // Don't reset delta_w -> keep this accumulating and apply weight change in future + } + + // Add contribution to synaptic input + // Convert into ring buffer offset + uint32_t ring_buffer_index = synapse_row_get_ring_buffer_index_combined( + time, type_index, + synapse_type_index_bits, synapse_delay_mask); + + // Check for ring buffer saturation + int16_t accumulation = ring_buffers[ring_buffer_index] + + synapse_structure_get_final_weight(final_state); + + // overflow check + if (accumulation < ring_buffers[ring_buffer_index] + ( + synapse_structure_get_final_weight(final_state)) + && ring_buffers[ring_buffer_index] > 0 && ( + synapse_structure_get_final_weight(final_state) > 0)) { + accumulation = ring_buffers[ring_buffer_index]; + } + // underflow check + if (accumulation > ring_buffers[ring_buffer_index] + ( + synapse_structure_get_final_weight(final_state)) + && ring_buffers[ring_buffer_index] < 0 && ( + synapse_structure_get_final_weight(final_state) < 0)) { + accumulation = ring_buffers[ring_buffer_index]; + } + + ring_buffers[ring_buffer_index] = accumulation; + + // Write back updated synaptic word to plastic region + *plastic_words++ = + synapse_structure_get_final_synaptic_word(final_state); + } + *write_back = true; + return true; +} + +void synapse_dynamics_process_post_synaptic_event( + uint32_t time, index_t neuron_index) { + log_debug("Adding post-synaptic event to trace at time:%u", time); + + // 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)); +} + +// TODO: fix below to match other dynamics impls? (Do we want structural plasticity +// mixed with eprop??) + +#if SYNGEN_ENABLED == 1 + +//! \brief Searches the synaptic row for the the connection with the +//! specified post-synaptic ID +//! \param[in] id: the (core-local) ID of the neuron to search for in the +//! synaptic row +//! \param[in] row: the core-local address of the synaptic row +//! \param[out] sp_data: the address of a struct through which to return +//! weight, delay information +//! \return bool: was the search successful? +bool find_plastic_neuron_with_id( + uint32_t id, address_t row, structural_plasticity_data_t *sp_data) { + address_t fixed_region = synapse_row_fixed_region(row); + address_t plastic_region_address = synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(plastic_region_address); + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + plastic_synapse_t weight; + uint32_t delay; + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + weight = *plastic_words++; + uint32_t control_word = *control_words++; + + // Check if index is the one I'm looking for + delay = synapse_row_sparse_delay(control_word, synapse_type_index_bits); + if (synapse_row_sparse_index(control_word, synapse_index_mask) == id) { + sp_data->weight = weight; + sp_data->offset = + synapse_row_num_plastic_controls(fixed_region) + - plastic_synapse; + sp_data->delay = delay; + return true; + } + } + + sp_data->weight = -1; + sp_data->offset = -1; + sp_data->delay = -1; + return false; +} + +//! \brief Remove the entry at the specified offset in the synaptic row +//! \param[in] offset: the offset in the row at which to remove the entry +//! \param[in] row: the core-local address of the synaptic row +//! \return bool: was the removal successful? +bool remove_plastic_neuron_at_offset(uint32_t offset, address_t row) { + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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]; + plastic_words[plastic_synapse - 1] = 0; + + // Delete control word at offset + control_words[offset] = control_words[plastic_synapse - 1]; + control_words[plastic_synapse - 1] = 0; + + // Decrement FP + fixed_region[1]--; + + return true; +} + +//! ensuring the weight is of the correct type and size +static inline plastic_synapse_t weight_conversion(uint32_t weight) { + return (plastic_synapse_t) (0xFFFF & weight); +} + +//! packing all of the information into the required plastic control word +static inline control_t control_conversion( + uint32_t id, uint32_t delay, uint32_t type) { + control_t new_control = + (delay & ((1 << SYNAPSE_DELAY_BITS) - 1)) << synapse_type_index_bits; + new_control |= (type & ((1 << synapse_type_index_bits) - 1)) << synapse_index_bits; + new_control |= id & ((1 << synapse_index_bits) - 1); + return new_control; +} + +//! \brief Add a plastic entry in the synaptic row +//! \param[in] id: the (core-local) ID of the post-synaptic neuron to be added +//! \param[in] row: the core-local address of the synaptic row +//! \param[in] weight: the initial weight associated with the connection +//! \param[in] delay: the delay associated with the connection +//! \param[in] type: the type of the connection (e.g. inhibitory) +//! \return bool: was the addition successful? +bool add_plastic_neuron_with_id(uint32_t id, address_t row, + uint32_t weight, uint32_t delay, uint32_t type) { + plastic_synapse_t new_weight = weight_conversion(weight); + control_t new_control = control_conversion(id, delay, type); + + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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[1]++; + return true; +} +#endif diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_left_right_readout_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_left_right_readout_impl.c new file mode 100644 index 00000000000..91447305117 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_left_right_readout_impl.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 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 + * + * https://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. + */ + +// Spinn_common includes +#include "static-assert.h" + +// sPyNNaker neural modelling includes +#include + +// Plasticity includes +#include "maths.h" +#include "post_events.h" +#include "synapse_dynamics_stdp_common.h" + +#include + +extern neuron_t *neuron_array; + +//--------------------------------------- +// Macros +//--------------------------------------- +// The plastic control words used by Morrison synapses store an axonal delay +// in the upper 3 bits. +// Assuming a maximum of 16 delay slots, this is all that is required as: +// +// 1) Dendritic + Axonal <= 15 +// 2) Dendritic >= Axonal +// +// Therefore: +// +// * Maximum value of dendritic delay is 15 (with axonal delay of 0) +// - It requires 4 bits +// * Maximum value of axonal delay is 7 (with dendritic delay of 8) +// - It requires 3 bits +// +// | Axonal delay | Dendritic delay | Type | Index | +// |---------------------------|--------------------|-------------------|--------------------| +// | SYNAPSE_AXONAL_DELAY_BITS | SYNAPSE_DELAY_BITS | SYNAPSE_TYPE_BITS | SYNAPSE_INDEX_BITS | +// | | | SYNAPSE_TYPE_INDEX_BITS | +// |---------------------------|--------------------|----------------------------------------| +#ifndef SYNAPSE_AXONAL_DELAY_BITS +#define SYNAPSE_AXONAL_DELAY_BITS 3 +#endif + +#define SYNAPSE_AXONAL_DELAY_MASK \ + ((1 << SYNAPSE_AXONAL_DELAY_BITS) - 1) + + +uint32_t RECURRENT_SYNAPSE_OFFSET = 100; + +////--------------------------------------- +//// Structures +////--------------------------------------- +//! 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[]; +}; + +//--------------------------------------- +// 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]; +} + +void synapse_dynamics_print_plastic_synapses( + synapse_row_plastic_data_t *plastic_region_data, + synapse_row_fixed_part_t *fixed_region, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + __use(plastic_region_data); + __use(fixed_region); + __use(ring_buffer_to_input_buffer_left_shifts); + +#if LOG_LEVEL >= LOG_DEBUG + // Extract separate arrays of weights (from plastic region), + // Control words (from fixed region) and number of plastic synapses + const plastic_synapse_t *plastic_words = plastic_region_data->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + size_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + log_debug("Plastic region %u synapses\n", plastic_synapse); + + // Loop through plastic synapses + for (uint32_t i = 0; i < plastic_synapse; i++) { + // Get next control word (auto incrementing control word) + uint32_t control_word = *control_words++; + uint32_t synapse_type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + + // Get weight + update_state_t update_state = synapse_structure_get_update_state( + *plastic_words++, synapse_type); + final_state_t final_state = synapse_structure_get_final_state( + update_state); + weight_t weight = synapse_structure_get_final_weight(final_state); + + log_debug("%08x [%3d: (w: %5u (=", control_word, i, weight); + synapses_print_weight( + weight, ring_buffer_to_input_buffer_left_shifts[synapse_type]); + log_debug("nA) d: %2u, %s, n = %3u)] - {%08x %08x}\n", + synapse_row_sparse_delay(control_word, synapse_type_index_bits, synapse_delay_mask), + synapse_types_get_type_char(synapse_type), + synapse_row_sparse_index(control_word, synapse_index_mask), + synapse_delay_mask, synapse_type_index_bits); + } +#endif // LOG_LEVEL >= LOG_DEBUG +} + +//--------------------------------------- +static inline index_t sparse_axonal_delay(uint32_t x) { +#if 1 + 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 final_state_t eprop_plasticity_update( + update_state_t current_state, REAL delta_w){ + + // Convert delta_w to int16_t (same as weight) - take only integer bits from REAL? + int32_t delta_w_int = (int32_t)roundk(delta_w, 15); + // TODO: THIS NEEDS UPDATING TO APPROPRIATE SCALING (?) + + if (delta_w){ // TODO: delta_w_int instead? + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "delta_w: %k, delta_w_int: %d\n", + delta_w, delta_w_int); + } + + if (delta_w_int < 0){ + current_state = weight_one_term_apply_depression( + current_state, delta_w_int << 3); + } else { + current_state = weight_one_term_apply_potentiation( + current_state, delta_w_int << 3); + } + } + + // Regularisation error isn't used in this case + REAL reg_error = 0.0; + + // Return final synaptic word and weight + return synapse_structure_get_final_state(current_state, reg_error); +} + +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 plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + num_plastic_pre_synaptic_events += plastic_synapse; + + // TODO: Could maybe have a single z_bar for the entire synaptic row + // and update it once here for all synaptic words? + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + uint32_t control_word = *control_words++; + + // Extract control-word components + // **NOTE** cunningly, control word is just the same as lower + // 16-bits of 32-bit fixed synapse so same functions can be used + uint32_t delay = 1; + uint32_t syn_ind_from_delay = synapse_row_sparse_delay( + control_word, synapse_type_index_bits, synapse_delay_mask); + + uint32_t type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + uint32_t index = + synapse_row_sparse_index(control_word, synapse_index_mask); + uint32_t type_index = synapse_row_sparse_type_index( + control_word, synapse_type_index_mask); + + int32_t neuron_ind = synapse_row_sparse_index( + control_word, synapse_index_mask); + + // For low pass filter of incoming spike train on this synapse + // Use postsynaptic neuron index to access neuron struct, + + if (type==1) { + // this is a recurrent synapse: add 100 to index to correct array location + syn_ind_from_delay += RECURRENT_SYNAPSE_OFFSET; + } + + neuron_t *neuron = &neuron_array[neuron_ind]; + neuron->syn_state[syn_ind_from_delay].z_bar_inp = 1024.0k; + // !!!! TODO: Check what units this is in - same as weight? !!!! + + // Create update state from the plastic synaptic word + update_state_t current_state = + synapse_structure_get_update_state(*plastic_words, type); + + // Perform weight update: only if batch time has elapsed + final_state_t final_state; + + if (neuron->syn_state[syn_ind_from_delay].update_ready <= 0){ + // enough time has elapsed - perform weight update + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready=0\n"); + } + + // Go through typical weight update process to clip to limits + final_state = eprop_plasticity_update(current_state, + neuron->syn_state[syn_ind_from_delay].delta_w); + + // reset delta_w as weight change has now been applied + neuron->syn_state[syn_ind_from_delay].delta_w = 0.0k; + + // reset update_ready counter based on pattern cycle time + neuron->syn_state[syn_ind_from_delay].update_ready += neuron->window_size; + + } else { + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready: %u - no update performed\n", + neuron->syn_state[syn_ind_from_delay].update_ready); + } + // don't update weight - get update state based on state cached in SDRAM + // assume reg rate is zero to avoid + + final_state = synapse_structure_get_final_state(current_state, 0); + // Don't reset delta_w -> keep this accumulating and apply weight change in future + } + + // Add contribution to synaptic input + // Convert into ring buffer offset + uint32_t ring_buffer_index = synapse_row_get_ring_buffer_index_combined( + time, type_index, + synapse_type_index_bits, synapse_delay_mask); + + // Check for ring buffer saturation + int16_t accumulation = ring_buffers[ring_buffer_index] + + synapse_structure_get_final_weight(final_state); + + // overflow check + if (accumulation < ring_buffers[ring_buffer_index] + ( + synapse_structure_get_final_weight(final_state)) + && ring_buffers[ring_buffer_index] > 0 && ( + synapse_structure_get_final_weight(final_state) > 0)){ + accumulation = ring_buffers[ring_buffer_index]; + plastic_saturation_count++; + } + // underflow check + if (accumulation > ring_buffers[ring_buffer_index] + ( + synapse_structure_get_final_weight(final_state)) + && ring_buffers[ring_buffer_index] < 0 && ( + synapse_structure_get_final_weight(final_state) < 0)) { + accumulation = ring_buffers[ring_buffer_index]; + plastic_saturation_count++; + } + + ring_buffers[ring_buffer_index] = accumulation; + + // Write back updated synaptic word to plastic region + *plastic_words++ = + synapse_structure_get_final_synaptic_word(final_state); + } + // TODO: set write_back here? + *write_back = true; + return true; +} + +void synapse_dynamics_process_post_synaptic_event( + uint32_t time, index_t neuron_index) { + log_debug("Adding post-synaptic event to trace at time:%u", time); + + // 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)); +} + +// TODO: fix below to match other dynamics impls so that structural +// plasticity can be used if desired? + +#if SYNGEN_ENABLED == 1 + +//! \brief Searches the synaptic row for the the connection with the +//! specified post-synaptic ID +//! \param[in] id: the (core-local) ID of the neuron to search for in the +//! synaptic row +//! \param[in] row: the core-local address of the synaptic row +//! \param[out] sp_data: the address of a struct through which to return +//! weight, delay information +//! \return bool: was the search successful? +bool find_plastic_neuron_with_id( + uint32_t id, address_t row, structural_plasticity_data_t *sp_data) { + address_t fixed_region = synapse_row_fixed_region(row); + address_t plastic_region_address = synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(plastic_region_address); + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + plastic_synapse_t weight; + uint32_t delay; + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + weight = *plastic_words++; + uint32_t control_word = *control_words++; + + // Check if index is the one I'm looking for + delay = synapse_row_sparse_delay(control_word, synapse_type_index_bits); + if (synapse_row_sparse_index(control_word, synapse_index_mask) == id) { + sp_data->weight = weight; + sp_data->offset = + synapse_row_num_plastic_controls(fixed_region) + - plastic_synapse; + sp_data->delay = delay; + return true; + } + } + + sp_data->weight = -1; + sp_data->offset = -1; + sp_data->delay = -1; + return false; +} + +//! \brief Remove the entry at the specified offset in the synaptic row +//! \param[in] offset: the offset in the row at which to remove the entry +//! \param[in] row: the core-local address of the synaptic row +//! \return bool: was the removal successful? +bool remove_plastic_neuron_at_offset(uint32_t offset, address_t row) { + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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]; + plastic_words[plastic_synapse - 1] = 0; + + // Delete control word at offset + control_words[offset] = control_words[plastic_synapse - 1]; + control_words[plastic_synapse - 1] = 0; + + // Decrement FP + fixed_region[1]--; + + return true; +} + +//! ensuring the weight is of the correct type and size +static inline plastic_synapse_t weight_conversion(uint32_t weight) { + return (plastic_synapse_t) (0xFFFF & weight); +} + +//! packing all of the information into the required plastic control word +static inline control_t control_conversion( + uint32_t id, uint32_t delay, uint32_t type) { + control_t new_control = + (delay & ((1 << SYNAPSE_DELAY_BITS) - 1)) << synapse_type_index_bits; + new_control |= (type & ((1 << synapse_type_index_bits) - 1)) << synapse_index_bits; + new_control |= id & ((1 << synapse_index_bits) - 1); + return new_control; +} + +//! \brief Add a plastic entry in the synaptic row +//! \param[in] id: the (core-local) ID of the post-synaptic neuron to be added +//! \param[in] row: the core-local address of the synaptic row +//! \param[in] weight: the initial weight associated with the connection +//! \param[in] delay: the delay associated with the connection +//! \param[in] type: the type of the connection (e.g. inhibitory) +//! \return bool: was the addition successful? +bool add_plastic_neuron_with_id(uint32_t id, address_t row, + uint32_t weight, uint32_t delay, uint32_t type) { + plastic_synapse_t new_weight = weight_conversion(weight); + control_t new_control = control_conversion(id, delay, type); + + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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[1]++; + return true; +} +#endif diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_sinusoid_readout_impl.c b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_sinusoid_readout_impl.c new file mode 100644 index 00000000000..ff1edd97eb9 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_dynamics_sinusoid_readout_impl.c @@ -0,0 +1,433 @@ +/* + * Copyright (c) 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 + * + * https://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. + */ + +// Spinn_common includes +#include "static-assert.h" + +// sPyNNaker neural modelling includes +#include + +// Plasticity includes +#include "maths.h" +#include "post_events.h" +#include "synapse_dynamics_stdp_common.h" + +#include + +extern neuron_t *neuron_array; + +//--------------------------------------- +// Macros +//--------------------------------------- +// The plastic control words used by Morrison synapses store an axonal delay +// in the upper 3 bits. +// Assuming a maximum of 16 delay slots, this is all that is required as: +// +// 1) Dendritic + Axonal <= 15 +// 2) Dendritic >= Axonal +// +// Therefore: +// +// * Maximum value of dendritic delay is 15 (with axonal delay of 0) +// - It requires 4 bits +// * Maximum value of axonal delay is 7 (with dendritic delay of 8) +// - It requires 3 bits +// +// | Axonal delay | Dendritic delay | Type | Index | +// |---------------------------|--------------------|-------------------|--------------------| +// | SYNAPSE_AXONAL_DELAY_BITS | SYNAPSE_DELAY_BITS | SYNAPSE_TYPE_BITS | SYNAPSE_INDEX_BITS | +// | | | SYNAPSE_TYPE_INDEX_BITS | +// |---------------------------|--------------------|----------------------------------------| +#ifndef SYNAPSE_AXONAL_DELAY_BITS +#define SYNAPSE_AXONAL_DELAY_BITS 3 +#endif + +#define SYNAPSE_AXONAL_DELAY_MASK \ + ((1 << SYNAPSE_AXONAL_DELAY_BITS) - 1) + + +uint32_t RECURRENT_SYNAPSE_OFFSET = 100; + +//--------------------------------------- +// Structures +//--------------------------------------- +//! 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[]; +}; + +//--------------------------------------- +// 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]; +} + +void synapse_dynamics_print_plastic_synapses( + synapse_row_plastic_data_t *plastic_region_data, + synapse_row_fixed_part_t *fixed_region, + uint32_t *ring_buffer_to_input_buffer_left_shifts) { + __use(plastic_region_data); + __use(fixed_region); + __use(ring_buffer_to_input_buffer_left_shifts); + +#if LOG_LEVEL >= LOG_DEBUG + // Extract separate arrays of weights (from plastic region), + // Control words (from fixed region) and number of plastic synapses + const plastic_synapse_t *plastic_words = plastic_region_data->synapses; + const control_t *control_words = synapse_row_plastic_controls(fixed_region); + size_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + log_debug("Plastic region %u synapses\n", plastic_synapse); + + // Loop through plastic synapses + for (uint32_t i = 0; i < plastic_synapse; i++) { + // Get next control word (auto incrementing control word) + uint32_t control_word = *control_words++; + uint32_t synapse_type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + + // Get weight + update_state_t update_state = synapse_structure_get_update_state( + *plastic_words++, synapse_type); + final_state_t final_state = synapse_structure_get_final_state( + update_state); + weight_t weight = synapse_structure_get_final_weight(final_state); + + log_debug("%08x [%3d: (w: %5u (=", control_word, i, weight); + synapses_print_weight( + weight, ring_buffer_to_input_buffer_left_shifts[synapse_type]); + log_debug("nA) d: %2u, %s, n = %3u)] - {%08x %08x}\n", + synapse_row_sparse_delay( + control_word, synapse_type_index_bits, synapse_delay_mask), + synapse_types_get_type_char(synapse_type), + synapse_row_sparse_index(control_word, synapse_index_mask), + synapse_delay_mask, synapse_type_index_bits); + } +#endif // LOG_LEVEL >= LOG_DEBUG +} + +//--------------------------------------- +static inline index_t sparse_axonal_delay(uint32_t x) { +#if 1 + 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 final_state_t eprop_plasticity_update( + update_state_t current_state, REAL delta_w){ + // Convert delta_w to int16_t (same as weight) - take only integer bits from REAL? + int32_t delta_w_int = (int32_t)roundk(delta_w, 15); + // THIS NEEDS UPDATING TO APPROPRIATE SCALING + + if (delta_w){ // TODO: This should probably be delta_w_int + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "delta_w: %k, delta_w_int: %d \n", + delta_w, delta_w_int); + } + + if (delta_w_int < 0){ + current_state = weight_one_term_apply_depression( + current_state, delta_w_int << 3); + } else { + current_state = weight_one_term_apply_potentiation( + current_state, delta_w_int << 3); + } + } + + // Regularisation error isn't used in this case + REAL reg_error = 0.0; + + // Return final synaptic word and weight + return synapse_structure_get_final_state(current_state, reg_error); +} + +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 plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + + num_plastic_pre_synaptic_events += plastic_synapse; + + // Could maybe have a single z_bar for the entire synaptic row + // and update it once here for all synaptic words? + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + uint32_t control_word = *control_words++; + + // Extract control-word components + // **NOTE** cunningly, control word is just the same as lower + // 16-bits of 32-bit fixed synapse so same functions can be used + uint32_t delay = 1; + uint32_t syn_ind_from_delay = synapse_row_sparse_delay( + control_word, synapse_type_index_bits, synapse_delay_mask); + + uint32_t type = synapse_row_sparse_type( + control_word, synapse_index_bits, synapse_type_mask); + uint32_t index = + synapse_row_sparse_index(control_word, synapse_index_mask); + uint32_t type_index = synapse_row_sparse_type_index( + control_word, synapse_type_index_mask); + + int32_t neuron_ind = synapse_row_sparse_index( + control_word, synapse_index_mask); + + // For low pass filter of incoming spike train on this synapse + // Use postsynaptic neuron index to access neuron struct, + + if (type==1){ + // this is a recurrent synapse: add 100 to index to correct array location + syn_ind_from_delay += RECURRENT_SYNAPSE_OFFSET; + } + + neuron_t *neuron = &neuron_array[neuron_ind]; + neuron->syn_state[syn_ind_from_delay].z_bar_inp = 1024.0k; + // !!!! Check what units this is in - same as weight? !!!! + + // Create update state from the plastic synaptic word + update_state_t current_state = + synapse_structure_get_update_state(*plastic_words, type); + + // Perform weight update: only if batch time has elapsed + final_state_t final_state; + + if (neuron->syn_state[syn_ind_from_delay].update_ready <= 0) { + // enough time has elapsed - perform weight update + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready=0\n"); + } + + // Go through typical weight update process to clip to limits + final_state = eprop_plasticity_update(current_state, + neuron->syn_state[syn_ind_from_delay].delta_w); + + // reset delta_w as weight change has now been applied + neuron->syn_state[syn_ind_from_delay].delta_w = 0.0k; + + // reset update_ready counter based on pattern cycle time + neuron->syn_state[syn_ind_from_delay].update_ready += 1024; + + } else { + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "update_ready: %u - no update performed\n", + neuron->syn_state[syn_ind_from_delay].update_ready); + } + // don't update weight - get update state based on state cached in SDRAM + // assume reg rate is zero to avoid + + final_state = synapse_structure_get_final_state(current_state, 0); + // Don't reset delta_w -> keep this accumulating and apply weight change in future + } + + // Add contribution to synaptic input + // Convert into ring buffer offset + uint32_t ring_buffer_index = synapse_row_get_ring_buffer_index_combined( + // delay_axonal + delay_dendritic + + time, type_index, + synapse_type_index_bits, synapse_delay_mask); + + // Check for ring buffer saturation (?) + int16_t accumulation = ring_buffers[ring_buffer_index] + + synapse_structure_get_final_weight(final_state); + + ring_buffers[ring_buffer_index] = accumulation; + + // TODO: no overflow or underflow checking? + + // Write back updated synaptic word to plastic region + *plastic_words++ = + synapse_structure_get_final_synaptic_word(final_state); + } + *write_back = true; + return true; +} + +void synapse_dynamics_process_post_synaptic_event( + uint32_t time, index_t neuron_index) { + log_debug("Adding post-synaptic event to trace at time:%u", time); + + // 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)); +} + +// TODO: fix below to match other dynamics impls so that structural +// plasticity can be used if desired? + +#if SYNGEN_ENABLED == 1 + +//! \brief Searches the synaptic row for the the connection with the +//! specified post-synaptic ID +//! \param[in] id: the (core-local) ID of the neuron to search for in the +//! synaptic row +//! \param[in] row: the core-local address of the synaptic row +//! \param[out] sp_data: the address of a struct through which to return +//! weight, delay information +//! \return bool: was the search successful? +bool find_plastic_neuron_with_id( + uint32_t id, address_t row, structural_plasticity_data_t *sp_data) { + address_t fixed_region = synapse_row_fixed_region(row); + address_t plastic_region_address = synapse_row_plastic_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(plastic_region_address); + control_t *control_words = synapse_row_plastic_controls(fixed_region); + int32_t plastic_synapse = synapse_row_num_plastic_controls(fixed_region); + plastic_synapse_t weight; + uint32_t delay; + + // Loop through plastic synapses + for (; plastic_synapse > 0; plastic_synapse--) { + // Get next control word (auto incrementing) + weight = *plastic_words++; + uint32_t control_word = *control_words++; + + // Check if index is the one I'm looking for + delay = synapse_row_sparse_delay(control_word, synapse_type_index_bits); + if (synapse_row_sparse_index(control_word, synapse_index_mask) == id) { + sp_data->weight = weight; + sp_data->offset = + synapse_row_num_plastic_controls(fixed_region) + - plastic_synapse; + sp_data->delay = delay; + return true; + } + } + + sp_data->weight = -1; + sp_data->offset = -1; + sp_data->delay = -1; + return false; +} + +//! \brief Remove the entry at the specified offset in the synaptic row +//! \param[in] offset: the offset in the row at which to remove the entry +//! \param[in] row: the core-local address of the synaptic row +//! \return bool: was the removal successful? +bool remove_plastic_neuron_at_offset(uint32_t offset, address_t row) { + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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]; + plastic_words[plastic_synapse - 1] = 0; + + // Delete control word at offset + control_words[offset] = control_words[plastic_synapse - 1]; + control_words[plastic_synapse - 1] = 0; + + // Decrement FP + fixed_region[1]--; + + return true; +} + +//! ensuring the weight is of the correct type and size +static inline plastic_synapse_t weight_conversion(uint32_t weight) { + return (plastic_synapse_t) (0xFFFF & weight); +} + +//! packing all of the information into the required plastic control word +static inline control_t control_conversion( + uint32_t id, uint32_t delay, uint32_t type) { + control_t new_control = + (delay & ((1 << SYNAPSE_DELAY_BITS) - 1)) << synapse_type_index_bits; + new_control |= (type & ((1 << synapse_type_index_bits) - 1)) << synapse_index_bits; + new_control |= id & ((1 << synapse_index_bits) - 1); + return new_control; +} + +//! \brief Add a plastic entry in the synaptic row +//! \param[in] id: the (core-local) ID of the post-synaptic neuron to be added +//! \param[in] row: the core-local address of the synaptic row +//! \param[in] weight: the initial weight associated with the connection +//! \param[in] delay: the delay associated with the connection +//! \param[in] type: the type of the connection (e.g. inhibitory) +//! \return bool: was the addition successful? +bool add_plastic_neuron_with_id(uint32_t id, address_t row, + uint32_t weight, uint32_t delay, uint32_t type) { + plastic_synapse_t new_weight = weight_conversion(weight); + control_t new_control = control_conversion(id, delay, type); + + address_t fixed_region = synapse_row_fixed_region(row); + plastic_synapse_t *plastic_words = + plastic_synapses(synapse_row_plastic_region(row)); + 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[1]++; + return true; +} +#endif 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..b6361c91626 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 @@ -104,7 +104,7 @@ static inline nm_final_state_t get_nm_final_state( nm_final_state_t final_state = { .weight=(weight_t) (bitsk(update_state.weight) >> update_state.weight_shift), .final_state=synapse_structure_get_final_state( - update_state.eligibility_state) + update_state.eligibility_state, 0.0k) }; return final_state; } @@ -340,7 +340,7 @@ void synapse_dynamics_print_plastic_synapses( update_state_t update_state = synapse_structure_get_update_state( *plastic_words++, synapse_type); final_state_t final_state = synapse_structure_get_final_state( - update_state); + update_state, 0.0k); weight_t weight = synapse_structure_get_final_weight(final_state); log_debug("%08x [%3d: (w: %5u (=", control_word, i, weight); 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..a30f7043aa6 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 @@ -99,7 +99,7 @@ static inline final_state_t plasticity_update_synapse( } // Return final synaptic word and weight - return synapse_structure_get_final_state(current_state); + return synapse_structure_get_final_state(current_state, 0.0k); } bool synapse_dynamics_initialise( @@ -150,7 +150,7 @@ void synapse_dynamics_print_plastic_synapses( update_state_t update_state = synapse_structure_get_update_state( *plastic_words++, synapse_type); final_state_t final_state = synapse_structure_get_final_state( - update_state); + update_state, 0.0k); weight_t weight = synapse_structure_get_final_weight(final_state); log_debug("%08x [%3d: (w: %5u (=", control_word, i, weight); diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure.h index db5f45aca45..94ae7349823 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure.h @@ -40,7 +40,7 @@ static update_state_t synapse_structure_get_update_state( //! \param[in] state: the update state //! \return the final state static final_state_t synapse_structure_get_final_state( - update_state_t state); + update_state_t state, REAL reg_error); //! \brief Get the final weight from the final state //! \param[in] final_state: the final state diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_accumulator_impl.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_accumulator_impl.h index 175cb04fbca..2945a463da0 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_accumulator_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_accumulator_impl.h @@ -66,9 +66,9 @@ static inline update_state_t synapse_structure_get_update_state( //! \param[in] state: the update state //! \return the final state static inline final_state_t synapse_structure_get_final_state( - update_state_t state) { + update_state_t state, REAL reg_error) { // Get weight from state - weight_t weight = weight_get_final(state.weight_state); + weight_t weight = weight_get_final(state.weight_state, reg_error); // Build this into synaptic word along with updated accumulator and state return (final_state_t) { diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_eprop_impl.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_eprop_impl.h new file mode 100644 index 00000000000..a6350b7fce7 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_eprop_impl.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 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 Synapses just hold weight +#ifndef _SYNAPSE_STRUCUTRE_WEIGHT_EPROP_IMPL_H_ +#define _SYNAPSE_STRUCUTRE_WEIGHT_EPROP_IMPL_H_ + +//--------------------------------------- +// Structures +//--------------------------------------- +//! Plastic synapse types are just weights; +typedef weight_t plastic_synapse_t; + +//! The update state is purely a weight state +typedef weight_state_t update_state_t; + +// The final state is just a weight as this is +//! Both the weight and the synaptic word +typedef weight_t final_state_t; + +#include "synapse_structure.h" + +//--------------------------------------- +// Synapse interface functions +//--------------------------------------- +//! \brief Get the update state from the synapse structure +//! \param[in] synaptic_word: The plastic synapse data +//! \param[in] synapse_type: What (supported) type of synapse is this? +//! \return The update state +static inline update_state_t synapse_structure_get_update_state( + plastic_synapse_t synaptic_word, index_t synapse_type) { + return weight_get_initial(synaptic_word, synapse_type); +} + +//--------------------------------------- +//! \brief Get the final state from the update state. +//! \param[in] state: the update state +//! \return the final state +static inline final_state_t synapse_structure_get_final_state( + update_state_t state, REAL reg_error) { + return weight_get_final(state, reg_error); +} + +//--------------------------------------- +//! \brief Get the final weight from the final state +//! \param[in] final_state: the final state +//! \return the final weight +static inline weight_t synapse_structure_get_final_weight( + final_state_t final_state) { + return final_state; +} + +//--------------------------------------- +//! \brief Get the final plastic synapse data from the final state +//! \param[in] final_state: the final state +//! \return the final plastic synapse data, ready to be stored +static inline plastic_synapse_t synapse_structure_get_final_synaptic_word( + final_state_t final_state) { + return final_state; +} + +//--------------------------------------- +//! \brief Create the initial plastic synapse data +//! \param[in] weight: the initial synaptic weight +//! \return the plastic synapse data +static inline plastic_synapse_t synapse_structure_create_synapse( + weight_t weight) { + return weight; +} + +//--------------------------------------- +//! \brief Get the current synaptic weight from the plastic synapse data +//! \param[in] synaptic_word: the plastic synapse data +//! \return the current synaptic weight +static inline weight_t synapse_structure_get_weight( + plastic_synapse_t synaptic_word) { + return synaptic_word; +} + +static inline void synapse_structure_decay_weight( + update_state_t *state, uint32_t decay) { + return weight_decay(state, decay); +} + +static inline accum synapse_structure_get_update_weight(update_state_t state) { + return weight_get_update(state); +} + +#endif // _SYNAPSE_STRUCUTRE_WEIGHT_EPROP_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_impl.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_impl.h index c2155e2c885..96fe9775314 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_impl.h @@ -51,8 +51,8 @@ static inline update_state_t synapse_structure_get_update_state( //! \param[in] state: the update state //! \return the final state static inline final_state_t synapse_structure_get_final_state( - update_state_t state) { - return weight_get_final(state); + update_state_t state, REAL reg_error) { + return weight_get_final(state, reg_error); } //--------------------------------------- diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_impl.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_impl.h index 621a5e0251e..112decda95f 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_impl.h @@ -66,9 +66,9 @@ static inline update_state_t synapse_structure_get_update_state( //! \param[in] state: the update state //! \return the final state static inline final_state_t synapse_structure_get_final_state( - update_state_t state) { + update_state_t state, REAL reg_error) { // Get weight from state - weight_t weight = weight_get_final(state.weight_state); + weight_t weight = weight_get_final(state.weight_state, reg_error); // Build this into synaptic word along with updated accumulator and state return (final_state_t) { diff --git a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_window_impl.h b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_window_impl.h index a13a76b8f7e..7818b9c774a 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_window_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/synapse_structure/synapse_structure_weight_state_accumulator_window_impl.h @@ -67,9 +67,9 @@ static inline update_state_t synapse_structure_get_update_state( //! \param[in] state: the update state //! \return the final state static inline final_state_t synapse_structure_get_final_state( - update_state_t state) { + update_state_t state, REAL reg_error) { // Get weight from state - weight_t weight = weight_get_final(state.weight_state); + weight_t weight = weight_get_final(state.weight_state, reg_error); // Build this into synaptic word along with updated accumulator and state return (final_state_t) { diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c new file mode 100644 index 00000000000..2fa2ff7abe1 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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 + * + * https://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_eprop_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Exponential lookup-tables +int16_t tau_plus_lookup[TAU_PLUS_SIZE]; +int16_t tau_minus_lookup[TAU_MINUS_SIZE]; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t timing_initialise(address_t address) { +// io_printf(IO_BUF, "timing_initialise: starting\n"); +// io_printf(IO_BUF, "\t Nothing to be done\n"); + + return address; +} diff --git a/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h new file mode 100644 index 00000000000..08866b5032d --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/timing_dependence/timing_eprop_impl.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 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 + * + * https://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_EPROP_IMPL_H_ +#define _TIMING_EPROP_IMPL_H_ + +//--------------------------------------- +// 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 +#define TAU_PLUS_SIZE 256 + +#define TAU_MINUS_TIME_SHIFT 0 +#define TAU_MINUS_SIZE 256 + +// Helper macros for looking up decays +#define DECAY_LOOKUP_TAU_PLUS(time) \ + maths_lut_exponential_decay( \ + time, tau_plus_lookup) +#define DECAY_LOOKUP_TAU_MINUS(time) \ + maths_lut_exponential_decay( \ + time, tau_minus_lookup) + +//--------------------------------------- +// Externals +//--------------------------------------- +extern int16_t tau_plus_lookup[TAU_PLUS_SIZE]; +extern int16_t tau_minus_lookup[TAU_MINUS_SIZE]; + +//--------------------------------------- +// Timing dependence inline functions +//--------------------------------------- +static inline post_trace_t timing_get_initial_post_trace(void) { + return 0; +} + +//--------------------------------------- +static inline post_trace_t timing_add_post_spike( + uint32_t time, uint32_t last_time, post_trace_t last_trace) { + // Get time since last spike + uint32_t delta_time = time - last_time; + + // Decay previous o1 and o2 traces + int32_t decayed_o1_trace = STDP_FIXED_MUL_16X16(last_trace, + DECAY_LOOKUP_TAU_MINUS(delta_time)); + + // Add energy caused by new spike to trace + // **NOTE** o2 trace is pre-multiplied by a3_plus + int32_t new_o1_trace = decayed_o1_trace + STDP_FIXED_POINT_ONE; + + log_debug("\tdelta_time=%d, o1=%d\n", delta_time, new_o1_trace); + + // 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) { + // Get time since last spike + uint32_t delta_time = time - last_time; + + // Decay previous r1 and r2 traces + int32_t decayed_r1_trace = STDP_FIXED_MUL_16X16( + last_trace, DECAY_LOOKUP_TAU_PLUS(delta_time)); + + // Add energy caused by new spike to trace + int32_t new_r1_trace = decayed_r1_trace + STDP_FIXED_POINT_ONE; + + log_debug("\tdelta_time=%u, r1=%d\n", delta_time, new_r1_trace); + + // Return new pre-synaptic event with decayed trace values with energy + // for new spike added + return (pre_trace_t) 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(&trace); + use(last_pre_time); + use(&last_pre_trace); + + // Get time of event relative to last post-synaptic event + uint32_t time_since_last_post = time - last_post_time; + if (time_since_last_post > 0) { + int32_t decayed_o1 = STDP_FIXED_MUL_16X16( + last_post_trace, DECAY_LOOKUP_TAU_MINUS(time_since_last_post)); + + log_debug("\t\t\ttime_since_last_post_event=%u, decayed_o1=%d\n", + time_since_last_post, decayed_o1); + + // Apply depression to state (which is a weight_state) + return weight_one_term_apply_depression(previous_state, decayed_o1); + } else { + return previous_state; + } +} + +//--------------------------------------- +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(&trace); + use(last_post_time); + use(&last_post_trace); + + // Get time of event relative to last pre-synaptic event + uint32_t time_since_last_pre = time - last_pre_time; + if (time_since_last_pre > 0) { + int32_t decayed_r1 = STDP_FIXED_MUL_16X16( + last_pre_trace, DECAY_LOOKUP_TAU_PLUS(time_since_last_pre)); + + log_debug("\t\t\ttime_since_last_pre_event=%u, decayed_r1=%d\n", + time_since_last_pre, decayed_r1); + + // Apply potentiation to state (which is a weight_state) + return weight_one_term_apply_potentiation(previous_state, decayed_r1); + } else { + return previous_state; + } +} + +#endif // _TIMING_EPROP_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight.h index e999f3337a2..fd443380c3c 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight.h +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight.h @@ -61,9 +61,10 @@ static weight_state_t weight_get_initial(weight_t weight, index_t synapse_type); /*! * \brief Gets the final weight. * \param[in] new_state: The updated weight state + * \param[in] reg_error: The regularisation error * \return The new weight. */ -static weight_t weight_get_final(weight_state_t new_state); +static weight_t weight_get_final(weight_state_t new_state, REAL reg_error); /*! * \brief Decay the weight inside the state by multiplication 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..ef3426615b3 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 @@ -102,7 +102,7 @@ static inline weight_state_t weight_one_term_apply_potentiation( * \param[in] state: The updated weight state * \return The new weight. */ -static inline weight_t weight_get_final(weight_state_t state) { +static inline weight_t weight_get_final(weight_state_t state, REAL reg_error) { return (weight_t) (bitsk(state.weight) >> state.weight_shift); } 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..ac1474b022f 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 @@ -108,7 +108,7 @@ static inline weight_state_t weight_two_term_apply_potentiation( * \param[in] state: The updated weight state * \return The new weight. */ -static inline weight_t weight_get_final(weight_state_t state) { +static inline weight_t weight_get_final(weight_state_t state, REAL reg_error) { return (weight_t) (bitsk(state.weight) >> state.weight_shift); } diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c new file mode 100644 index 00000000000..38c13aa4cf4 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 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 + * + * https://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_eprop_reg_impl.h" + +//--------------------------------------- +// Globals +//--------------------------------------- +// Global plasticity parameter data +plasticity_weight_region_data_t *plasticity_weight_region_data; + +uint32_t *weight_shift; + +//! \brief How the configuration data for additive_one_term is laid out in +//! SDRAM. The layout is an array of these. +typedef struct { + accum min_weight; + accum max_weight; + accum a2_plus; + accum a2_minus; + accum reg_rate; +} eprop_one_term_config_t; + +//--------------------------------------- +// Functions +//--------------------------------------- +address_t weight_initialise( + address_t address, uint32_t n_synapse_types, + UNUSED uint32_t *ring_buffer_to_input_buffer_left_shifts) { + // Copy plasticity region data from address + // **NOTE** this seems somewhat safer than relying on sizeof + eprop_one_term_config_t *config = (eprop_one_term_config_t *) address; + + 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; + } + + for (uint32_t s = 0; s < n_synapse_types; s++, config++) { + 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; + dtcm_copy[s].reg_rate = config->reg_rate; + + // Copy weight shift + weight_shift[s] = ring_buffer_to_input_buffer_left_shifts[s]; + + log_debug("\tSynapse type %u: Min weight:%k, Max weight:%k, A2+:%k, A2-:%k reg_rate:%k", + s, dtcm_copy[s].min_weight, dtcm_copy[s].max_weight, + dtcm_copy[s].a2_plus, dtcm_copy[s].a2_minus, + dtcm_copy[s].reg_rate); + } + + // Return end address of region + return (address_t) config; +} + +////--------------------------------------- +//// Functions +////--------------------------------------- +//address_t weight_initialise( +// address_t address, uint32_t n_synapse_types, +// uint32_t *ring_buffer_to_input_buffer_left_shifts) { +// use(ring_buffer_to_input_buffer_left_shifts); +// +// io_printf(IO_BUF, "weight_initialise: starting\n"); +// io_printf(IO_BUF, "\teprop_reg weight dependence\n"); +// +// // Copy plasticity region data from address +// // **NOTE** this seems somewhat safer than relying on sizeof +// int32_t *plasticity_word = (int32_t *) address; +// plasticity_weight_region_data = +// spin1_malloc(sizeof(plasticity_weight_region_data_t) * n_synapse_types); +// if (plasticity_weight_region_data == NULL) { +// io_printf(IO_BUF, "Could not initialise weight region data\n"); +// return NULL; +// } +// for (uint32_t s = 0; s < n_synapse_types; s++) { +// plasticity_weight_region_data[s].min_weight = *plasticity_word++; +// plasticity_weight_region_data[s].max_weight = *plasticity_word++; +// plasticity_weight_region_data[s].reg_rate = kbits(*plasticity_word++); +// +//// plasticity_weight_region_data[s].a2_plus = *plasticity_word++; +//// plasticity_weight_region_data[s].a2_minus = *plasticity_word++; +// +// io_printf(IO_BUF, "\tSynapse type %u: Min weight:%d, Max weight:%d, reg_rate: %k \n" +//// "A2+:%d, A2-:%d" +// , +// s, plasticity_weight_region_data[s].min_weight, +// plasticity_weight_region_data[s].max_weight, +// plasticity_weight_region_data[s].reg_rate +//// plasticity_weight_region_data[s].a2_plus, +//// plasticity_weight_region_data[s].a2_minus +// ); +// } +// io_printf(IO_BUF, "weight_initialise: completed successfully\n"); +// +// // Return end address of region +// return (address_t) plasticity_word; +//} diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h new file mode 100644 index 00000000000..0a274926b75 --- /dev/null +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_eprop_reg_impl.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 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 + * + * https://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_EPROPREG_ONE_TERM_IMPL_H_ +#define _WEIGHT_EPROPREG_ONE_TERM_IMPL_H_ + +// Include generic plasticity maths functions +#include +#include +#include + +#include + +//--------------------------------------- +// Structures +//--------------------------------------- +typedef struct { + accum min_weight; + accum max_weight; + accum a2_plus; + accum a2_minus; + + REAL reg_rate; +} 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" + +//--------------------------------------- +// Externals +//--------------------------------------- +//extern plasticity_weight_region_data_t *plasticity_weight_region_data; + +//--------------------------------------- +// STDP weight dependence 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 a2_minus) { + + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "depressing: %d\n", a2_minus); + } + state.weight -= kbits(a2_minus); + state.weight = kbits(MAX(bitsk(state.weight), bitsk(state.weight_region->min_weight))); + return state; +} + +//--------------------------------------- +static inline weight_state_t weight_one_term_apply_potentiation( + weight_state_t state, int32_t a2_plus) { + + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "potentiating: %d\n", a2_plus); + } + state.weight += kbits(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, + REAL reg_error) { + // Apply eprop plasticity updates to initial weight + accum new_weight = new_state.weight; + accum reg_weight = new_weight; + accum reg_change = 0.0k; + REAL reg_boundary = 1.0k; + + // Calculate regularisation + if (new_state.weight_region->reg_rate > 0.0k && ( + reg_error > reg_boundary || reg_error < -reg_boundary)) { + // if reg rate is zero or error small, regularisation is turned off + reg_change = new_state.weight_region->max_weight * ( + new_state.weight_region->reg_rate * reg_error); + reg_weight = new_weight + reg_change; + } + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "\tbefore minmax reg_w:%d, reg_shift:%d, max:%d", + reg_weight, reg_change, new_state.weight_region->max_weight); + } + + if (PRINT_PLASTICITY){ + io_printf(IO_BUF, "\told_weight:%d, a2+:%d, a2-:%d, " + " new_weight:%d, reg_weight:%d, reg_l_rate:%k, reg_error:%k\n", + new_state.weight, new_state.weight_region->a2_plus, + new_state.weight_region->a2_minus, new_weight, reg_weight, + new_state.weight_region->reg_rate, reg_error); + } + + return (weight_t) (bitsk(reg_weight) >> new_state.weight_shift); +} + +#endif // _WEIGHT_EPROPREG_ONE_TERM_IMPL_H_ diff --git a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_multiplicative_impl.h b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_multiplicative_impl.h index 6d147aa37be..65bae5c1370 100644 --- a/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_multiplicative_impl.h +++ b/neural_modelling/src/neuron/plasticity/stdp/weight_dependence/weight_multiplicative_impl.h @@ -109,7 +109,7 @@ static inline weight_state_t weight_one_term_apply_potentiation( * \param[in] state: The updated weight state * \return The new weight. */ -static inline weight_t weight_get_final(weight_state_t state) { +static inline weight_t weight_get_final(weight_state_t state, REAL reg_error) { return (weight_t) (bitsk(state.weight) >> state.weight_shift); } diff --git a/neural_modelling/src/neuron/spike_processing.c b/neural_modelling/src/neuron/spike_processing.c index c32b61f6323..a6b0e32daa5 100644 --- a/neural_modelling/src/neuron/spike_processing.c +++ b/neural_modelling/src/neuron/spike_processing.c @@ -117,6 +117,8 @@ static struct { //! the region to record the packets per timestep in static uint32_t p_per_ts_region; +REAL learning_signal; + /* PRIVATE FUNCTIONS - static for inlining */ //! \brief Perform a DMA read of a synaptic row @@ -308,18 +310,18 @@ static void multicast_packet_received_callback(uint key, UNUSED uint unused) { //! \brief Called when a multicast packet is received //! \param[in] key: The key of the packet. The spike. //! \param[in] payload: the payload of the packet. The count. -static void multicast_packet_pl_received_callback(uint key, uint payload) { - p_per_ts_struct.packets_this_time_step += 1; - - // cycle through the packet insertion - bool added = false; - for (uint count = payload; count > 0; count--) { - added = in_spikes_add_spike(key); - } - if (added) { - start_dma_loop(); - } -} +//static void multicast_packet_pl_received_callback(uint key, uint payload) { +// p_per_ts_struct.packets_this_time_step += 1; +// +// // cycle through the packet insertion +// bool added = false; +// for (uint count = payload; count > 0; count--) { +// added = in_spikes_add_spike(key); +// } +// if (added) { +// start_dma_loop(); +// } +//} //! \brief Called when a DMA completes //! \param unused: unused @@ -329,6 +331,9 @@ static void dma_complete_callback(UNUSED uint unused, UNUSED uint tag) { // increment the dma complete count for provenance generation dma_complete_count++; +// io_printf(IO_BUF, "Entering DMA Complete...\n"); +// log_info("Entering DMA Complete...\n"); + // Get pointer to current buffer uint32_t current_buffer_index = buffer_being_read; dma_buffer *current_buffer = &dma_buffers[current_buffer_index]; @@ -409,6 +414,34 @@ void user_event_callback(UNUSED uint unused0, UNUSED uint unused1) { } } +static void multicast_packet_wpayload_received_callback(uint key, uint payload){ + + learning_signal = kbits(payload); + + // Print payload to test transmission of error +// io_printf(IO_BUF, " payload (learning signal): %k\n", learning_signal); + + // Assign learning signal to global memory + +// // If there was space to add spike to incoming spike queue +// if (in_spikes_add_spike(key)) { +// // If we're not already processing synaptic DMAs, +// // flag pipeline as busy and trigger a feed event +// if (!dma_busy) { +// log_debug("Sending user event for new spike"); +// if (spin1_trigger_user_event(0, 0)) { +// dma_busy = true; +// } else { +// log_debug("Could not trigger user event\n"); +// } +// } +// } else { +// io_printf(IO_BUF, "Could not add spike in mc_payload_received\n"); +// } + +} + + /* INTERFACE FUNCTIONS - cannot be static */ void spike_processing_clear_input_buffer(timer_t time) { uint32_t n_spikes = in_spikes_size(); @@ -455,12 +488,22 @@ bool spike_processing_initialise( // EXPORTED // Set up the callbacks spin1_callback_on(MC_PACKET_RECEIVED, multicast_packet_received_callback, mc_packet_callback_priority); - spin1_callback_on(MCPL_PACKET_RECEIVED, - multicast_packet_pl_received_callback, mc_packet_callback_priority); +// spin1_callback_on(MCPL_PACKET_RECEIVED, +// multicast_packet_pl_received_callback, mc_packet_callback_priority); simulation_dma_transfer_done_callback_on( DMA_TAG_READ_SYNAPTIC_ROW, dma_complete_callback); spin1_callback_on(USER_EVENT, user_event_callback, user_event_priority); + +// io_printf(IO_BUF, "About to register MCPL callback\n"); +// +// // This is unlikely to work properly now because of the use of MCPL for multiple spikes +// // Register MC_PACKET_RECEIVED_PAYLOAD + spin1_callback_on(MCPL_PACKET_RECEIVED, + multicast_packet_wpayload_received_callback, mc_packet_callback_priority); +// +// io_printf(IO_BUF, "Registered MCPL callback successfully\n"); + return true; } diff --git a/neural_modelling/src/neuron/synapse_row.h b/neural_modelling/src/neuron/synapse_row.h index a8ad535744a..dc0c2b358ea 100644 --- a/neural_modelling/src/neuron/synapse_row.h +++ b/neural_modelling/src/neuron/synapse_row.h @@ -95,10 +95,24 @@ #define SYNAPSE_WEIGHT_BITS 16 #endif +//! how many bits the synapse delay will take +//#ifndef SYNAPSE_DELAY_BITS +//#define SYNAPSE_DELAY_BITS 8 +//#endif + +//// Create some masks based on the number of bits +////! the mask for the synapse delay in the row +//#define SYNAPSE_DELAY_MASK ((1 << SYNAPSE_DELAY_BITS) - 1) + +//! should this go somewhere else, perhaps in the models that use it? +#define SYNAPSE_WEIGHTS_SIGNED true + +// Define the type of the weights #ifdef SYNAPSE_WEIGHTS_SIGNED //! Define the type of the weights typedef __int_t(SYNAPSE_WEIGHT_BITS) weight_t; #else +//io_printf(IO_BUF, "Using signed weights!! \n "); //! Define the type of the weights typedef __uint_t(SYNAPSE_WEIGHT_BITS) weight_t; #endif diff --git a/neural_modelling/src/neuron/synapse_types/synapse_types_eprop_adaptive_impl.h b/neural_modelling/src/neuron/synapse_types/synapse_types_eprop_adaptive_impl.h new file mode 100644 index 00000000000..0588f045116 --- /dev/null +++ b/neural_modelling/src/neuron/synapse_types/synapse_types_eprop_adaptive_impl.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 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 + * + * https://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 _SYNAPSE_TYPES_EPROP_ADPATIVE_IMPL_H_ +#define _SYNAPSE_TYPES_EPROP_ADAPTIVE_IMPL_H_ + +//--------------------------------------- +// Macros +//--------------------------------------- +#define SYNAPSE_TYPE_BITS 2 +#define SYNAPSE_TYPE_COUNT 4 + +#define NUM_EXCITATORY_RECEPTORS 2 +#define NUM_INHIBITORY_RECEPTORS 2 + +#include +#include +#include "synapse_types.h" + + +//--------------------------------------- +// Synapse parameters +//--------------------------------------- + +struct synapse_types_params_t { + input_t exc; + input_t exc2; + input_t inh; + input_t inh2; +}; + +struct synapse_types_t { + input_t exc; //!< First excitatory synaptic input + input_t exc2; //!< Second excitatory synaptic input + input_t inh; //!< First inhibitory synaptic input + input_t inh2; //!< Second inhibitory synaptic input +}; + +//! human readable definition for the positions in the input regions for the +//! different synapse types. +// TODO: these have different names on the python side... +typedef enum input_buffer_regions { + EXCITATORY_ONE, EXCITATORY_TWO, INHIBITORY_ONE, INHIBITORY_TWO +} input_buffer_regions; + +//--------------------------------------- +// Synapse shaping inline implementation +//--------------------------------------- +static inline void synapse_types_initialise(synapse_types_t *state, + synapse_types_params_t *params, UNUSED uint32_t n_steps_per_timestep) { + state->exc = params->exc; + state->exc2 = params->exc2; + state->inh = params->inh; + state->inh2 = params->inh2; +} + +static inline void synapse_types_save_state(synapse_types_t *state, + synapse_types_params_t *params) { + params->exc = state->exc; + params->exc2 = state->exc2; + params->inh = state->inh; + params->inh2 = state->inh2; +} + +//! \brief decays the stuff thats sitting in the input buffers +//! (to compensate for the valve behaviour of a synapse +//! in biology (spike goes in, synapse opens, then closes slowly) plus the +//! leaky aspect of a neuron). as these have not yet been processed and applied +//! to the neuron. +//! \param[in] parameter: the pointer to the parameters to use +//! \return nothing +static inline void synapse_types_shape_input( + synapse_types_t *parameter) { + parameter->exc = 0; + parameter->exc2 = 0; + parameter->inh = 0; + parameter->inh2 = 0; +} + +//! \brief adds the inputs for a give timer period to a given neuron that is +//! being simulated by this model +//! \param[in] synapse_type_index the type of input that this input is to be +//! considered (aka excitatory or inhibitory etc) +//! \param[in] parameter: the pointer to the parameters to use +//! \param[in] input the inputs for that given synapse_type. +//! \return None +static inline void synapse_types_add_neuron_input( + index_t synapse_type_index, synapse_types_t *parameter, + input_t input) { + if (synapse_type_index == EXCITATORY_ONE) { + parameter->exc += input; + } else if (synapse_type_index == EXCITATORY_TWO) { + parameter->exc2 += input; + } else if (synapse_type_index == INHIBITORY_ONE) { + parameter->inh += input; + } else if (synapse_type_index == INHIBITORY_TWO) { + parameter->inh2 += input; + } +} + +//! \brief extracts the excitatory input buffers from the buffers available +//! for a given parameter set +//! \param[in] parameter: the pointer to the parameters to use +//! \return the excitatory input buffers for a given neuron ID. +static inline input_t* synapse_types_get_excitatory_input( + input_t *excitatory_response, synapse_types_t *parameter) { + excitatory_response[0] = parameter->exc; + excitatory_response[1] = parameter->exc2; + return &excitatory_response[0]; +} + +//! \brief extracts the inhibitory input buffers from the buffers available +//! for a given parameter set +//! \param[in] parameter: the pointer to the parameters to use +//! \return the inhibitory input buffers for a given neuron ID. +static inline input_t* synapse_types_get_inhibitory_input( + input_t *inhibitory_response, synapse_types_t *parameter) { + inhibitory_response[0] = parameter->inh; + inhibitory_response[1] = parameter->inh2; + return &inhibitory_response[0]; +} + +//! \brief returns a human readable character for the type of synapse. +//! examples would be X = excitatory types, I = inhibitory types etc etc. +//! \param[in] synapse_type_index the synapse type index +//! (there is a specific index interpretation in each synapse type) +//! \return a human readable character representing the synapse type. +static inline const char *synapse_types_get_type_char( + index_t synapse_type_index) { + if (synapse_type_index == EXCITATORY_ONE) { + return "X1"; + } else if (synapse_type_index == EXCITATORY_TWO) { + return "X2"; + } else if (synapse_type_index == INHIBITORY_ONE) { + return "I1"; + } else if (synapse_type_index == INHIBITORY_TWO) { + return "I2"; + } else { + log_debug("did not recognise synapse type %i", synapse_type_index); + return "?"; + } +} + +//! \brief prints the input for a neuron ID given the available inputs +//! currently only executed when the models are in debug mode, as the prints are +//! controlled from the synapses.c _print_inputs method. +//! \param[in] parameter: the pointer to the parameters to use +//! \return Nothing +static inline void synapse_types_print_input( + synapse_types_t *parameter) { + log_debug("%12.6k + %12.6k - %12.6k - %12.6k", + parameter->exc, parameter->exc2, + parameter->inh, parameter->inh2); +} + +//! \brief printer call +//! \param[in] parameter: the pointer to the parameters to print +static inline void synapse_types_print_parameters( + synapse_types_t *parameter) { + log_debug("exc_init = %11.4k\n", parameter->exc); + log_debug("exc2_init = %11.4k\n", parameter->exc2); + log_debug("inh_init = %11.4k\n", parameter->inh); + log_debug("inh2_init = %11.4k\n", parameter->inh2); +} + +#endif // _SYNAPSE_TYPES_ERBP_IMPL_H_ diff --git a/neural_modelling/src/neuron/synapses.c b/neural_modelling/src/neuron/synapses.c index 1b7e141505e..87718f4195b 100644 --- a/neural_modelling/src/neuron/synapses.c +++ b/neural_modelling/src/neuron/synapses.c @@ -24,6 +24,7 @@ #include #include #include +//#include "models/neuron_model_eprop_adaptive_impl.h" //! if using profiler import profiler tags #ifdef PROFILER_ENABLED @@ -32,6 +33,7 @@ //! Globals required for synapse benchmarking to work. uint32_t num_fixed_pre_synaptic_events = 0; +//extern neuron_t *neuron_array; //! The number of neurons static uint32_t n_neurons; @@ -232,20 +234,22 @@ static inline bool process_fixed_synapses( // The addition of the masked time to the delay even with the mask might // overflow into the weight at worst but can't affect the lower bits. uint32_t ring_buffer_index = (synaptic_word + masked_time) & ring_buffer_mask; - uint32_t weight = synapse_row_sparse_weight(synaptic_word); + int32_t weight = synapse_row_sparse_weight(synaptic_word); // Add weight to current ring buffer value - uint32_t accumulation = ring_buffers[ring_buffer_index] + weight; + int32_t accumulation = ring_buffers[ring_buffer_index] + weight; + // TODO: switch to saturated arithmetic to avoid complicated saturation check, + // will it check saturation at both ends? // If 17th bit is set, saturate accumulator at UINT16_MAX (0xFFFF) // **NOTE** 0x10000 can be expressed as an ARM literal, // but 0xFFFF cannot. Therefore, we use (0x10000 - 1) // to obtain this value - uint32_t sat_test = accumulation & 0x10000; - if (sat_test) { - accumulation = sat_test - 1; - synapses_saturation_count++; - } +// uint32_t sat_test = accumulation & 0x10000; +// if (sat_test) { +// accumulation = sat_test - 1; +// synapses_saturation_count++; +// } // Store saturated value back in ring-buffer ring_buffers[ring_buffer_index] = accumulation; @@ -306,14 +310,16 @@ bool synapses_initialise( synapse_index_mask = (1 << synapse_index_bits) - 1; synapse_type_bits = log_n_synapse_types; synapse_type_mask = (1 << log_n_synapse_types) - 1; - synapse_delay_bits = log_max_delay; + synapse_delay_bits = log_max_delay; // 8; 3; synapse_delay_mask = (1 << synapse_delay_bits) - 1; synapse_delay_mask_shifted = synapse_delay_mask << synapse_type_index_bits; n_neurons_peak = 1 << log_n_neurons; uint32_t n_ring_buffer_bits = - log_n_neurons + log_n_synapse_types + synapse_delay_bits; + log_n_neurons + log_n_synapse_types + synapse_delay_bits; // synapse_delay_bits = 1? +// uint32_t n_ring_buffer_bits = +// log_n_neurons + log_n_synapse_types + 1; // synapse_delay_bits = 1? ring_buffer_size = 1 << (n_ring_buffer_bits); ring_buffer_mask = ring_buffer_size - 1; diff --git a/neural_modelling/src/neuron/synapses.h b/neural_modelling/src/neuron/synapses.h index f98fdda6534..e35d463e66f 100644 --- a/neural_modelling/src/neuron/synapses.h +++ b/neural_modelling/src/neuron/synapses.h @@ -47,7 +47,6 @@ extern uint32_t synapse_type_mask; extern uint32_t synapse_delay_bits; //! Mask to pick out the delay extern uint32_t synapse_delay_mask; - //! Count of the number of times the synapses have saturated their weights. extern uint32_t synapses_saturation_count; diff --git a/neural_modelling/src/neuron/threshold_types/threshold_type_adaptive.h b/neural_modelling/src/neuron/threshold_types/threshold_type_adaptive.h new file mode 100644 index 00000000000..6da49c3542a --- /dev/null +++ b/neural_modelling/src/neuron/threshold_types/threshold_type_adaptive.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 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 + * + * https://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 _THRESHOLD_TYPE_ADAPTIVE_H_ +#define _THRESHOLD_TYPE_ADAPTIVE_H_ + +#include "threshold_type.h" +#include +#include "debug.h" + +typedef struct threshold_type_t { + + REAL B; // Capital B(t) + REAL b; // b(t) + REAL b_0; // small b^0 + decay_t e_to_dt_on_tau_a; // rho + REAL beta; + decay_t adpt; // (1-rho) + REAL scalar; + +} threshold_type_t; + +static REAL var_scal = 1; + +static void _print_threshold_params(threshold_type_pointer_t threshold_type){ + io_printf(IO_BUF, "B: %k, " + "b: %k, " + "b_0: %k, " + "e_to_dt_on_tau_a: %u, " + "beta: %k, " + "adpt: %u, \n" + "scalar: %k, \n\n", + threshold_type->B, + threshold_type->b, + threshold_type->b_0, + threshold_type->e_to_dt_on_tau_a, + threshold_type->beta, + threshold_type->adpt, + threshold_type->scalar + ); +} + + +static inline bool threshold_type_is_above_threshold(state_t value, + threshold_type_pointer_t threshold_type) { + + // Not used + use(value); + use(threshold_type); + + return false; +} + +static inline void threshold_type_update_threshold(state_t z, + threshold_type_pointer_t threshold_type){ + +// _print_threshold_params(threshold_type); + + + s1615 temp1 = decay_s1615(threshold_type->b, threshold_type->e_to_dt_on_tau_a); + s1615 temp2 = decay_s1615(threshold_type->scalar, threshold_type->adpt) * z; + + threshold_type->b = temp1 + + temp2; + + +// // Evolve threshold dynamics (decay to baseline) and adapt if z=nonzero +// // Update small b (same regardless of spike - uses z from previous timestep) +// threshold_type->b = +// decay_s1615(threshold_type->b, threshold_type->e_to_dt_on_tau_a) +// + decay_s1615(1000k, threshold_type->adpt) // fold scaling into decay to increase precision +// * z; // stored on neuron +// + // Update large B + threshold_type->B = threshold_type->b_0 + + threshold_type->beta*threshold_type->b; + +} + + +#endif // _THRESHOLD_TYPE_ADAPTIVE_H_ 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..501bb2f63c0 100644 --- a/neural_modelling/src/spike_source/poisson/spike_source_poisson.c +++ b/neural_modelling/src/spike_source/poisson/spike_source_poisson.c @@ -364,8 +364,8 @@ void set_spike_source_rate(uint32_t sub_id, unsigned long accum rate) { REAL rate_per_tick = kbits( (__U64(bitsk(rate)) * __U64(bitsulr(ssp_params.seconds_per_tick))) >> 32); - log_debug("Setting rate of %u to %kHz (%k per tick)", - sub_id, rate, rate_per_tick); + log_debug("At time %u setting rate of %u to %k Hz (%k per tick)", + time, sub_id, (accum) rate, rate_per_tick); spike_source_t *spike_source = &source[sub_id]; if (rate_per_tick >= ssp_params.slow_rate_per_tick_cutoff) { @@ -417,7 +417,8 @@ static inline void set_spike_source_details(uint32_t id, bool rate_changed) { log_debug("Source %u is at index %u", id, index); source_details details = source_data[id]->details[index]; if (rate_changed) { - log_debug("Setting rate of %u to %k at %u", id, (s1615) details.rate, time); + log_debug("At time %u setting rate of %u to %k", + time, id, (s1615) details.rate); set_spike_source_rate(id, details.rate); } spike_source_t *p = &(source[id]); @@ -881,7 +882,10 @@ static void process_fast_source(index_t s_id, spike_source_t *source) { if (ssp_params.has_key) { // Send spikes const uint32_t spike_key = keys[s_id] | colour; - send_spike_mc_payload(spike_key, num_spikes); + for (uint32_t n_spike = 0; n_spike < num_spikes; n_spike++) { + send_spike_mc(spike_key); + } +// send_spike_mc_payload(spike_key, num_spikes); } else if (sdram_inputs->address != 0) { input_this_timestep[sdram_inputs->offset + s_id] += sdram_inputs->weights[s_id] * num_spikes; @@ -918,7 +922,10 @@ static void process_slow_source(index_t s_id, spike_source_t *source) { if (ssp_params.has_key) { // Send package const uint32_t spike_key = keys[s_id] | colour; - send_spike_mc_payload(spike_key, count); + for (uint32_t n_spike = 0; n_spike < count; n_spike++) { + send_spike_mc(spike_key); + } +// send_spike_mc_payload(spike_key, count); } else if (sdram_inputs->address != 0) { input_this_timestep[sdram_inputs->offset + s_id] += sdram_inputs->weights[s_id] * count; @@ -1028,7 +1035,13 @@ static void timer_callback(UNUSED uint timer_count, UNUSED uint unused) { //! \param[in] key: Received multicast key //! \param[in] payload: Received multicast payload static void multicast_packet_callback(uint key, uint payload) { + +// log_info("multicast packet callback SSP key %u payload %u time %u", +// key, payload, time); + uint32_t id = key & ssp_params.set_rate_neuron_id_mask; + +// log_info("id value is %u mask %u", id, ssp_params.set_rate_neuron_id_mask); if ((id < ssp_params.first_source_id) || (id - ssp_params.first_source_id >= ssp_params.n_spike_sources)) { return; diff --git a/spynnaker/pyNN/extra_models/__init__.py b/spynnaker/pyNN/extra_models/__init__.py index 30c98e6a959..d1950bc8a67 100644 --- a/spynnaker/pyNN/extra_models/__init__.py +++ b/spynnaker/pyNN/extra_models/__init__.py @@ -20,7 +20,9 @@ from spynnaker.pyNN.models.neuron.synapse_dynamics import ( SynapseDynamicsNeuromodulation as Neuromodulation) from spynnaker.pyNN.models.neuron.plasticity.stdp.weight_dependence import ( - WeightDependenceAdditiveTriplet) + WeightDependenceAdditiveTriplet, WeightDependenceEpropReg) +from spynnaker.pyNN.models.neuron.plasticity.stdp.timing_dependence import ( + TimingDependenceEprop) from spynnaker.pyNN.models.neuron.builds import ( IFCondExpStoc, IFCurrDelta as IFCurDelta, @@ -29,6 +31,9 @@ IzkCondExpBase as Izhikevich_cond, IzkCondDualExpBase as Izhikevich_cond_dual, IFCurrExpSEMDBase as IF_curr_exp_sEMD, + EPropAdaptive as EPropAdaptive, + SinusoidReadout as SinusoidReadout, + LeftRightReadout as LeftRightReadout, IFCurrDeltaCa2Adaptive) # Variable rate poisson @@ -40,6 +45,9 @@ 'Izhikevich_cond', 'IF_curr_dual_exp', 'IF_curr_exp_sEMD', 'Izhikevich_cond_dual', 'IFCurrDeltaCa2Adaptive', + # Eprop implementation and related models (Adam Perrett/Oliver Rhodes) + 'EPropAdaptive', 'SinusoidReadout', 'LeftRightReadout', + # Neuromodulation synapse dynamics (Mantas Mikaitis) 'Neuromodulation', @@ -48,6 +56,9 @@ 'PfisterSpikeTriplet', 'SpikeNearestPairRule', 'RecurrentRule', 'Vogels2011Rule', + # eprop plastic stuff + 'TimingDependenceEprop', + 'WeightDependenceEpropReg', # Variable rate Poisson 'SpikeSourcePoissonVariable'] diff --git a/spynnaker/pyNN/models/neural_projections/connectors/abstract_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/abstract_connector.py index e83a1503346..5e29f59bb8d 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/abstract_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/abstract_connector.py @@ -141,6 +141,8 @@ def _get_delay_maximum(self, delays, n_connections, synapse_info): return numpy.max(_expr_context.eval(delays, d=d)) elif numpy.isscalar(delays): return delays + elif hasattr(delays, "__getitem__"): + return numpy.max(delays) raise SpynnakerException(f"Unrecognised delay format: {type(delays)}") @abstractmethod @@ -178,6 +180,8 @@ def get_delay_variance(self, delays, synapse_info): return numpy.var(_expr_context.eval(delays, d=d)) elif numpy.isscalar(delays): return 0.0 + elif hasattr(delays, "__getitem__"): + return numpy.var(delays) raise SpynnakerException("Unrecognised delay format") def _get_n_connections_from_pre_vertex_with_delay_maximum( @@ -270,6 +274,8 @@ def get_weight_mean(self, weights, synapse_info): return numpy.mean(_expr_context.eval(weights, d=d)) elif numpy.isscalar(weights): return abs(weights) + elif hasattr(weights, "__getitem__"): + return numpy.mean(weights) raise SpynnakerException("Unrecognised weight format") def _get_weight_maximum(self, weights, n_connections, synapse_info): @@ -302,6 +308,8 @@ def _get_weight_maximum(self, weights, n_connections, synapse_info): return numpy.max(_expr_context.eval(weights, d=d)) elif numpy.isscalar(weights): return abs(weights) + elif hasattr(weights, "__getitem__"): + return numpy.amax(numpy.abs(weights)) raise SpynnakerException("Unrecognised weight format") @abstractmethod @@ -328,6 +336,8 @@ def get_weight_variance(self, weights, synapse_info): return numpy.var(_expr_context.eval(weights, d=d)) elif numpy.isscalar(weights): return 0.0 + elif hasattr(weights, "__getitem__"): + return numpy.var(weights) raise SpynnakerException("Unrecognised weight format") def _expand_distances(self, d_expression): @@ -427,6 +437,8 @@ def _generate_values( return values(d) elif numpy.isscalar(values): return numpy.repeat([values], n_connections).astype("float64") + elif hasattr(values, "__getitem__"): + return numpy.array(values).astype("float64") raise SpynnakerException(f"Unrecognised values {values}") def _generate_weights( @@ -445,12 +457,15 @@ def _generate_weights( if self.__safe: if not weights.size: warn_once(logger, "No connection in " + str(self)) - elif numpy.amin(weights) < 0 < numpy.amax(weights): - raise SpynnakerException( - "Weights must be either all positive or all negative in " - f"projection {synapse_info.pre_population.label}->" - f"{synapse_info.post_population.label}") - return numpy.abs(weights) + # TODO: I think this is allowed for local_only so investigate + # what's going on there + # elif numpy.amin(weights) < 0 < numpy.amax(weights): + # raise SpynnakerException( + # "Weights must be either all positive or all negative in " + # f"projection {synapse_info.pre_population.label}->" + # f"{synapse_info.post_population.label}") + # return numpy.abs(weights) + return weights # numpy.abs(weights) def _clip_delays(self, delays): """ diff --git a/spynnaker/pyNN/models/neuron/abstract_population_vertex.py b/spynnaker/pyNN/models/neuron/abstract_population_vertex.py index 9ada2fa3567..d601bf4b782 100644 --- a/spynnaker/pyNN/models/neuron/abstract_population_vertex.py +++ b/spynnaker/pyNN/models/neuron/abstract_population_vertex.py @@ -67,6 +67,8 @@ MAX_PARAMS_BYTES, is_param_generatable) from spynnaker.pyNN.exceptions import SpynnakerException from spynnaker.pyNN.models.spike_source import SpikeSourcePoissonVertex +# from spynnaker.pyNN.models.neuron.neuron_models import ( +# NeuronModelLeftRightReadout) from .population_machine_neurons import PopulationMachineNeurons from .synapse_io import get_max_row_info from .master_pop_table import MasterPopTableAsBinarySearch @@ -957,6 +959,10 @@ def __str__(self): def __repr__(self): return self.__str__() + @property + def _pynn_model(self): + return self.__pynn_model + @overrides(AbstractCanReset.reset_to_first_timestep) def reset_to_first_timestep(self): # Reset state variables @@ -1083,6 +1089,14 @@ def get_ring_buffer_shifts(self): w + 1 if (2 ** w) <= a else w for w, a in zip(max_weight_powers, max_weights)) + # TODO: check this EPROP (from synaptic_manager) + # fix weight shift so we can scale eligibility trace calculations + # accordingly. + max_weight_powers = (2 # if w >= 1 else w + for w in max_weight_powers) + + # Not entirely sure why but eprop also had signed weights stuff... + return list(max_weight_powers) @staticmethod diff --git a/spynnaker/pyNN/models/neuron/builds/__init__.py b/spynnaker/pyNN/models/neuron/builds/__init__.py index 86411db3d1c..71bbf94dc5a 100644 --- a/spynnaker/pyNN/models/neuron/builds/__init__.py +++ b/spynnaker/pyNN/models/neuron/builds/__init__.py @@ -27,6 +27,9 @@ from .if_curr_delta import IFCurrDelta from .if_curr_exp_ca2_adaptive import IFCurrExpCa2Adaptive from .if_curr_exp_semd_base import IFCurrExpSEMDBase +from .eprop_adaptive import EPropAdaptive +from .sinusoid_readout import SinusoidReadout +from .left_right_readout import LeftRightReadout from .if_curr_delta_ca2_adaptive import IFCurrDeltaCa2Adaptive __all__ = ["EIFConductanceAlphaPopulation", "HHCondExp", "IFCondAlpha", @@ -34,4 +37,5 @@ "IFCurrExpBase", "IFFacetsConductancePopulation", "IzkCondExpBase", "IzkCurrExpBase", "IFCondExpStoc", "IzkCondDualExpBase", "IFCurrDelta", "IFCurrExpCa2Adaptive", "IFCurrExpSEMDBase", + "EPropAdaptive", "SinusoidReadout", "LeftRightReadout", "IFCurrDeltaCa2Adaptive"] diff --git a/spynnaker/pyNN/models/neuron/builds/eprop_adaptive.py b/spynnaker/pyNN/models/neuron/builds/eprop_adaptive.py new file mode 100644 index 00000000000..e6fe33acaa8 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/builds/eprop_adaptive.py @@ -0,0 +1,82 @@ +# Copyright (c) 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 +# +# https://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 spynnaker.pyNN.models.neuron import AbstractPyNNNeuronModelStandard +from spynnaker.pyNN.models.defaults import default_initial_values +from spynnaker.pyNN.models.neuron.neuron_models import ( + NeuronModelEPropAdaptive) +from spynnaker.pyNN.models.neuron.synapse_types import ( + SynapseTypeEPropAdaptive) +from spynnaker.pyNN.models.neuron.input_types import InputTypeCurrent +from spynnaker.pyNN.models.neuron.threshold_types import ThresholdTypeNone + + +class EPropAdaptive(AbstractPyNNNeuronModelStandard): + """ Adaptive threshold neuron with eprop support + """ + + @default_initial_values({"v", "isyn_exc", "isyn_exc2", + "isyn_inh", "isyn_inh2", "psi", "target_rate", + "tau_err", "B", "small_b", "learning_signal", + "w_fb", "window_size", "number_of_cues", "eta"}) + def __init__( + self, + # neuron model params + tau_m=20.0, cm=1.0, v_rest=0, v_reset=0, + tau_refrac=5.0, i_offset=0.0, v=0.0, psi=0.0, + + # synapse type params + # tau_syn_E=5.0, tau_syn_E2=5.0, tau_syn_I=5.0, tau_syn_I2=5.0, + isyn_exc=0.0, isyn_exc2=0.0, isyn_inh=0.0, isyn_inh2=0.0, + + # Regularisation params + target_rate=10.0, tau_err=1000.0, # fits with 1 ms timestep + + # Threshold parameters + B=10.0, small_b=0.0, small_b_0=10.0, tau_a=500.0, beta=1.8, + + # Learning signal and weight update constants + learning_signal=0.0, w_fb=0.5, window_size=13000, + number_of_cues=0, + + # eprop "global" + eta=1.0 + ): + # pylint: disable=too-many-arguments, too-many-locals + neuron_model = NeuronModelEPropAdaptive( + v, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, psi, + # threshold params + B, small_b, small_b_0, tau_a, beta, + # Regularisation params + target_rate, tau_err, + # Learning signal params + learning_signal, w_fb, window_size, number_of_cues, + # eprop global + eta) + + synapse_type = SynapseTypeEPropAdaptive( + isyn_exc, isyn_exc2, isyn_inh, isyn_inh2) + + input_type = InputTypeCurrent() + + threshold_type = ThresholdTypeNone() + + super(EPropAdaptive, self).__init__( + model_name="eprop_adaptive", binary="eprop_adaptive.aplx", + neuron_model=neuron_model, input_type=input_type, + synapse_type=synapse_type, threshold_type=threshold_type) + + @classmethod + def get_max_atoms_per_core(cls): + return 8 diff --git a/spynnaker/pyNN/models/neuron/builds/left_right_readout.py b/spynnaker/pyNN/models/neuron/builds/left_right_readout.py new file mode 100644 index 00000000000..418c0f63739 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/builds/left_right_readout.py @@ -0,0 +1,59 @@ +# Copyright (c) 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 +# +# https://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 spynnaker.pyNN.models.neuron import AbstractPyNNNeuronModelStandard +from spynnaker.pyNN.models.defaults import default_initial_values +from spynnaker.pyNN.models.neuron.neuron_models import ( + NeuronModelLeftRightReadout) +from spynnaker.pyNN.models.neuron.synapse_types import ( + SynapseTypeEPropAdaptive) +from spynnaker.pyNN.models.neuron.input_types import InputTypeCurrent +from spynnaker.pyNN.models.neuron.threshold_types import ThresholdTypeStatic + + +class LeftRightReadout(AbstractPyNNNeuronModelStandard): + """ + """ + + @default_initial_values({"v", "isyn_exc", "isyn_exc2", "isyn_inh", + "isyn_inh2", "learning_signal", "w_fb", "eta", + "number_of_cues"}) + def __init__( + self, tau_m=20.0, cm=1.0, v_rest=0.0, v_reset=0.0, + v_thresh=100, tau_refrac=0.1, i_offset=0.0, v=50, + isyn_exc=0.0, isyn_exc2=0.0, isyn_inh=0.0, isyn_inh2=0.0, + rate_on=40, rate_off=0, poisson_pop_size=10, + # Learning signal and weight update constants + learning_signal=0, w_fb=0.5, eta=1.0, window_size=13000, + number_of_cues=1): + + # pylint: disable=too-many-arguments, too-many-locals + neuron_model = NeuronModelLeftRightReadout( + v, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, + # Learning signal params + rate_on, rate_off, poisson_pop_size, learning_signal, w_fb, eta, + window_size, number_of_cues) + + synapse_type = SynapseTypeEPropAdaptive( + isyn_exc, isyn_exc2, isyn_inh, isyn_inh2) + + input_type = InputTypeCurrent() + + threshold_type = ThresholdTypeStatic(v_thresh) + + super(LeftRightReadout, self).__init__( + model_name="left_right_readout", + binary="left_right_readout.aplx", + neuron_model=neuron_model, input_type=input_type, + synapse_type=synapse_type, threshold_type=threshold_type) diff --git a/spynnaker/pyNN/models/neuron/builds/sinusoid_readout.py b/spynnaker/pyNN/models/neuron/builds/sinusoid_readout.py new file mode 100644 index 00000000000..63ed6c7b9d8 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/builds/sinusoid_readout.py @@ -0,0 +1,57 @@ +# Copyright (c) 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 +# +# https://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 spynnaker.pyNN.models.neuron import AbstractPyNNNeuronModelStandard +from spynnaker.pyNN.models.defaults import default_initial_values +from spynnaker.pyNN.models.neuron.neuron_models import ( + NeuronModelLeakyIntegrateAndFireSinusoidReadout) +from spynnaker.pyNN.models.neuron.synapse_types import ( + SynapseTypeEPropAdaptive) +from spynnaker.pyNN.models.neuron.input_types import InputTypeCurrent +from spynnaker.pyNN.models.neuron.threshold_types import ThresholdTypeStatic + + +class SinusoidReadout(AbstractPyNNNeuronModelStandard): + """ + """ + + @default_initial_values({"v", "isyn_exc", "isyn_exc2", "isyn_inh", + "isyn_inh2", "target_data", "learning_signal", + "w_fb", "eta", "update_ready"}) + def __init__( + self, tau_m=20.0, cm=1.0, v_rest=0.0, v_reset=0.0, + v_thresh=100, tau_refrac=0.1, i_offset=0.0, v=50, + isyn_exc=0.0, isyn_exc2=0.0, isyn_inh=0.0, isyn_inh2=0.0, + target_data=0, + # Learning signal and weight update constants + learning_signal=0, w_fb=0.5, eta=1.0, update_ready=1024): + + # pylint: disable=too-many-arguments, too-many-locals + neuron_model = NeuronModelLeakyIntegrateAndFireSinusoidReadout( + v, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, target_data, + # Learning signal params + learning_signal, w_fb, eta, update_ready) + + synapse_type = SynapseTypeEPropAdaptive( + isyn_exc, isyn_exc2, isyn_inh, isyn_inh2) + + input_type = InputTypeCurrent() + + threshold_type = ThresholdTypeStatic(v_thresh) + + super(SinusoidReadout, self).__init__( + model_name="sinusoid_readout", + binary="sinusoid_readout.aplx", + neuron_model=neuron_model, input_type=input_type, + synapse_type=synapse_type, threshold_type=threshold_type) diff --git a/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py b/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py index f5376318111..92dc4fc1f50 100644 --- a/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py +++ b/spynnaker/pyNN/models/neuron/implementations/abstract_standard_neuron_component.py @@ -79,3 +79,13 @@ def get_units(self, variable): :param str variable: The name of the variable """ return self.__units[variable] + + @property + def uses_eprop(self): + """ + Says whether the component is an e-prop model (default false) + + :rtype: bool + + """ + return False diff --git a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py index d534c158c8f..45fe6ddefbf 100644 --- a/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py +++ b/spynnaker/pyNN/models/neuron/implementations/neuron_impl_standard.py @@ -87,6 +87,10 @@ def __init__( if self.__additional_input_type is not None: self.__components.append(self.__additional_input_type) + @property + def neuron_model(self): + return self.__neuron_model + @property def n_steps_per_timestep(self): return self.__n_steps_per_timestep diff --git a/spynnaker/pyNN/models/neuron/neuron_models/__init__.py b/spynnaker/pyNN/models/neuron/neuron_models/__init__.py index 3da5f9c9665..e5c6112f5da 100644 --- a/spynnaker/pyNN/models/neuron/neuron_models/__init__.py +++ b/spynnaker/pyNN/models/neuron/neuron_models/__init__.py @@ -15,6 +15,12 @@ from .neuron_model_izh import NeuronModelIzh from .neuron_model_leaky_integrate_and_fire import ( NeuronModelLeakyIntegrateAndFire) +from .neuron_model_eprop_adaptive import NeuronModelEPropAdaptive +from .neuron_model_sinusoid_readout import ( + NeuronModelLeakyIntegrateAndFireSinusoidReadout) +from .neuron_model_left_right_readout import NeuronModelLeftRightReadout -__all__ = ["NeuronModelIzh", - "NeuronModelLeakyIntegrateAndFire"] +__all__ = ["NeuronModelIzh", "NeuronModelLeakyIntegrateAndFire", + "NeuronModelEPropAdaptive", + "NeuronModelLeakyIntegrateAndFireSinusoidReadout", + "NeuronModelLeftRightReadout"] diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_eprop_adaptive.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_eprop_adaptive.py new file mode 100644 index 00000000000..270351a3738 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_eprop_adaptive.py @@ -0,0 +1,362 @@ +# Copyright (c) 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 +# +# https://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 + +# constants +SYNAPSES_PER_NEURON = 250 + +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" + +# eprop +PSI = "psi" +Z = "z" +A = "a" + +# Threshold +BIG_B = "big_b" +SMALL_B = "small_b" +SMALL_B_0 = "small_b_0" +TAU_A = "tau_a" +BETA = "beta" +SCALAR = "scalar" + +# Learning signal +L = "learning_signal" +W_FB = "feedback_weight" +WINDOW_SIZE = "window_size" +NUMBER_OF_CUES = "number_of_cues" + +# eprop "global" +CORE_POP_RATE = "core_pop_rate" +TARGET_RATE = "target_rate" +TAU_ERR = "tau_err" +ETA = "eta" # (global learning rate) + +# eprop synapse +DELTA_W = "delta_w" +Z_BAR_OLD = "z_bar_old" +Z_BAR = "z_bar" +EP_A = "ep_a" +E_BAR = "e_bar" +UPDATE_READY = "update_ready" + + +class NeuronModelEPropAdaptive(AbstractStandardNeuronComponent): + __slots__ = [ + "__v_init", + "__v_rest", + "__tau_m", + "__cm", + "__i_offset", + "__v_reset", + "__tau_refrac", + "__z", + "__a", + "__psi", + # threshold params + "__B", + "__small_b", + "__small_b_0", + "__tau_a", + "__beta", + "__scalar", + # reg params + "__target_rate", + "__tau_err", + # learning signal + "__learning_signal", + "__w_fb", + "__window_size", + "__number_of_cues", + # eprop "global" + "__core_pop_rate", + "__core_target_rate", + "__rate_exp_TC", + "__eta" + ] + + def __init__( + self, v_init, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, + psi, + # threshold params + B, small_b, small_b_0, tau_a, beta, + # regularisation params + target_rate, tau_err, learning_signal, w_fb, window_size, + number_of_cues, + # eprop "global" + eta + ): + # TODO: documentation of parameters + + struct_neuron_vals = [ + # neuron params + (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, REFRACT_TIMER), + (DataType.S1615, TIMESTEP), + (DataType.S1615, Z), + (DataType.S1615, A), + (DataType.S1615, PSI), # psi, pseuo_derivative + (DataType.S1615, BIG_B), + (DataType.S1615, SMALL_B), + (DataType.S1615, SMALL_B_0), + (DataType.UINT32, TAU_A), + (DataType.S1615, BETA), + (DataType.S1615, SCALAR), + (DataType.S1615, L), + (DataType.S1615, W_FB), + (DataType.UINT32, WINDOW_SIZE), + (DataType.UINT32, NUMBER_OF_CUES), + (DataType.S1615, CORE_POP_RATE), # core_pop_rate + (DataType.S1615, TARGET_RATE), # core_target_rate + (DataType.S1615, TAU_ERR), # rate_exp_TC + (DataType.S1615, ETA)] # eta (learning rate) + + for n in range(SYNAPSES_PER_NEURON): + struct_neuron_vals.extend( + # eprop syn state + [(DataType.S1615, DELTA_W+str(n)), # delta_w + (DataType.S1615, Z_BAR_OLD+str(n)), # z_bar_old + (DataType.S1615, Z_BAR+str(n)), # z_bar + (DataType.S1615, EP_A+str(n)), # ep_a + (DataType.S1615, E_BAR+str(n)), # e_bar + (DataType.INT32, UPDATE_READY+str(n))]) + + super().__init__( + [Struct( + struct_neuron_vals)], + {V: 'mV', V_REST: 'mV', TAU_M: 'ms', CM: 'nF', I_OFFSET: 'nA', + V_RESET: 'mV', TAU_REFRAC: 'ms', Z: '', A: '', PSI: '', + BIG_B: 'mV', SMALL_B: 'mV', SMALL_B_0: 'mV', TAU_A: '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 + self.__psi = psi + + # threshold params + self.__B = B + self.__small_b = small_b + self.__small_b_0 = small_b_0 + self.__tau_a = tau_a + self.__beta = beta + self.__scalar = 1000 + + # Regularisation params + self.__target_rate = target_rate + self.__tau_err = tau_err + + # learning signal + self.__learning_signal = learning_signal + self.__w_fb = w_fb + self.__window_size = window_size + self.__number_of_cues = number_of_cues + + # eprop "global" + self.__eta = eta + + @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() + + parameters[SMALL_B_0] = self.__small_b_0 + parameters[TAU_A] = self.__tau_a + parameters[BETA] = self.__beta + parameters[SCALAR] = self.__scalar + parameters[W_FB] = self.__w_fb + parameters[WINDOW_SIZE] = self.__window_size + parameters[NUMBER_OF_CUES] = self.__number_of_cues + + # Are these parameters or variables? + parameters[CORE_POP_RATE] = 0.0 # initialise here, not in C + parameters[TARGET_RATE] = self.__target_rate + parameters[TAU_ERR] = self.__tau_err + parameters[ETA] = self.__eta + + @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[PSI] = self.__psi + state_variables[Z] = 0 # initialise to zero + state_variables[A] = 0 # initialise to zero + + state_variables[BIG_B] = self.__B + state_variables[SMALL_B] = self.__small_b + + state_variables[L] = self.__learning_signal + + for n in range(SYNAPSES_PER_NEURON): + state_variables[DELTA_W+str(n)] = 0 + state_variables[Z_BAR_OLD+str(n)] = 0 + state_variables[Z_BAR+str(n)] = 0 + state_variables[EP_A+str(n)] = 0 + state_variables[E_BAR+str(n)] = 0 + state_variables[UPDATE_READY+str(n)] = self.__window_size + + @property + def v_init(self): + return self.__v_init + + @v_init.setter + def v_init(self, v_init): + self.__v_init = v_init + + @property + def v_rest(self): + return self.__v_rest + + @v_rest.setter + def v_rest(self, v_rest): + self.__v_rest = v_rest + + @property + def tau_m(self): + return self.__tau_m + + @tau_m.setter + def tau_m(self, tau_m): + self.__tau_m = tau_m + + @property + def cm(self): + return self.__cm + + @cm.setter + def cm(self, cm): + self.__cm = cm + + @property + def i_offset(self): + return self.__i_offset + + @i_offset.setter + def i_offset(self, i_offset): + self.__i_offset = i_offset + + @property + def v_reset(self): + return self.__v_reset + + @v_reset.setter + def v_reset(self, v_reset): + self.__v_reset = v_reset + + @property + def tau_refrac(self): + return self.__tau_refrac + + @tau_refrac.setter + def tau_refrac(self, tau_refrac): + self.__tau_refrac = tau_refrac + + @property + def B(self): + return self.__B + + @B.setter + def B(self, new_value): + self.__B = new_value + + @property + def small_b(self): + return self.__small_b + + @small_b.setter + def small_b(self, new_value): + self.__small_b = new_value + + @property + def small_b_0(self): + return self.__small_b_0 + + @small_b_0.setter + def small_b_0(self, new_value): + self.__small_b_0 = new_value + + @property + def tau_a(self): + return self.__tau_a + + @tau_a.setter + def tau_a(self, new_value): + self.__tau_a = new_value + + @property + def beta(self): + return self.__beta + + @beta.setter + def beta(self, new_value): + self.__beta = new_value + + @property + def w_fb(self): + return self.__w_fb + + @w_fb.setter + def w_fb(self, new_value): + self.__w_fb = new_value + + @property + def window_size(self): + return self.__window_size + + @window_size.setter + def window_size(self, new_value): + self.__window_size = new_value + + @property + def number_of_cues(self): + return self.__number_of_cues + + @number_of_cues.setter + def number_of_cues(self, new_value): + self.__number_of_cues = new_value + + @property + @overrides(AbstractStandardNeuronComponent.uses_eprop) + def uses_eprop(self): + return True diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_left_right_readout.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_left_right_readout.py new file mode 100644 index 00000000000..d024b70dd1d --- /dev/null +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_left_right_readout.py @@ -0,0 +1,304 @@ +# Copyright (c) 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 +# +# https://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 + +# constants +SYNAPSES_PER_NEURON = 250 # around 415 with only 3 in syn_state (?) + +V = "v" +V_REST = "v_rest" +TAU_M = "tau_m" +CM = "cm" +I_OFFSET = "i_offset" +V_RESET = "v_reset" +TAU_REFRAC = "tau_refrac" +# COUNT_REFRAC = "count_refrac" +TIMESTEP = "timestep" +REFRACT_TIMER = "refract_timer" + +# Learning signal +L = "learning_signal" +W_FB = "feedback_weight" +WINDOW_SIZE = "window_size" + +SEED1 = "seed1" +SEED2 = "seed2" +SEED3 = "seed3" +SEED4 = "seed4" +TICKS_PER_SECOND = "ticks_per_second" +TIME_SINCE_LAST_SPIKE = "time_since_last_spike" +RATE_AT_LAST_SETTING = "rate_at_last_setting" +MEAN_L = "mean_l" +MEAN_R = "mean_r" +RATE_ON = "rate_on" +RATE_OFF = "rate_off" +POISSON_POP_SIZE = 'poisson_pop_size' +POISSON_KEY = 'poisson_key' +CROSS_ENTROPY = 'cross_entropy' +ETA = 'eta' +NUMBER_OF_CUES = 'number_of_cues' + +DELTA_W = "delta_w" +Z_BAR_OLD = "z_bar_old" +Z_BAR = "z_bar" +UPDATE_READY = "update_ready" + + +class NeuronModelLeftRightReadout(AbstractStandardNeuronComponent): + __slots__ = [ + "__v_init", + "__v_rest", + "__tau_m", + "__cm", + "__i_offset", + "__v_reset", + "__tau_refrac", + "__rate_off", + "__rate_on", + "__learning_signal", + "__w_fb", + "__window_size", + "__eta", + "__mean_l", + "__mean_r", + "__cross_entropy", + "__poisson_key", + "__poisson_pop_size", + "__number_of_cues" + ] + + def __init__( + self, v_init, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, + rate_on, rate_off, poisson_pop_size, learning_signal, w_fb, eta, + window_size, number_of_cues): + + struct_neuron_vals = [ + (DataType.S1615, V), # v + (DataType.S1615, V_REST), # v_rest + (DataType.S1615, CM), # r_membrane (= tau_m / cm) + (DataType.S1615, TAU_M), # exp_tc (= e^(-ts / tau_m)) + (DataType.S1615, I_OFFSET), # i_offset + (DataType.S1615, V_RESET), # v_reset + (DataType.S1615, TAU_REFRAC), # tau_refrac + (DataType.INT32, REFRACT_TIMER), # count_refrac + (DataType.S1615, TIMESTEP), # timestep + # Learning signal + (DataType.S1615, L), # Learning_signal + (DataType.S1615, W_FB), # w_fb + (DataType.UINT32, WINDOW_SIZE), # window_size + # former global parameters + (DataType.UINT32, SEED1), + (DataType.UINT32, SEED2), + (DataType.UINT32, SEED3), + (DataType.UINT32, SEED4), + (DataType.S1615, TICKS_PER_SECOND), + (DataType.S1615, TIME_SINCE_LAST_SPIKE), + (DataType.S1615, RATE_AT_LAST_SETTING), + (DataType.S1615, RATE_ON), + (DataType.S1615, RATE_OFF), + (DataType.S1615, MEAN_L), + (DataType.S1615, MEAN_R), + (DataType.S1615, CROSS_ENTROPY), + (DataType.UINT32, POISSON_KEY), + (DataType.UINT32, POISSON_POP_SIZE), + (DataType.S1615, ETA), + (DataType.UINT32, NUMBER_OF_CUES) + ] + + # Synapse states - always initialise to zero + for n in range(SYNAPSES_PER_NEURON): + struct_neuron_vals.extend( + [(DataType.S1615, DELTA_W+str(n)), + (DataType.S1615, Z_BAR_OLD+str(n)), + (DataType.S1615, Z_BAR+str(n)), + (DataType.UINT32, UPDATE_READY+str(n))]) + + super().__init__( + [Struct(struct_neuron_vals)], + {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 + self.__rate_off = rate_off + self.__rate_on = rate_on + self.__mean_l = 0.0 + self.__mean_r = 0.0 + self.__cross_entropy = 0.0 + self.__poisson_key = 0 # None TODO: work out how to pass this in + self.__poisson_pop_size = poisson_pop_size + self.__learning_signal = learning_signal + self.__w_fb = w_fb + self.__eta = eta + self.__window_size = window_size + self.__number_of_cues = number_of_cues + + def set_poisson_key(self, p_key): + self.__poisson_key = p_key + + @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() + + parameters[L] = self.__learning_signal + parameters[W_FB] = self.__w_fb + parameters[WINDOW_SIZE] = self.__window_size + # These should probably have defaults earlier than this + # TODO: some confusion as to which values were actually being used? + parameters[SEED1] = 1 # 10065 + parameters[SEED2] = 2 # 232 + parameters[SEED3] = 3 # 3634 + parameters[SEED4] = 4 # 4877 + + parameters[RATE_ON] = self.__rate_on + parameters[RATE_OFF] = self.__rate_off + + parameters[TICKS_PER_SECOND] = 0.0 + parameters[TIME_SINCE_LAST_SPIKE] = 0.0 + parameters[RATE_AT_LAST_SETTING] = 0.0 + parameters[POISSON_POP_SIZE] = self.__poisson_pop_size + parameters[MEAN_L] = self.__mean_l + parameters[MEAN_R] = self.__mean_r + parameters[CROSS_ENTROPY] = self.__cross_entropy + parameters[POISSON_KEY] = self.__poisson_key + parameters[POISSON_POP_SIZE] = self.__poisson_pop_size + parameters[ETA] = self.__eta + parameters[NUMBER_OF_CUES] = self.__number_of_cues + + @overrides(AbstractStandardNeuronComponent.add_state_variables) + def add_state_variables(self, state_variables): + state_variables[V] = self.__v_init + state_variables[REFRACT_TIMER] = 0 + + # learning params + state_variables[L] = self.__learning_signal + + for n in range(SYNAPSES_PER_NEURON): + state_variables[DELTA_W+str(n)] = 0 + state_variables[Z_BAR_OLD+str(n)] = 0 + state_variables[Z_BAR+str(n)] = 0 + state_variables[UPDATE_READY+str(n)] = self.__window_size + + @property + def rate_on(self): + return self.__rate_on + + @rate_on.setter + def rate_on(self, rate_on): + self.__rate_on = rate_on + + @property + def rate_off(self): + return self.__rate_off + + @rate_on.setter + def rate_on(self, rate_off): + self.__rate_off = rate_off + + @property + def v_init(self): + return self.__v_init + + @v_init.setter + def v_init(self, v_init): + self.__v_init = v_init + + @property + def v_rest(self): + return self.__v_rest + + @v_rest.setter + def v_rest(self, v_rest): + self.__v_rest = v_rest + + @property + def tau_m(self): + return self.__tau_m + + @tau_m.setter + def tau_m(self, tau_m): + self.__tau_m = tau_m + + @property + def cm(self): + return self.__cm + + @cm.setter + def cm(self, cm): + self.__cm = cm + + @property + def i_offset(self): + return self.__i_offset + + @i_offset.setter + def i_offset(self, i_offset): + self.__i_offset = i_offset + + @property + def v_reset(self): + return self.__v_reset + + @v_reset.setter + def v_reset(self, v_reset): + self.__v_reset = v_reset + + @property + def tau_refrac(self): + return self.__tau_refrac + + @tau_refrac.setter + def tau_refrac(self, tau_refrac): + self.__tau_refrac = tau_refrac + + @property + def w_fb(self): + return self.__w_fb + + @w_fb.setter + def w_fb(self, new_value): + self.__w_fb = new_value + + @property + def window_size(self): + return self.__window_size + + @window_size.setter + def window_size(self, new_value): + self.__window_size = new_value + + @property + @overrides(AbstractStandardNeuronComponent.uses_eprop) + def uses_eprop(self): + return True diff --git a/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_sinusoid_readout.py b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_sinusoid_readout.py new file mode 100644 index 00000000000..c5e8cbd6e59 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/neuron_models/neuron_model_sinusoid_readout.py @@ -0,0 +1,233 @@ +# Copyright (c) 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 +# +# https://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 + +# constants +SYNAPSES_PER_NEURON = 250 # around 415 with only 3 in syn_state + +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" +TARGET_DATA = "target_data" +# Learning signal +L = "learning_signal" +W_FB = "feedback_weight" +ETA = "eta" + +# eprop synapse +DELTA_W = "delta_w" +Z_BAR_OLD = "z_bar_old" +Z_BAR = "z_bar" +UPDATE_READY = "update_ready" + + +class NeuronModelLeakyIntegrateAndFireSinusoidReadout( + AbstractStandardNeuronComponent): + __slots__ = [ + "__v_init", + "__v_rest", + "__tau_m", + "__cm", + "__i_offset", + "__v_reset", + "__tau_refrac", + "__target_data", + + # learning signal + "__learning_signal", + "__w_fb", + "__eta", + "__update_ready" + ] + + def __init__( + self, v_init, v_rest, tau_m, cm, i_offset, v_reset, tau_refrac, + target_data, learning_signal, w_fb, eta, update_ready): + + struct_neuron_vals = [ + (DataType.S1615, V), # v + (DataType.S1615, V_REST), # v_rest + (DataType.S1615, CM), # r_membrane (= tau_m / cm) + (DataType.S1615, TAU_M), # exp_tc (= e^(-ts / tau_m)) + (DataType.S1615, I_OFFSET), # i_offset + (DataType.S1615, V_RESET), # v_reset + (DataType.S1615, TAU_REFRAC), # tau_refrac + (DataType.INT32, REFRACT_TIMER), # count_refrac + (DataType.S1615, TIMESTEP), # timestep + # Learning signal + (DataType.S1615, L), # L + (DataType.S1615, W_FB) # w_fb + ] + + # former global parameters + for n in range(1024): + struct_neuron_vals.extend( + [(DataType.S1615, TARGET_DATA+str(n))]) + struct_neuron_vals.extend([(DataType.S1615, ETA)]) + + # Synapse states - always initialise to zero + for n in range(SYNAPSES_PER_NEURON): + struct_neuron_vals.extend( + # eprop_syn_state + [(DataType.S1615, DELTA_W+str(n)), # delta_w + (DataType.S1615, Z_BAR_OLD+str(n)), # z_bar_old + (DataType.S1615, Z_BAR+str(n)), # z_bar + (DataType.UINT32, UPDATE_READY+str(n))]) # update_ready + + super().__init__( + [Struct(struct_neuron_vals)], + {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 + self.__target_data = target_data + + # learning signal + self.__learning_signal = learning_signal + self.__w_fb = w_fb + + self.__eta = eta + + self.__update_ready = update_ready + + @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() + + # learning params + parameters[W_FB] = self.__w_fb + + # Target data (formerly global data) + for n in range(1024): + parameters[TARGET_DATA+str(n)] = self.__target_data[n] + + parameters[ETA] = self.__eta + + @overrides(AbstractStandardNeuronComponent.add_state_variables) + def add_state_variables(self, state_variables): + state_variables[V] = self.__v_init + state_variables[REFRACT_TIMER] = 0 + + # learning params + state_variables[L] = self.__learning_signal + + for n in range(SYNAPSES_PER_NEURON): + state_variables[DELTA_W+str(n)] = 0 + state_variables[Z_BAR_OLD+str(n)] = 0 + state_variables[Z_BAR+str(n)] = 0 + state_variables[UPDATE_READY+str(n)] = self.__update_ready + + @property + def target_data(self): + return self.__target_data + + @target_data.setter + def target_data(self, target_data): + self.__target_data = target_data + + @property + def v_init(self): + return self.__v_init + + @v_init.setter + def v_init(self, v_init): + self.__v_init = v_init + + @property + def v_rest(self): + return self.__v_rest + + @v_rest.setter + def v_rest(self, v_rest): + self.__v_rest = v_rest + + @property + def tau_m(self): + return self.__tau_m + + @tau_m.setter + def tau_m(self, tau_m): + self.__tau_m = tau_m + + @property + def cm(self): + return self.__cm + + @cm.setter + def cm(self, cm): + self.__cm = cm + + @property + def i_offset(self): + return self.__i_offset + + @i_offset.setter + def i_offset(self, i_offset): + self.__i_offset = i_offset + + @property + def v_reset(self): + return self.__v_reset + + @v_reset.setter + def v_reset(self, v_reset): + self.__v_reset = v_reset + + @property + def tau_refrac(self): + return self.__tau_refrac + + @tau_refrac.setter + def tau_refrac(self, tau_refrac): + self.__tau_refrac = tau_refrac + + @property + def w_fb(self): + return self.__w_fb + + @w_fb.setter + def w_fb(self, w_fb): + self.__w_fb = w_fb + + @property + @overrides(AbstractStandardNeuronComponent.uses_eprop) + def uses_eprop(self): + return True 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..a665c24dade 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,11 @@ from .timing_dependence_spike_nearest_pair import ( TimingDependenceSpikeNearestPair) from .timing_dependence_vogels_2011 import TimingDependenceVogels2011 +from .timing_dependence_eprop import TimingDependenceEprop __all__ = [ "AbstractTimingDependence", "TimingDependenceSpikePair", "TimingDependencePfisterSpikeTriplet", "TimingDependenceRecurrent", - "TimingDependenceSpikeNearestPair", "TimingDependenceVogels2011" + "TimingDependenceSpikeNearestPair", "TimingDependenceVogels2011", + "TimingDependenceEprop" ] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_eprop.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_eprop.py new file mode 100644 index 00000000000..8c57674e571 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/timing_dependence/timing_dependence_eprop.py @@ -0,0 +1,87 @@ +# Copyright (c) 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 +# +# https://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 logging +from spinn_utilities.overrides import overrides +from .abstract_timing_dependence import AbstractTimingDependence +from spynnaker.pyNN.models.neuron.plasticity.stdp.synapse_structure import ( + SynapseStructureWeightOnly) + +logger = logging.getLogger(__name__) + + +class TimingDependenceEprop(AbstractTimingDependence): + __slots__ = [ + "__synapse_structure", + "__a_plus", + "__a_minus"] + + def __init__(self, A_plus=0.01, A_minus=0.01): + + self.__a_plus = A_plus + self.__a_minus = A_minus + + self.__synapse_structure = SynapseStructureWeightOnly() + + @property + def A_plus(self): + return self.__a_plus + + @A_plus.setter + def A_plus(self, new_value): + self.__a_plus = new_value + + @property + def A_minus(self): + return self.__a_minus + + @A_minus.setter + def A_minus(self, new_value): + self.__a_minus = new_value + + @overrides(AbstractTimingDependence.is_same_as) + def is_same_as(self, timing_dependence): + if not isinstance(timing_dependence, TimingDependenceEprop): + return False + return True + + @property + def vertex_executable_suffix(self): + return "eprop" + + @property + def pre_trace_n_bytes(self): + return 2 + + @overrides(AbstractTimingDependence.get_parameters_sdram_usage_in_bytes) + def get_parameters_sdram_usage_in_bytes(self): + return 0 + + @property + def n_weight_terms(self): + return 1 + + @overrides(AbstractTimingDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales): + # There are currently no parameters to write for this rule + pass + + @property + def synaptic_structure(self): + return self.__synapse_structure + + @overrides(AbstractTimingDependence.get_parameter_names) + def get_parameter_names(self): + return [] 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..1dff0693f4a 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,8 @@ from .weight_dependence_additive import WeightDependenceAdditive from .weight_dependence_multiplicative import WeightDependenceMultiplicative from .weight_dependence_additive_triplet import WeightDependenceAdditiveTriplet +from .weight_dependence_eprop_reg import WeightDependenceEpropReg __all__ = ["AbstractHasAPlusAMinus", "AbstractWeightDependence", "WeightDependenceAdditive", "WeightDependenceMultiplicative", - "WeightDependenceAdditiveTriplet"] + "WeightDependenceAdditiveTriplet", "WeightDependenceEpropReg"] diff --git a/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_eprop_reg.py b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_eprop_reg.py new file mode 100644 index 00000000000..e77e23943c1 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/plasticity/stdp/weight_dependence/weight_dependence_eprop_reg.py @@ -0,0 +1,103 @@ +# Copyright (c) 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 +# +# https://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 WeightDependenceEpropReg( + AbstractHasAPlusAMinus, AbstractWeightDependence): + __slots__ = [ + "__w_max", + "__w_min", + "__reg_rate"] + + def __init__(self, w_min=0.0, w_max=1.0, reg_rate=0.0): + super().__init__() + self.__w_min = w_min + self.__w_max = w_max + self.__reg_rate = reg_rate + + @property + def w_min(self): + return self.__w_min + + @property + def w_max(self): + return self.__w_max + + @property + def reg_rate(self): + return self.__reg_rate + + @overrides(AbstractWeightDependence.is_same_as) + def is_same_as(self, weight_dependence): + # pylint: disable=protected-access + if not isinstance(weight_dependence, WeightDependenceEpropReg): + 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 "reg" + + @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( + "Eprop_reg weight dependence only supports single terms") + + return (5 # Number of 32-bit parameters + * 4) * n_synapse_types + + @overrides(AbstractWeightDependence.write_parameters) + def write_parameters( + self, spec, global_weight_scale, synapse_weight_scales, + n_weight_terms): + if n_weight_terms != 1: + raise NotImplementedError( + "Eprop_reg weight dependence only supports single terms") + + # Loop through each synapse type's weight scale + for _ in synapse_weight_scales: + 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) + + spec.write_value( + data=self.A_plus * global_weight_scale, + data_type=DataType.S1615) + spec.write_value( + data=self.A_minus * global_weight_scale, + data_type=DataType.S1615) + + spec.write_value(self.__reg_rate, 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'] diff --git a/spynnaker/pyNN/models/neuron/population_machine_synapses.py b/spynnaker/pyNN/models/neuron/population_machine_synapses.py index f589ab23eb4..2420386474e 100644 --- a/spynnaker/pyNN/models/neuron/population_machine_synapses.py +++ b/spynnaker/pyNN/models/neuron/population_machine_synapses.py @@ -206,7 +206,10 @@ def _write_synapse_parameters(self, spec, ring_buffer_shifts): spec.write_value(n_synapse_types) spec.write_value(get_n_bits(n_neurons)) spec.write_value(get_n_bits(n_synapse_types)) - spec.write_value(get_n_bits(max_delay)) + if self._app_vertex.neuron_impl.neuron_model.uses_eprop: + spec.write_value(1) + else: + spec.write_value(get_n_bits(max_delay)) spec.write_value(int(self._app_vertex.drop_late_spikes)) spec.write_value(self._app_vertex.incoming_spike_buffer_size) spec.write_array(ring_buffer_shifts) diff --git a/spynnaker/pyNN/models/neuron/population_machine_vertex.py b/spynnaker/pyNN/models/neuron/population_machine_vertex.py index 5033f72e794..16e2fea5ccb 100644 --- a/spynnaker/pyNN/models/neuron/population_machine_vertex.py +++ b/spynnaker/pyNN/models/neuron/population_machine_vertex.py @@ -11,6 +11,7 @@ # 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 enum import Enum import os import ctypes @@ -19,6 +20,10 @@ from spinn_front_end_common.abstract_models import ( AbstractGeneratesDataSpecification, AbstractRewritesDataSpecification) from spinn_front_end_common.interface.provenance import ProvenanceWriter +from spynnaker.pyNN.data import SpynnakerDataView +from spynnaker.pyNN.models.neuron.neuron_models import ( + NeuronModelLeftRightReadout) +from spynnaker.pyNN.utilities import constants from .population_machine_common import CommonRegions, PopulationMachineCommon from .population_machine_neurons import ( NeuronRegions, PopulationMachineNeurons, NeuronProvenance) @@ -296,6 +301,17 @@ def generate_data_specification(self, spec, placement): self.vertex_slice)) self._write_common_data_spec(spec, rec_regions) + # Set the poisson key for eprop left-right + routing_info = SpynnakerDataView.get_routing_infos() + # pylint: disable=protected-access + if isinstance(self._app_vertex._pynn_model._model.neuron_model, + NeuronModelLeftRightReadout): + poisson_key = routing_info.get_first_key_from_pre_vertex( + placement.vertex, constants.LIVE_POISSON_CONTROL_PARTITION_ID) + # pylint: disable=protected-access + self._app_vertex._pynn_model._model.neuron_model.set_poisson_key( + poisson_key) + self._write_neuron_data_spec(spec, self.__ring_buffer_shifts) self._write_synapse_data_spec( @@ -402,4 +418,18 @@ def set_do_synapse_regeneration(self): @overrides(PopulationMachineCommon.get_n_keys_for_partition) def get_n_keys_for_partition(self, partition_id): n_colours = 2 ** self._app_vertex.n_colour_bits - return self._vertex_slice.n_atoms * n_colours + if partition_id == constants.LIVE_POISSON_CONTROL_PARTITION_ID: + n_keys = 0 + # Seems like overkill, there should be a simpler way to do this + partitions = ( + SpynnakerDataView. + get_outgoing_edge_partitions_starting_at_vertex( + self._app_vertex)) + for partition in partitions: + if partition.identifier == ( + constants.LIVE_POISSON_CONTROL_PARTITION_ID): + for edge in partition.edges: + n_keys += edge.post_vertex.n_atoms + return n_keys * n_colours + else: + return self._vertex_slice.n_atoms * n_colours diff --git a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py index de761eee880..b3d30330481 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_static.py @@ -103,15 +103,28 @@ def get_static_synaptic_data( neuron_id_mask = (1 << n_neuron_id_bits) - 1 n_synapse_type_bits = get_n_bits(n_synapse_types) + # Branch fixed_fixed = ( - ((numpy.rint(numpy.abs(connections["weight"])).astype("uint32") & - 0xFFFF) << 16) | - (connections["delay"].astype("uint32") << + ((numpy.rint(connections["weight"]).astype("uint16") & + 0xFFFF).astype("uint32") << 16) | + ((connections["delay"].astype("uint32") & 0xFF) << (n_neuron_id_bits + n_synapse_type_bits)) | (connections["synapse_type"].astype( "uint32") << n_neuron_id_bits) | ((connections["target"] - post_vertex_slice.lo_atom) & neuron_id_mask)) + + # Master + # fixed_fixed = ( + # ((numpy.rint(numpy.abs(connections["weight"])).astype("uint32") & + # 0xFFFF) << 16) | + # (connections["delay"].astype("uint32") << + # (n_neuron_id_bits + n_synapse_type_bits)) | + # (connections["synapse_type"].astype( + # "uint32") << n_neuron_id_bits) | + # ((connections["target"] - post_vertex_slice.lo_atom) & + # neuron_id_mask)) + fixed_fixed_rows = self.convert_per_connection_data_to_rows( connection_row_indices, n_rows, fixed_fixed.view(dtype="uint8").reshape((-1, BYTES_PER_WORD)), @@ -168,9 +181,16 @@ def read_static_synaptic_data( [numpy.repeat(i, ff_size[i]) for i in range(len(ff_size))]) connections["target"] = ( (data & neuron_id_mask) + post_vertex_slice.lo_atom) - connections["weight"] = (data >> 16) & 0xFFFF - connections["delay"] = (data & 0xFFFF) >> ( - n_neuron_id_bits + n_synapse_type_bits) + # branch + connections["weight"] = ((data >> 16) & 0xFFFF).astype("int16") + connections["delay"] = (data >> (n_neuron_id_bits + + n_synapse_type_bits)) & 0xFF + connections["delay"][connections["delay"] == 0] = 16 + + # master + # connections["weight"] = (data >> 16) & 0xFFFF + # connections["delay"] = (data & 0xFFFF) >> ( + # n_neuron_id_bits + n_synapse_type_bits) return connections 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..d6da735abe0 100644 --- a/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py +++ b/spynnaker/pyNN/models/neuron/synapse_dynamics/synapse_dynamics_stdp.py @@ -348,14 +348,33 @@ def get_plastic_synaptic_data( n_neuron_id_bits = get_n_bits(max_atoms_per_core) neuron_id_mask = (1 << n_neuron_id_bits) - 1 + # Get the fixed data + # Old branch + dendritic_delays = ( + connections["delay"] * self.__dendritic_delay_fraction) + axonal_delays = ( + connections["delay"] * (1.0 - self.__dendritic_delay_fraction)) + # Get the fixed data fixed_plastic = ( - (connections["delay"].astype("uint16") << + ((dendritic_delays.astype("uint16") & 0xFF) << (n_neuron_id_bits + n_synapse_type_bits)) | + ((axonal_delays.astype("uint16") & 0xF) << + (4 + n_neuron_id_bits + n_synapse_type_bits)) | (connections["synapse_type"].astype("uint16") << n_neuron_id_bits) | ((connections["target"].astype("uint16") - post_vertex_slice.lo_atom) & neuron_id_mask)) + + # Master + # fixed_plastic = ( + # (connections["delay"].astype("uint16") << + # (n_neuron_id_bits + n_synapse_type_bits)) | + # (connections["synapse_type"].astype("uint16") + # << n_neuron_id_bits) | + # ((connections["target"].astype("uint16") - + # post_vertex_slice.lo_atom) & neuron_id_mask)) + fixed_plastic_rows = self.convert_per_connection_data_to_rows( connection_row_indices, n_rows, fixed_plastic.view(dtype="uint8").reshape((-1, 2)), @@ -379,7 +398,9 @@ def get_plastic_synaptic_data( plastic_plastic = numpy.zeros( len(connections) * n_half_words, dtype="uint16") plastic_plastic[half_word::n_half_words] = \ - numpy.rint(numpy.abs(connections["weight"])).astype("uint16") + numpy.rint(connections["weight"]).astype("uint16") + + # numpy.rint(numpy.abs(connections["weight"])).astype("uint16") # Convert the plastic data into groups of bytes per connection and # then into rows @@ -462,7 +483,7 @@ def read_plastic_synaptic_data( n_half_words += 1 half_word = 0 pp_half_words = numpy.concatenate([ - pp[:size * n_half_words * BYTES_PER_SHORT].view("uint16")[ + pp[:size * n_half_words * BYTES_PER_SHORT].view("int16")[ half_word::n_half_words] for pp, size in zip(pp_without_headers, fp_size)]) @@ -473,8 +494,12 @@ def read_plastic_synaptic_data( connections["target"] = ( (data_fixed & neuron_id_mask) + post_vertex_slice.lo_atom) connections["weight"] = pp_half_words - connections["delay"] = data_fixed >> ( - n_neuron_id_bits + n_synapse_type_bits) + connections["delay"] = (data_fixed >> ( + n_neuron_id_bits + n_synapse_type_bits)) & 0xFF + connections["delay"][connections["delay"] == 0] = 16 + # master commented out here... + # connections["delay"] = data_fixed >> ( + # n_neuron_id_bits + n_synapse_type_bits) return connections @overrides(AbstractPlasticSynapseDynamics.get_weight_mean) diff --git a/spynnaker/pyNN/models/neuron/synapse_types/__init__.py b/spynnaker/pyNN/models/neuron/synapse_types/__init__.py index 6d248cc6f61..29d2d0230cc 100644 --- a/spynnaker/pyNN/models/neuron/synapse_types/__init__.py +++ b/spynnaker/pyNN/models/neuron/synapse_types/__init__.py @@ -18,7 +18,8 @@ from .synapse_type_delta import SynapseTypeDelta from .synapse_type_alpha import SynapseTypeAlpha from .synapse_type_semd import SynapseTypeSEMD +from .synapse_type_eprop_adaptive import SynapseTypeEPropAdaptive __all__ = ["AbstractSynapseType", "SynapseTypeDualExponential", "SynapseTypeExponential", "SynapseTypeDelta", "SynapseTypeAlpha", - "SynapseTypeSEMD"] + "SynapseTypeSEMD", "SynapseTypeEPropAdaptive"] diff --git a/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_eprop_adaptive.py b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_eprop_adaptive.py new file mode 100644 index 00000000000..ea53fbe4e5c --- /dev/null +++ b/spynnaker/pyNN/models/neuron/synapse_types/synapse_type_eprop_adaptive.py @@ -0,0 +1,112 @@ +# Copyright (c) 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 +# +# https://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_synapse_type import AbstractSynapseType +from spynnaker.pyNN.utilities.struct import Struct + +ISYN_EXC = "isyn_exc" +ISYN_EXC2 = "isyn_exc2" +ISYN_INH = "isyn_inh" +ISYN_INH2 = "isyn_inh2" + + +class SynapseTypeEPropAdaptive(AbstractSynapseType): + __slots__ = [ + "_isyn_exc", + "_isyn_exc2", + "_isyn_inh", + "_isyn_inh2"] + + def __init__( + self, isyn_exc, isyn_exc2, isyn_inh, isyn_inh2 + ): + super().__init__( + [Struct([ + (DataType.S1615, ISYN_EXC), + (DataType.S1615, ISYN_EXC2), + (DataType.S1615, ISYN_INH), + (DataType.S1615, ISYN_INH2)])], + {ISYN_EXC: "", ISYN_EXC2: "", + ISYN_INH: "", ISYN_INH2: ""}) + + self._isyn_exc = isyn_exc + self._isyn_exc2 = isyn_exc2 + self._isyn_inh = isyn_inh + self._isyn_inh2 = isyn_inh2 + + @overrides(AbstractSynapseType.add_parameters) + def add_parameters(self, parameters): + pass + + @overrides(AbstractSynapseType.add_state_variables) + def add_state_variables(self, state_variables): + state_variables[ISYN_EXC] = self._isyn_exc + state_variables[ISYN_EXC2] = self._isyn_exc2 + state_variables[ISYN_INH] = self._isyn_inh + state_variables[ISYN_INH2] = self._isyn_inh2 + + @overrides(AbstractSynapseType.get_n_synapse_types) + def get_n_synapse_types(self): + return 4 + + @overrides(AbstractSynapseType.get_synapse_id_by_target) + def get_synapse_id_by_target(self, target): + if target == "input_connections": + return 0 + elif target == "recurrent_connections": + return 1 + elif target == "learning_signal": + return 2 + elif target == "unused": + return 3 + return None + + @overrides(AbstractSynapseType.get_synapse_targets) + def get_synapse_targets(self): + return ["input_connections", "recurrent_connections", + "learning_signal", "unused"] + + @property + def isyn_exc(self): + return self._isyn_exc + + @isyn_exc.setter + def isyn_exc(self, isyn_exc): + self._isyn_exc = isyn_exc + + @property + def isyn_inh(self): + return self._isyn_inh + + @isyn_inh.setter + def isyn_inh(self, isyn_inh): + self._isyn_inh = isyn_inh + + @property + def isyn_inh2(self): + return self._isyn_inh2 + + @isyn_inh2.setter + def isyn_inh2(self, isyn_inh2): + self._isyn_inh2 = isyn_inh2 + + @property + def isyn_exc2(self): + return self._isyn_exc2 + + @isyn_exc2.setter + def isyn_exc2(self, isyn_exc2): + self._isyn_exc2 = isyn_exc2 diff --git a/spynnaker/pyNN/models/neuron/threshold_types/__init__.py b/spynnaker/pyNN/models/neuron/threshold_types/__init__.py index 3656a70f8b7..0fe923e0561 100644 --- a/spynnaker/pyNN/models/neuron/threshold_types/__init__.py +++ b/spynnaker/pyNN/models/neuron/threshold_types/__init__.py @@ -15,6 +15,9 @@ from .abstract_threshold_type import AbstractThresholdType from .threshold_type_static import ThresholdTypeStatic from .threshold_type_maass_stochastic import ThresholdTypeMaassStochastic +from .threshold_type_adaptive import ThresholdTypeAdaptive +from .threshold_type_none import ThresholdTypeNone __all__ = ["AbstractThresholdType", "ThresholdTypeStatic", - "ThresholdTypeMaassStochastic"] + "ThresholdTypeMaassStochastic", "ThresholdTypeAdaptive", + "ThresholdTypeNone"] diff --git a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_adaptive.py b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_adaptive.py new file mode 100644 index 00000000000..9ef8424dd98 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_adaptive.py @@ -0,0 +1,110 @@ +# Copyright (c) 2019 The University of Manchester +# Copyright (c) 2017 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 +# +# https://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_threshold_type import AbstractThresholdType +from spynnaker.pyNN.utilities.struct import Struct + +BIG_B = "big_b" +SMALL_B = "small_b" +SMALL_B_0 = "small_b_0" +TAU_A = "tau_a" +BETA = "beta" +ADPT = "adpt" +SCALAR = "scalar" + + +class ThresholdTypeAdaptive(AbstractThresholdType): + """ A threshold that is adaptive + """ + __slots__ = [ + "__B", + "__small_b", + "__small_b_0", + "__tau_a", + "__beta", + "__scalar" + ] + + def __init__(self, B, small_b, small_b_0, tau_a, beta): + super().__init__( + [Struct([ + (DataType.S1615, BIG_B), + (DataType.S1615, SMALL_B), + (DataType.S1615, SMALL_B_0), + (DataType.UINT32, TAU_A), + (DataType.S1615, BETA), + (DataType.UINT32, SCALAR)])], + {BIG_B: "mV", SMALL_B: "mV", SMALL_B_0: "mV", TAU_A: "ms", + BETA: "", SCALAR: ""}) + self.__B = B + self.__small_b = small_b + self.__small_b_0 = small_b_0 + self.__tau_a = tau_a + self.__beta = beta + self.__scalar = 1000 + + @overrides(AbstractThresholdType.add_parameters) + def add_parameters(self, parameters): + parameters[SMALL_B_0] = self.__small_b_0 + parameters[TAU_A] = self.__tau_a + parameters[BETA] = self.__beta + parameters[SCALAR] = self.__scalar + + @overrides(AbstractThresholdType.add_state_variables) + def add_state_variables(self, state_variables): + state_variables[BIG_B] = self.__B + state_variables[SMALL_B] = self.__small_b + + @property + def B(self): + return self.__B + + @B.setter + def B(self, new_value): + self.__B = new_value + + @property + def small_b(self): + return self.__small_b + + @small_b.setter + def small_b(self, new_value): + self.__small_b = new_value + + @property + def small_b_0(self): + return self.__small_b_0 + + @small_b_0.setter + def small_b_0(self, new_value): + self.__small_b_0 = new_value + + @property + def tau_a(self): + return self.__tau_a + + @tau_a.setter + def tau_a(self, new_value): + self.__tau_a = new_value + + @property + def beta(self): + return self.__beta + + @beta.setter + def beta(self, new_value): + self.__beta = new_value diff --git a/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_none.py b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_none.py new file mode 100644 index 00000000000..668eb7f1c62 --- /dev/null +++ b/spynnaker/pyNN/models/neuron/threshold_types/threshold_type_none.py @@ -0,0 +1,36 @@ +# Copyright (c) 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 +# +# https://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 .abstract_threshold_type import AbstractThresholdType +from spynnaker.pyNN.utilities.struct import Struct + + +class ThresholdTypeNone(AbstractThresholdType): + """ A threshold that is empty of parameters and unused + """ + __slots__ = [] + + def __init__(self): + super().__init__( + [Struct([])], # no params + {}) # no units + + @overrides(AbstractThresholdType.add_parameters) + def add_parameters(self, parameters): + pass + + @overrides(AbstractThresholdType.add_state_variables) + def add_state_variables(self, state_variables): + pass diff --git a/spynnaker/pyNN/utilities/constants.py b/spynnaker/pyNN/utilities/constants.py index de224c3c599..320698c7b6e 100644 --- a/spynnaker/pyNN/utilities/constants.py +++ b/spynnaker/pyNN/utilities/constants.py @@ -53,6 +53,9 @@ WEIGHT_FLOAT_TO_FIXED_SCALE = 16.0 SCALE = WEIGHT_FLOAT_TO_FIXED_SCALE * NA_TO_PA_SCALE +# natively supported delays for all abstract_models +MAX_SUPPORTED_DELAY_TICS = 64 + #: the minimum supported delay slot between two neurons MIN_SUPPORTED_DELAY = 1