neuron_model_izh_impl.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  2. // = Set up the desired ODE solver implementation coresponding to the
  3. // = iterations described in Trensch et al. and Gutzen et al.
  4. // =
  5. // = For all implementations the simulation time step must be set to 1.0, i.e,
  6. // = the value of SIM_TIME_STEP in polychronizationNetwork.py
  7. // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  8. #define IMPL01 false // SpiNNaker ESR implementation corresponds to Iteration I, (i) Gutzen et al.
  9. #define IMPL02 false // original Izhikevich implementation (ii) Gutzen et al.
  10. #define IMPL03 false // original Izhikevich implementation with FP conversion (iii) Gutzen et al.
  11. #define IMPL04 false // fixed-step size (h=1/16) symplectic forward Euler and corresponds to Iteration II plus III
  12. // precise threshold detection
  13. #define IMPL05 true // fixed-step size (h=1/16) symplectic forward Euler,
  14. // precise threshold detection and fixed-point conversion
  15. // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  16. #include "neuron_model_izh_impl.h"
  17. #include <debug.h>
  18. static global_neuron_params_pointer_t global_params;
  19. #if( IMPL01 )
  20. // Original SpiNNaker implementation
  21. // Solver: Explicit Solver Reduction (ESR) implementation
  22. // For linear membrane voltages, 1.5 is the correct value. However with actual membrane voltage
  23. // behaviour and tested over an wide range of use cases 1.85 gives slightly better spike timings.
  24. static const REAL SIMPLE_TQ_OFFSET = REAL_CONST(1.85);
  25. static inline void _rk2_kernel_midpoint(REAL h, neuron_pointer_t neuron, REAL input_this_timestep) {
  26. // to match Mathematica names
  27. REAL lastV1 = neuron->V;
  28. REAL lastU1 = neuron->U;
  29. REAL a = neuron->A;
  30. REAL b = neuron->B;
  31. REAL pre_alph = REAL_CONST(140.0) + input_this_timestep - lastU1;
  32. REAL alpha = pre_alph + ( REAL_CONST(5.0) + REAL_CONST(0.0400) * lastV1) * lastV1;
  33. REAL eta = lastV1 + REAL_HALF(h * alpha);
  34. // could be represented as a long fract?
  35. REAL beta = REAL_HALF(h * (b * lastV1 - lastU1) * a);
  36. neuron->V += h * (pre_alph - beta + ( REAL_CONST(5.0) + REAL_CONST(0.0400) * eta) * eta);
  37. neuron->U += a * h * (-lastU1 - beta + b * eta);
  38. }
  39. #endif
  40. #if( IMPL02 )
  41. // Original Izhikevich implementation
  42. // Solver: semi-implicit fixed-step size Forward Euler
  43. static inline void _originalIzhikevich( REAL h, neuron_pointer_t neuron, REAL input_this_timestep) {
  44. // h is set to a fixed value: h = 1.0, i.e., simulation time-step must be set to 1.0!
  45. REAL v = neuron->V;
  46. REAL u = neuron->U;
  47. REAL param_a = neuron->A;
  48. REAL param_b = neuron->B;
  49. v += REAL_HALF(( 0.04k * v + 5.0k) * v + 140.0k - u + input_this_timestep ); // for numerical stability
  50. v += REAL_HALF(( 0.04k * v + 5.0k) * v + 140.0k - u + input_this_timestep ); // 2 time steps of 0.5 ms
  51. u += param_a * ( param_b * v - u );
  52. neuron->V = v;
  53. neuron->U = u;
  54. }
  55. #endif
  56. #if( IMPL03 )
  57. // Original Izhikevich implementation with fixed-point conversion of constant 0.04 (i.e., s16.15 -> s8.23)
  58. // Solver: semi-implicit fixed-step size Forward Euler
  59. static inline void _originalIzhikevichWithFPConv( REAL h, neuron_pointer_t neuron, REAL input_this_timestep) {
  60. REAL v = neuron->V;
  61. REAL u = neuron->U;
  62. REAL param_a = neuron->A;
  63. REAL param_b = neuron->B;
  64. REAL a;
  65. REAL b;
  66. a = 10.24k * v; // byte left-shift
  67. a *= 0.00390625k; // byte right-shift
  68. a *= v;
  69. b = 5.0k * v + 140.0k - u + input_this_timestep;
  70. v += REAL_HALF(a + b);
  71. a = 10.24k * v; // byte left-shift
  72. a *= 0.00390625k; // byte right-shift
  73. a *= v;
  74. b = 5.0k * v + 140.0k - u + input_this_timestep;
  75. v += REAL_HALF(a + b);
  76. u += param_a * ( param_b * v - u );
  77. neuron->V = v;
  78. neuron->U = u;
  79. }
  80. #endif
  81. #if( IMPL04 )
  82. // Refined ODE solver implementation including a precise threshold detection
  83. // Solver: Fixed-step size (1/16) semi-implicit symplectic Forward Euler
  84. static inline void _fixedStepSizeEuler( REAL h, neuron_pointer_t neuron, REAL input_this_timestep) {
  85. #define THRESHOLD_DETECTED 1024.0k // Some high value above threshold, used to trigger a spike event
  86. // in the SpiNNaker kernel.
  87. REAL v = neuron->V;
  88. REAL u = neuron->U;
  89. REAL param_a = neuron->A;
  90. REAL param_b = neuron->B;
  91. REAL a;
  92. REAL b;
  93. bool spikeEvent = false;
  94. int steps = 16;
  95. for( int cycles = 0; cycles < steps; ++cycles ) {
  96. a = 0.04k * v;
  97. a *= v;
  98. b = 5.0k * v + 140.0k - u + input_this_timestep;
  99. v += 0.0625k * (a + b); // 0.0625 = 1/16, avoid costly division
  100. u += 0.0625k * (param_a * ( param_b * v - u )); // 0.0625 = 1/16
  101. if( v >= 30.0k ) {
  102. spikeEvent = true;
  103. v = neuron->C;
  104. u += neuron->D;
  105. }
  106. }
  107. if( spikeEvent ) {
  108. neuron->V = v + THRESHOLD_DETECTED; // Overlay the data with the spike event information.
  109. // Note: Hiding and encoding information in the data to
  110. // control the program flow is not good practice!
  111. // This has been done here for the sake of simplicity
  112. // and to avoid changes in the SpiNNaker kernel.
  113. }
  114. else {
  115. neuron->V = v;
  116. }
  117. neuron->U = u;
  118. }
  119. #endif
  120. #if( IMPL05 )
  121. // Refined ODE solver implementation including a precise threshold detection and fixed-point conversion
  122. // Solver: Fixed-step size (1/16) semi-implicit symplectic Forward Euler
  123. static inline void _fixedStepSizeEulerWithFPConv( REAL h, neuron_pointer_t neuron, REAL input_this_timestep) {
  124. #define THRESHOLD_DETECTED 1024.0k // Some high value above threshold, used to trigger a spike event
  125. // in the SpiNNaker kernel.
  126. REAL v = neuron->V;
  127. REAL u = neuron->U;
  128. REAL param_a = neuron->A;
  129. REAL param_a_RS = 5.12k; // 0.02 RS param_a s8.23
  130. REAL param_a_FS = 25.6k; // 0.1 FS param_a s8.23
  131. REAL param_b_FSRS = 51.2k; // 0.2 FS RS param_b s8.23
  132. REAL param_a_s8_23;
  133. REAL a;
  134. REAL b;
  135. REAL c;
  136. REAL d;
  137. bool spikeEvent = false;
  138. int steps = 16;
  139. for( int cycles = 0; cycles < (steps + 2); ++cycles ) { // (steps + 2) to supress unrolling the loop ..
  140. if(cycles < steps ) { // .. to prevent ITCM overflow (2 empty cyles)
  141. // (Corresponding compiler option did not work.)
  142. a = 10.24k * v; // byte left-shift
  143. a *= 0.00390625k; // byte right-shift
  144. a *= v;
  145. b = 5.0k * v + 140.0k - u + input_this_timestep;
  146. v += 0.0625k * (a + b); // 0.0625 = 1/16
  147. if( param_a > 0.05k ) { // identify neuron type
  148. param_a_s8_23 = param_a_FS;
  149. }
  150. else {
  151. param_a_s8_23 = param_a_RS;
  152. }
  153. d = param_b_FSRS * v;
  154. d *= 0.00390625k; // byte right-shift
  155. d -= u;
  156. c = 0.0625k * (param_a_s8_23 * ( d )); // 0.0625 = 1/16
  157. c *= 0.00390625k; // byte right-shift
  158. u += c;
  159. if( v >= 30.0k ) {
  160. spikeEvent = true;
  161. v = neuron->C;
  162. u += neuron->D;
  163. }
  164. }
  165. }
  166. if( spikeEvent ) {
  167. neuron->V = v + THRESHOLD_DETECTED; // Overlay the data with the spike event information.
  168. // Note: Hiding and encoding information in the data to
  169. // control the program flow is not good practice!
  170. // This has been done here for the sake of simplicity
  171. // and to avoid changes in the SpiNNaker kernel.
  172. }
  173. else {
  174. neuron->V = v;
  175. }
  176. neuron->U = u;
  177. }
  178. #endif
  179. void neuron_model_set_global_neuron_params( global_neuron_params_pointer_t params ) {
  180. global_params = params;
  181. }
  182. state_t neuron_model_state_update( input_t exc_input, input_t inh_input, input_t external_bias,
  183. neuron_pointer_t neuron) {
  184. input_t input_this_timestep = exc_input - inh_input + external_bias + neuron->I_offset;
  185. #if( IMPL01 )
  186. _rk2_kernel_midpoint(neuron->this_h, neuron, input_this_timestep);
  187. #endif
  188. #if( IMPL02 )
  189. _originalIzhikevich(neuron->this_h, neuron, input_this_timestep);
  190. #endif
  191. #if( IMPL03 )
  192. _originalIzhikevichWithFPConv(neuron->this_h, neuron, input_this_timestep);
  193. #endif
  194. #if( IMPL04 )
  195. _fixedStepSizeEuler(neuron->this_h, neuron, input_this_timestep);
  196. #endif
  197. #if( IMPL05 )
  198. _fixedStepSizeEulerWithFPConv(neuron->this_h, neuron, input_this_timestep);
  199. #endif
  200. neuron->this_h = global_params->machine_timestep_ms;
  201. return neuron->V;
  202. }
  203. void neuron_model_has_spiked(neuron_pointer_t neuron) {
  204. #if( IMPL01 || IMPL02 || IMPL03 )
  205. neuron->V = neuron->C;
  206. neuron->U += neuron->D;
  207. #endif
  208. #if( IMPL01 )
  209. // simple threshold correction - next timestep (only) gets a bump
  210. neuron->this_h = global_params->machine_timestep_ms * SIMPLE_TQ_OFFSET;
  211. #endif
  212. #if( IMPL04 || IMPL05 )
  213. // remove metadata from the membran voltage after the spike processing
  214. // has been triggered in the SpiNNaker kernel
  215. neuron->V -= THRESHOLD_DETECTED;
  216. #endif
  217. }
  218. state_t neuron_model_get_membrane_voltage(neuron_pointer_t neuron) {
  219. return neuron->V;
  220. }
  221. void neuron_model_print_state_variables(restrict neuron_pointer_t neuron) {
  222. log_debug("V = %11.4k ", neuron->V);
  223. log_debug("U = %11.4k ", neuron->U);
  224. }
  225. void neuron_model_print_parameters(restrict neuron_pointer_t neuron) {
  226. log_debug("A = %11.4k ", neuron->A);
  227. log_debug("B = %11.4k ", neuron->B);
  228. log_debug("C = %11.4k ", neuron->C);
  229. log_debug("D = %11.4k ", neuron->D);
  230. log_debug("I = %11.4k \n", neuron->I_offset);
  231. }