/* * bcpnn_connection.h * * Written by Philip Tully * */ #ifndef BCPNN_CONNECTION_H #define BCPNN_CONNECTION_H /* BeginDocumentation Name: bcpnn_synapse - Synapse type for incremental, Bayesian spike-timing dependent plasticity. Description: bcpnn_synapse is a connector to create synapses with incremental, Bayesian spike timing dependent plasticity. tau_i double - Primary trace presynaptic time constant tau_j double - Primary trace postsynaptic time constant tau_e double - Secondary trace time constant tau_p double - Tertiarty trace time constant p_i double - \ p_j double - >- these 3 initial conditions determine weight, i.e. log(p_ij/(p_i * p_j)). p_ij double - / K_ double - Print-now signal // Neuromodulation. Turn off learning, K = 0. fmax_ double - Frequency assumed as maximum firing, for match with abstract rule epsilon_ double - lowest possible probability of spiking, e.g. lowest assumed firing rate bias_ double - ANN interpretation. Only calculated here to demonstrate match to rule. Will be eliminated in future versions, where bias will be calculated postsynaptically gain_ double - Coefficient to scale weight as conductance, can be zero-ed out Transmits: SpikeEvent References: [1] Wahlgren and Lansner (2001) Biological Evaluation of a Hebbian-Bayesian learning rule. Neurocomputing, 38-40, 433-438 [2] Bergel, Transforming the BCPNN Learning Rule for Spiking Units to a Learning Rule for Non-Spiking Units (2010). KTH Masters Thesis. FirstVersion: November 2011 CurrentVersion: March 2012 Author: Philip Tully tully@csc.kth.se SeeAlso: synapsedict, stdp_synapse, tsodyks_synapse, static_synapse */ /* for Debugging */ #include using namespace std; #include "connection_het_wd.h" #include "archiving_node.h" #include "generic_connector.h" #include namespace mynest { class BCPNNConnection : public nest::ConnectionHetWD { public: /* Default Constructor. Sets default values for all parameters. Needed by GenericConnectorModel. */ BCPNNConnection(); /* Copy constructor. Needs to be defined properly in order for GenericConnector to work. */ BCPNNConnection(const BCPNNConnection &); /* Default Destructor. */ ~BCPNNConnection() {} void check_connection(nest::Node & s, nest::Node & r, nest::port receptor_type, nest::double_t t_lastspike); /* Get all properties of this connection and put them into a dictionary. */ void get_status(DictionaryDatum & d) const; /* Set properties of this connection from the values given in dictionary. */ void set_status(const DictionaryDatum & d, nest::ConnectorModel &cm); /* Set properties of this connection from position p in the properties array given in dictionary. */ void set_status(const DictionaryDatum & d, nest::index p, nest::ConnectorModel &cm); /* Create new empty arrays for the properties of this connection in the given dictionary. It is assumed that they do not exist before. */ void initialize_property_arrays(DictionaryDatum & d) const; /* Append properties of this connection to the given dictionary. If the dictionary is empty, new arrays are created first. */ void append_properties(DictionaryDatum & d) const; /* Send an event to the receiver of this connection. */ void send(nest::Event& e, nest::double_t t_lastspike, const nest::CommonSynapseProperties &cp); /* Overloaded for all supported event types. */ using nest::Connection::check_event; void check_event(nest::SpikeEvent&) {} private: /* data members of each connection */ nest::double_t yi_; nest::double_t yj_; nest::double_t taui_; nest::double_t tauj_; nest::double_t taue_; nest::double_t taup_; nest::double_t epsilon_; nest::double_t K_; nest::double_t bias_; nest::double_t fmax_; nest::double_t gain_; nest::double_t zi_; nest::double_t zj_; nest::double_t ei_; nest::double_t ej_; nest::double_t eij_; nest::double_t pi_; nest::double_t pj_; nest::double_t pij_; nest::double_t t_k_; std::vector times_k_changed; std::vector post_spiketimes; std::vector K_values_; }; /* of class BCPNNConnection */ inline void BCPNNConnection::check_connection(nest::Node & s, nest::Node & r, nest::port receptor_type, nest::double_t t_lastspike) { nest::ConnectionHetWD::check_connection(s, r, receptor_type, t_lastspike); // For a new synapse, t_lastspike contains the point in time of the last spike. // So we initially read the history(t_last_spike - dendritic_delay, ..., T_spike-dendritic_delay] // which increases the access counter for these entries. // At registration, all entries' access counters of history[0, ..., t_last_spike - dendritic_delay] will be // incremented by the following call to Archiving_Node::register_stdp_connection(). // See bug #218 for details. r.register_stdp_connection(t_lastspike - nest::Time(nest::Time::step(delay_)).get_ms()); } /* Send an event to the receiver of this connection. * \param e The event to send * \param p The port under which this connection is stored in the Connector. * \param t_lastspike Time point of last spike emitted note: every time this method is called by an outside function, a presynaptic event has occured and is being transmitted to the postsynaptic side. */ inline void BCPNNConnection::send(nest::Event& e, nest::double_t t_lastspike, const nest::CommonSynapseProperties &) { nest::double_t t_spike = e.get_stamp().get_ms(); /* time stamp of current spike event */ nest::double_t dendritic_delay = nest::Time(nest::Time::step(delay_)).get_ms(); /* delay from dendrite -> soma */ nest::double_t resolution = nest::Time::get_resolution().get_ms(); /* nest.GetKernelStatus('resolution') simulation timestep */ nest::int_t spike_width = 10; /* assume spike width of 1ms, resolution is 0.1 so mult by 10 */ nest::double_t spike_height = 1000.0 / fmax_; /* normalizing to match this spiking rule to abstract = 1000/FMAX (Hz)*/ nest::int_t counter = 0; /* ensuring traces reverberate for duration of the spike width */ nest::double_t min_weight = epsilon_/std::pow(0.5 ,2); /* theoretical minimum weight = epsilon/(0.5*0.5) */ // cout << "t_lastspike: " << t_lastspike << " t_spike: " << t_spike << " K_values.size(): " << K_values_.size() << endl; /*STEP ONE: Get all timings of pre and postsynaptic spikes. Post store in dynamically allocated array */ /* get spike history in relevant range (t1, t2] from post-synaptic neuron */ std::deque::iterator start; std::deque::iterator finish; /* Initially read the history(t_last_spike - dendritic_delay, ..., T_spike-dendritic_delay] which increases the access counter for these entries. At registration, all entries' access counters of history[0, ..., t_last_spike - dendritic_delay] have been incremented by Archiving_Node::register_stdp_connection(). See bug #218 for details. */ target_->get_history(t_lastspike - dendritic_delay, t_spike - dendritic_delay, &start, &finish); /* For spike order pre-post, if dopamine present facilitate else depress. Pre spikes: | | t_lastpike is the last pre spike and t_spike is the current pre spike Post spikes | || start is a pointer to the first post spike in the interval between the two pre spikes. It is then iterated until the last post spike in the interval */ while (start != finish) {/* loop until you get to last post spike */ post_spiketimes.push_back(start->t_); start++; } /* of while */ /* STEP TWO: Consider the presynaptic firing window, delta t resolution, and update the traces */ /* nest stores with ms precision the timing of the spike. */ /* the following loop iterates through the presynaptic spike difference window */ counter = 0; nest::int_t number_iterations = (nest::int_t)((t_spike - t_lastspike)/resolution); // This if+else is to account for the case when K_ is initialized with 1 and K_ is changed via set_status /before/ the simulation starts std::vector K_vec (number_iterations, K_values_.front()); if((nest::int_t)t_lastspike == 0) { K_vec = std::vector (number_iterations, K_values_.back()); } std::vector::iterator post_it = post_spiketimes.begin(); std::vector::iterator time_it = times_k_changed.end(); std::vector::iterator K_it = K_values_.end(); if (K_values_.size() > 1) { // only if a set_status has ever been called --> TODO if (times_k_changed.back() >= t_lastspike){ // if K has changed since the last pre-synaptic spike has occured // --> find out when changes occured and set the K_vec values right K_it--; // move iterator one element to the left, because .end() returns iterator just past the last element time_it--; nest::int_t idx_first = (nest::int_t) ((t_spike - t_lastspike) / resolution); nest::int_t idx_second; while (*time_it > t_lastspike){ idx_second = (nest::int_t) ((*time_it - t_lastspike)/ resolution); // n_constant_K = (nest::int_t) ((idx_first - idx_second) / resolution); // cout << "New spike at *time_it: " << *time_it << " t_lastspike: " << t_lastspike << endl; for (nest::int_t i_k=idx_first-1; i_k >= idx_second; --i_k) { // alternative implementation would be to create a temporary vector with the correct K-values and assign this to K_vec // cout << "debug size K_vec = " << K_vec.size() << " i_k = " << i_k << " idx_first: " << idx_first << " idx_second: " << idx_second << " times_k_changed.size=" << times_k_changed.size() << " t_lastspike=" << t_lastspike << endl; K_vec.at(i_k) = *K_it; } idx_first = idx_second; time_it--; K_it--; } // end of while // K_ = *(K_values_.end()); // update the private K_ value } } K_values_.clear(); K_values_.push_back(K_); times_k_changed.clear(); times_k_changed.push_back(*time_it); nest::int_t idx_spike; /* Create a vector to represent the post spikes as a trace */ std::vector post_active (number_iterations + spike_width, 0.); // std::vector bar (5,0); // cout << "weight at send t_spike " << t_spike << " number_iterations: " << number_iterations << " t_lastspike: " << t_lastspike << endl; cout << "START PRINTOUT" << endl; // cout << t_spike << "\t" << number_iterations << "\t" << t_lastspike << endl; for (nest::int_t timestep = 0; timestep < number_iterations; timestep++) { /* CASE: Default. Neither Pre nor Post spike. */ yi_ = 0.0; yj_ = 0.0; /* CASE: Pre without (*OR WITH post) spike - synchronous events handled automatically. */ // if(timestep < spike_width && (nest::int_t)t_lastspike != 0) { if(timestep == 0 && t_lastspike != 0.) { yi_ = spike_height * spike_width; } // if you have any post spike at all if (post_spiketimes.size() > 0) { if (post_it != post_spiketimes.end()) { if (timestep == (nest::int_t)((*post_it) - t_lastspike) / resolution){ yj_ = spike_height * spike_width; post_it++; } } } /* Primary synaptic traces. Noise - commented out*/ zi_ += (yi_ - zi_ + epsilon_ /*+ (0.01 + (double)rand() / RAND_MAX * (0.05 - 0.01))*/ ) * resolution / taui_; // zj_ += (spike_height * post_active.at(timestep) - zj_ + epsilon_ /*+ (0.01 + (double)rand() / RAND_MAX * (0.05 - 0.01))*/ ) * resolution / tauj_; zj_ += (yj_ - zj_ + epsilon_ /*+ (0.01 + (double)rand() / RAND_MAX * (0.05 - 0.01))*/ ) * resolution / tauj_; /* Secondary synaptic traces */ ei_ += (zi_ - ei_) * resolution / taue_; ej_ += (zj_ - ej_) * resolution / taue_; eij_ += (zi_ * zj_ - eij_) * resolution / taue_; /* Tertiary synaptic traces. Commented is from Wahlgren paper. */ // pi_ += K_ * (ei_ - pi_) * resolution / taup_/* * eij_*/; // pj_ += K_ * (ej_ - pj_) * resolution / taup_/* * eij_*/; // pij_ += K_ * (eij_ - pij_) * resolution / taup_/* * eij_*/; pi_ += K_vec[timestep] * (ei_ - pi_) * resolution / taup_; pj_ += K_vec[timestep] * (ej_ - pj_) * resolution / taup_; pij_ += K_vec[timestep] * (eij_ - pij_) * resolution / taup_; weight_ = gain_ * std::log(pij_ / (pi_ * pj_)) /*- std::log(min_weight)*/; cout << timestep << "\t" << zi_ << "\t" << zj_ << "\t" << ei_ << "\t" << "\t" << ej_ << "\t" << eij_ << "\t" << pi_ << "\t" << pj_ << "\t" << pij_ << "\t" << weight_ << "\t" << t_spike << "\t" << yj_ << "\t" << yi_ << endl; } /* of for */ cout << "END PRINTOUT" << endl; /* Update the weight & bias before event is sent. Use commented normalization to implement soft weight bounds, this way the weight will never go below 0 because you push all weights up by the most negative weight possible. */ bias_ = std::log(pj_); weight_ = gain_ * (std::log(pij_ / (pi_ * pj_)) /*- std::log(min_weight) */); /* STEP THREE. Implement hard weight bounds. NOTE if using above normalization, weights are soft-bounded above zero already. */ /*weight_ = (weight_ < 0) ? weight_ : 0.0; nest::double_t Wmax = ...; weight_ = (weight_ > Wmax) ? weight_ : Wmax;*/ /* Send the spike to the target */ e.set_receiver(*target_); e.set_weight(weight_); e.set_delay(delay_); e.set_rport(rport_); e(); /* final clean up */ // before clearing it, remember the last spike, for the case that the post synaptic spike is just before the next presynaptic spike // nest::double_t t_temp = post_spiketimes.back(); post_spiketimes.clear(); // post_spiketimes.push_back(0.); // cout << "after send, post_spiketimes holds: " << post_spiketimes.front() << endl; // K_vec.clear(); } /* of BCPNNConnection::send */ } /* of namespace mynest */ #endif /* of #ifndef BCPNN_CONNECTION_H */