CARLsim  6.1.0
CARLsim: a GPU-accelerated SNN simulator
spike_monitor_core.cpp
Go to the documentation of this file.
1 /* * Copyright (c) 2016 Regents of the University of California. All rights reserved.
2 *
3 * Redistribution and use in source and binary forms, with or without
4 * modification, are permitted provided that the following conditions
5 * are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * 3. The names of its contributors may not be used to endorse or promote
15 * products derived from this software without specific prior written
16 * permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * *********************************************************************************************** *
31 * CARLsim
32 * created by: (MDR) Micah Richert, (JN) Jayram M. Nageswaran
33 * maintained by:
34 * (MA) Mike Avery <averym@uci.edu>
35 * (MB) Michael Beyeler <mbeyeler@uci.edu>,
36 * (KDC) Kristofor Carlson <kdcarlso@uci.edu>
37 * (TSC) Ting-Shuo Chou <tingshuc@uci.edu>
38 * (HK) Hirak J Kashyap <kashyaph@uci.edu>
39 *
40 * CARLsim v1.0: JM, MDR
41 * CARLsim v2.0/v2.1/v2.2: JM, MDR, MA, MB, KDC
42 * CARLsim3: MB, KDC, TSC
43 * CARLsim4: TSC, HK
44 * CARLsim5: HK, JX, KC
45 * CARLsim6: LN, JX, KC, KW
46 *
47 * CARLsim available from http://socsci.uci.edu/~jkrichma/CARLsim/
48 * Ver 12/31/2016
49 */
50 #include <spike_monitor_core.h>
51 
52 #include <snn.h> // CARLsim private implementation
53 #include <snn_definitions.h> // KERNEL_ERROR, KERNEL_INFO, ...
54 
55 #include <algorithm> // std::sort
56 
57 
58 
59 // we aren't using namespace std so pay attention!
60 SpikeMonitorCore::SpikeMonitorCore(SNN* snn, int monitorId, int grpId) {
61  snn_ = snn;
62  grpId_= grpId;
63  monitorId_ = monitorId;
64  nNeurons_ = -1;
65  spikeFileId_ = NULL;
66  recordSet_ = false;
67  spkMonLastUpdated_ = 0;
68 
69  mode_ = AER;
70  persistentData_ = false;
71  userHasBeenWarned_ = false;
72  needToWriteFileHeader_ = true;
73  spikeFileSignature_ = 206661989;
74  spikeFileVersion_ = 0.2f;
75 
76  // defer all unsafe operations to init function
77  init();
78 }
79 
80 void SpikeMonitorCore::init() {
81  nNeurons_ = snn_->getGroupNumNeurons(grpId_);
82  assert(nNeurons_>0);
83 
84  // spike vector will be a 2D structure that holds a list of spike times for each neuron in the group
85  // so the first dimension is neuron ID, and each spkVector_[i] holds the list of spike times of that neuron
86  // fix the first dimension of spike vector
87  spkVector_.resize(nNeurons_);
88 
89  clear();
90 
91  // use KERNEL_{ERROR|WARNING|etc} typesetting (const FILE*)
92  fpInf_ = snn_->getLogFpInf();
93  fpErr_ = snn_->getLogFpErr();
94  fpDeb_ = snn_->getLogFpDeb();
95  fpLog_ = snn_->getLogFpLog();
96 }
97 
99  if (spikeFileId_!=NULL) {
100  fclose(spikeFileId_);
101  spikeFileId_ = NULL;
102  }
103 }
104 
105 // +++++ PUBLIC METHODS: +++++++++++++++++++++++++++++++++++++++++++++++//
106 
108  assert(!isRecording());
109  recordSet_ = false;
110  userHasBeenWarned_ = false;
111  startTime_ = -1;
112  startTimeLast_ = -1;
113  stopTime_ = -1;
114  accumTime_ = 0;
115  totalTime_ = -1;
116 
117  for (int i=0; i<nNeurons_; i++)
118  spkVector_[i].clear();
119 
120  needToCalculateFiringRates_ = true;
121  needToSortFiringRates_ = true;
122  firingRates_.clear();
123  firingRatesSorted_.clear();
124  firingRates_.assign(nNeurons_,0);
125  firingRatesSorted_.assign(nNeurons_,0);
126 }
127 
129  assert(!isRecording());
130 
131  if (totalTime_==0)
132  return 0.0f;
133 
134  return getPopNumSpikes()*1000.0/(getRecordingTotalTime()*nNeurons_);
135 }
136 
138  assert(!isRecording());
139 
140  if (totalTime_==0)
141  return 0.0f;
142 
143  float meanRate = getPopMeanFiringRate();
144  std::vector<float> rates = getAllFiringRates();
145  float std = 0.0f;
146  if (nNeurons_>1) {
147  for (int i=0; i<nNeurons_; i++)
148  std += (rates[i]-meanRate)*(rates[i]-meanRate);
149  std = sqrt(std/(nNeurons_-1));
150  }
151 
152  return std;
153 }
154 
156  assert(!isRecording());
157 
158  int nSpk = 0;
159  for (int i=0; i<nNeurons_; i++)
160  nSpk += getNeuronNumSpikes(i);
161 
162  return nSpk;
163 }
164 
166  assert(!isRecording());
167 
168  // if necessary, get data structures up-to-date
169  calculateFiringRates();
170 
171  return firingRates_;
172 }
173 
175  assert(!isRecording());
176 
177  std::vector<float> rates = getAllFiringRatesSorted();
178 
179  return rates.back();
180 }
181 
183  assert(!isRecording());
184 
185  std::vector<float> rates = getAllFiringRatesSorted();
186 
187  return rates.front();
188 }
189 
191  assert(!isRecording());
192  assert(neurId>=0 && neurId<nNeurons_);
193 
194  return getNeuronNumSpikes(neurId)*1000.0/getRecordingTotalTime();
195 }
196 
198  assert(!isRecording());
199  assert(neurId>=0 && neurId<nNeurons_);
200  assert(getMode()==AER);
201 
202  return spkVector_[neurId].size();
203 }
204 
206  assert(!isRecording());
207 
208  // if necessary, get data structures up-to-date
209  sortFiringRates();
210 
211  return firingRatesSorted_;
212 }
213 
215  assert(!isRecording());
216  assert(min>=0.0f && max>=0.0f);
217  assert(max>=min);
218 
219  // if necessary, get data structures up-to-date
220  sortFiringRates();
221 
222  int counter = 0;
223  std::vector<float>::const_iterator it_begin = firingRatesSorted_.begin();
224  std::vector<float>::const_iterator it_end = firingRatesSorted_.end();
225  for(std::vector<float>::const_iterator it=it_begin; it!=it_end; it++){
226  if((*it) >= min && (*it) <= max)
227  counter++;
228  }
229 
230  return counter;
231 }
232 
234  assert(!isRecording());
235 
236  return getNumNeuronsWithFiringRate(0.0f, 0.0f);
237 }
238 
239 // \TODO need to do error check on interface
241  assert(!isRecording());
242 
243  return getNumNeuronsWithFiringRate(min,max)*100.0/nNeurons_;
244 }
245 
247  assert(!isRecording());
248 
249  return getNumNeuronsWithFiringRate(0,0)*100.0/nNeurons_;
250 }
251 
252 std::vector<std::vector<int> > SpikeMonitorCore::getSpikeVector2D(){
253  assert(!isRecording());
254  assert(mode_==AER);
255 
256  return spkVector_;
257 }
258 
259 void SpikeMonitorCore::print(bool printSpikeTimes) {
260  assert(!isRecording());
261 
262  // how many spike times to display per row
263  int dispSpkTimPerRow = 7;
264 
265  KERNEL_INFO("(t=%.3fs) SpikeMonitor for group %s(%d) has %d spikes in %ld ms (%.2f +/- %.2f Hz)",
266  (float)(snn_->getSimTime()/1000.0),
267  snn_->getGroupName(grpId_).c_str(),
268  grpId_,
269  getPopNumSpikes(),
273 
274  if (printSpikeTimes && mode_==AER) {
275  // spike times only available in AER mode
276  KERNEL_INFO("| Neur ID | Rate (Hz) | Spike Times (ms)");
277  KERNEL_INFO("|- - - - -|- - - - - -|- - - - - - - - - - - - - - - - -- - - - - - - - - - - - -")
278 
279  for (int i=0; i<nNeurons_; i++) {
280  char buffer[200];
281 #if defined(WIN32) || defined(WIN64)
282  _snprintf(buffer, 200, "| %7d | % 9.2f | ", i, getNeuronMeanFiringRate(i));
283 #else
284  snprintf(buffer, 200, "| %7d | % 9.2f | ", i, getNeuronMeanFiringRate(i));
285 #endif
286  int nSpk = spkVector_[i].size();
287  for (int j=0; j<nSpk; j++) {
288  char times[10];
289 #if defined(WIN32) || defined(WIN64)
290  _snprintf(times, 10, "%8d", spkVector_[i][j]);
291 #else
292  snprintf(times, 10, "%8d", spkVector_[i][j]);
293 #endif
294  strcat(buffer, times);
295  if (j%dispSpkTimPerRow == dispSpkTimPerRow-1 && j<nSpk-1) {
296  KERNEL_INFO("%s",buffer);
297  strcpy(buffer,"| | | ");
298  }
299  }
300  KERNEL_INFO("%s",buffer);
301  }
302  }
303 }
304 
305 void SpikeMonitorCore::pushAER(int time, int neurId) {
306  assert(isRecording());
307  assert(getMode()==AER);
308 
309  spkVector_[neurId].push_back(time);
310 }
311 
313  assert(!isRecording());
314 
315  if (!persistentData_) {
316  // if persistent mode is off (default behavior), automatically call clear() here
317  clear();
318  }
319 
320  // call updateSpikeMonitor to make sure spike file and spike vector are up-to-date
321  // Caution: must be called before recordSet_ is set to true!
322  snn_->updateSpikeMonitor(grpId_);
323 
324  needToCalculateFiringRates_ = true;
325  needToSortFiringRates_ = true;
326  recordSet_ = true;
327  long int currentTime = snn_->getSimTimeSec()*1000+snn_->getSimTimeMs();
328 
329  if (persistentData_) {
330  // persistent mode on: accumulate all times
331  // change start time only if this is the first time running it
332  startTime_ = (startTime_<0) ? currentTime : startTime_;
333  startTimeLast_ = currentTime;
334  accumTime_ = (totalTime_>0) ? totalTime_ : 0;
335  }
336  else {
337  // persistent mode off: we only care about the last probe
338  startTime_ = currentTime;
339  startTimeLast_ = currentTime;
340  accumTime_ = 0;
341  }
342 }
343 
345  assert(isRecording());
346  assert(startTime_>-1 && startTimeLast_>-1 && accumTime_>-1);
347 
348  // call updateSpikeMonitor to make sure spike file and spike vector are up-to-date
349  // Caution: must be called before recordSet_ is set to false!
350  snn_->updateSpikeMonitor(grpId_);
351 
352  recordSet_ = false;
353  userHasBeenWarned_ = false;
354  stopTime_ = snn_->getSimTimeSec()*1000+snn_->getSimTimeMs();
355 
356  // total time is the amount of time of the last probe plus all accumulated time from previous probes
357  totalTime_ = stopTime_-startTimeLast_ + accumTime_;
358  assert(totalTime_>=0);
359 }
360 
361 void SpikeMonitorCore::setSpikeFileId(FILE* spikeFileId) {
362  assert(!isRecording());
363 
364  // close previous file pointer if exists
365  if (spikeFileId_!=NULL) {
366  fclose(spikeFileId_);
367  spikeFileId_ = NULL;
368  }
369 
370  // set it to new file id
371  spikeFileId_=spikeFileId;
372 
373  if (spikeFileId_==NULL)
374  needToWriteFileHeader_ = false;
375  else {
376  // file pointer has changed, so we need to write header (again)
377  needToWriteFileHeader_ = true;
378  writeSpikeFileHeader();
379  }
380 }
381 
382 // calculate average firing rate for every neuron if we haven't done so already
383 void SpikeMonitorCore::calculateFiringRates() {
384  // only update if we have to
385  if (!needToCalculateFiringRates_)
386  return;
387 
388  assert(getMode()==AER);
389 
390  // clear, so we get the same answer every time.
391  firingRates_.assign(nNeurons_,0);
392  firingRatesSorted_.assign(nNeurons_,0);
393 
394  // this really shouldn't happen at this stage, but if recording time is zero, return all zeros
395  if (totalTime_==0) {
396  KERNEL_WARN("SpikeMonitorCore: calculateFiringRates has 0 totalTime");
397  return;
398  }
399 
400  // compute firing rate
401  assert(totalTime_>0); // avoid division by zero
402  for(int i=0;i<nNeurons_;i++) {
403  firingRates_[i]=spkVector_[i].size()*1000.0/totalTime_;
404  }
405 
406  needToCalculateFiringRates_ = false;
407 }
408 
409 // sort firing rates if we haven't done so already
410 void SpikeMonitorCore::sortFiringRates() {
411  // only sort if we have to
412  if (!needToSortFiringRates_)
413  return;
414 
415  // first make sure firing rate vector is up-to-date
416  calculateFiringRates();
417 
418  firingRatesSorted_=firingRates_;
419  std::sort(firingRatesSorted_.begin(),firingRatesSorted_.end());
420 
421  needToSortFiringRates_ = false;
422 }
423 
424 // write the header section of the spike file
425 // this should be done once per file, and should be the very first entries in the file
426 void SpikeMonitorCore::writeSpikeFileHeader() {
427  if (!needToWriteFileHeader_)
428  return;
429 
430  // write file signature
431  if (!fwrite(&spikeFileSignature_,sizeof(int),1,spikeFileId_))
432  KERNEL_ERROR("SpikeMonitorCore: writeSpikeFileHeader has fwrite error");
433 
434  // write version number
435  if (!fwrite(&spikeFileVersion_,sizeof(float),1,spikeFileId_))
436  KERNEL_ERROR("SpikeMonitorCore: writeSpikeFileHeader has fwrite error");
437 
438  // write grid dimensions
439  Grid3D grid = snn_->getGroupGrid3D(grpId_);
440  int tmpInt = grid.numX;
441  if (!fwrite(&tmpInt,sizeof(int),1,spikeFileId_))
442  KERNEL_ERROR("SpikeMonitorCore: writeSpikeFileHeader has fwrite error");
443 
444  tmpInt = grid.numY;
445  if (!fwrite(&tmpInt,sizeof(int),1,spikeFileId_))
446  KERNEL_ERROR("SpikeMonitorCore: writeSpikeFileHeader has fwrite error");
447 
448  tmpInt = grid.numZ;
449  if (!fwrite(&tmpInt,sizeof(int),1,spikeFileId_))
450  KERNEL_ERROR("SpikeMonitorCore: writeSpikeFileHeader has fwrite error");
451 
452 
453  needToWriteFileHeader_ = false;
454 }
455 
456 // Iterate through 2D spike vector and approximate size in memory.
457 // This is not exact, we are not counting the buffer overhead, only
458 // the size each subvector is memory.
460  long int bufferSize=0; // in bytes
461  for(int i=0; i<spkVector_.size();i++){
462  bufferSize+=spkVector_[i].size()*sizeof(int);
463  }
464  return bufferSize;
465 }
466 
467 // check if the spike vector is getting large. If it is, return true once until
468 // stopRecording is called.
470  if(userHasBeenWarned_)
471  return false;
472  else {
473  //check if buffer is too big
475  userHasBeenWarned_=true;
476  return true;
477  }
478  else {
479  return false;
480  }
481  }
482 }
483 
484 // returns the total accumulated time.
486  return accumTime_;
487 }
488 
int getNumNeuronsWithFiringRate(float min, float max)
returns number of neurons whose firing rate was in [min,max] during recording
void clear()
deletes data from the 2D spike vector
std::vector< float > getAllFiringRates()
returns a list of firing rates for all neurons in the group (sorted by neuron ID ascending) ...
int getNumSilentNeurons()
returns number of neurons that didn&#39;t spike while recording
float getPercentSilentNeurons()
returns percentage of neurons that didn&#39;t spike during recording
int getSimTimeSec()
Definition: snn.h:668
std::vector< float > getAllFiringRatesSorted()
returns a list of firing rates for all neurons in the group (sorted by firing rate ascending) ...
long int getRecordingTotalTime()
returns the total recorded time in ms
bool isBufferBig()
returns true if spike buffer is close to maxAllowedBufferSize
#define KERNEL_WARN(formatc,...)
SpikeMonMode getMode()
returns recording mode
float getPercentNeuronsWithFiringRate(float min, float max)
returns percentage of neurons whose firing rate was in [min,max] during recording ...
float getNeuronMeanFiringRate(int neurId)
returns the recorded mean firing rate for a specific neuron
#define KERNEL_ERROR(formatc,...)
int getNeuronNumSpikes(int neurId)
returns the number of recorded spikes of a specific neuron
Grid3D getGroupGrid3D(int grpId)
std::vector< std::vector< int > > getSpikeVector2D()
returns the 2D AER vector
void startRecording()
starts recording AER data
#define KERNEL_INFO(formatc,...)
A struct to arrange neurons on a 3D grid (a primitive cubic Bravais lattice with cubic side length 1)...
void setSpikeFileId(FILE *spikeFileId)
sets pointer to spike file
float getMaxFiringRate()
returns the largest recorded firing rate
std::string getGroupName(int grpId)
void updateSpikeMonitor(int grpId=ALL)
copy required spikes from firing buffer to spike buffer
mode in which spike information is collected in AER format
const FILE * getLogFpLog()
returns file pointer to log file
Definition: snn.h:602
long int getAccumTime()
returns the total accumulated time
void print(bool printSpikeTimes)
prints the AER vector in human-readable format
int getPopNumSpikes()
returns the total number of recorded spikes in the group
~SpikeMonitorCore()
destructor, cleans up all the memory upon object deletion
#define MAX_SPIKE_MON_BUFFER_SIZE
const FILE * getLogFpErr()
returns file pointer to error log
Definition: snn.h:598
Contains all of CARLsim&#39;s core functionality.
Definition: snn.h:138
int getSimTimeMs()
Definition: snn.h:669
float getMinFiringRate()
returns the smallest recorded firing rate
void pushAER(int time, int neurId)
inserts a (time,neurId) tupel into the 2D spike vector
bool isRecording()
returns recording status
int getSimTime()
Definition: snn.h:667
const FILE * getLogFpDeb()
returns file pointer to debug log
Definition: snn.h:600
float getPopStdFiringRate()
computes the standard deviation of firing rates in the group
int getGroupNumNeurons(int gGrpId)
Definition: snn.h:641
float getPopMeanFiringRate()
returns the recorded mean firing rate of the group
void stopRecording()
stops recording AER data
SpikeMonitorCore(SNN *snn, int monitorId, int grpId)
constructor (called by CARLsim::setSpikeMonitor)
long int getBufferSize()
returns the approximate size of the spike vector in bytes
const FILE * getLogFpInf()
function writes population weights from gIDpre to gIDpost to file fname in binary.
Definition: snn.h:596