| Total Complexity | 43 |
| Total Lines | 396 |
| Duplicated Lines | 0 % |
Complex classes like hhn_network often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | """! |
||
| 160 | class hhn_network(network): |
||
| 161 | """! |
||
| 162 | @brief Oscillatory Neural Network with central element based on Hodgkin-Huxley neuron model. Interaction between oscillators is performed via |
||
| 163 | central element (no connection between oscillators that are called as peripheral). Peripheral oscillators receive external stimulus. |
||
| 164 | Central element consist of two oscillators: the first is used for synchronization some ensemble of oscillators and the second controls |
||
| 165 | synchronization of the first cental oscillator with verious ensembles. |
||
| 166 | |||
| 167 | Example: |
||
| 168 | @code |
||
| 169 | # change period of time when high strength value of synaptic connection exists from CN2 to PN. |
||
| 170 | params = hhn_parameters(); |
||
| 171 | params.deltah = 400; |
||
| 172 | |||
| 173 | # create oscillatory network with stimulus |
||
| 174 | net = hhn_network(6, [0, 0, 25, 25, 47, 47], params); |
||
| 175 | |||
| 176 | # simulate network |
||
| 177 | (t, dyn) = net.simulate(1200, 600); |
||
| 178 | |||
| 179 | # draw network output during simulation |
||
| 180 | draw_dynamics(t, dyn, x_title = "Time", y_title = "V", separate = True); |
||
| 181 | @endcode |
||
| 182 | |||
| 183 | """ |
||
| 184 | |||
| 185 | _name = "Oscillatory Neural Network based on Hodgkin-Huxley Neuron Model" |
||
| 186 | |||
| 187 | # States of peripheral oscillators |
||
| 188 | _membrane_potential = None; # membrane potential of neuron (V) |
||
| 189 | _active_cond_sodium = None; # activation conductance of the sodium channel (m) |
||
| 190 | _inactive_cond_sodium = None; # inactivaton conductance of the sodium channel (h) |
||
| 191 | _active_cond_potassium = None; # activation conductance of the potassium channel (n) |
||
| 192 | _link_activation_time = None; # time of set w3 - connection from CN2 to PN for each oscillator. |
||
| 193 | _link_weight3 = None; # connection strength for each oscillator from CN2 to PN. |
||
| 194 | |||
| 195 | _pulse_generation_time = None; # time of spike generation for each oscillator. |
||
| 196 | _pulse_generation = None; # spike generation for each oscillator. |
||
| 197 | |||
| 198 | _stimulus = None; # stimulus of each oscillator |
||
| 199 | _noise = None; # Noise for each oscillator |
||
| 200 | |||
| 201 | _central_element = None; # Central element description |
||
| 202 | |||
| 203 | _params = None; # parameters of the network |
||
| 204 | |||
| 205 | _membrane_dynamic_pointer = None; # final result is stored here. |
||
| 206 | |||
| 207 | def __init__(self, num_osc, stimulus = None, parameters = None, type_conn = None, type_conn_represent = conn_represent.MATRIX): |
||
| 208 | """! |
||
| 209 | @brief Constructor of oscillatory network based on Hodgkin-Huxley meuron model. |
||
| 210 | |||
| 211 | @param[in] num_osc (uint): Number of peripheral oscillators in the network. |
||
| 212 | @param[in] stimulus (list): List of stimulus for oscillators, number of stimulus should be equal to number of peripheral oscillators. |
||
| 213 | @param[in] parameters (hhn_parameters): Parameters of the network. |
||
| 214 | @param[in] type_conn (conn_type): Type of connections between oscillators in the network (ignored for this type of network). |
||
| 215 | @param[in] type_conn_represent (conn_represent): Internal representation of connection in the network: matrix or list. |
||
| 216 | |||
| 217 | """ |
||
| 218 | |||
| 219 | super().__init__(num_osc, conn_type.NONE, type_conn_represent); |
||
| 220 | |||
| 221 | self._membrane_potential = [0.0] * self._num_osc; |
||
| 222 | self._active_cond_sodium = [0.0] * self._num_osc; |
||
| 223 | self._inactive_cond_sodium = [0.0] * self._num_osc; |
||
| 224 | self._active_cond_potassium = [0.0] * self._num_osc; |
||
| 225 | self._link_activation_time = [0.0] * self._num_osc; |
||
| 226 | self._link_pulse_counter = [0.0] * self._num_osc; |
||
| 227 | self._link_deactivation_time = [0.0] * self._num_osc; |
||
| 228 | self._link_weight3 = [0.0] * self._num_osc; |
||
| 229 | self._pulse_generation_time = [ [] for i in range(self._num_osc) ]; |
||
| 230 | self._pulse_generation = [False] * self._num_osc; |
||
| 231 | |||
| 232 | self._noise = [random.random() * 2.0 - 1.0 for i in range(self._num_osc)]; |
||
| 233 | |||
| 234 | self._central_element = [central_element(), central_element()]; |
||
| 235 | |||
| 236 | if (stimulus is None): |
||
| 237 | self._stimulus = [0.0] * self._num_osc; |
||
| 238 | else: |
||
| 239 | self._stimulus = stimulus; |
||
| 240 | |||
| 241 | if (parameters is not None): |
||
| 242 | self._params = parameters; |
||
| 243 | else: |
||
| 244 | self._params = hhn_parameters(); |
||
| 245 | |||
| 246 | |||
| 247 | def simulate(self, steps, time, solution = solve_type.RK4, collect_dynamic = True): |
||
| 248 | """! |
||
| 249 | @brief Performs static simulation of oscillatory network based on Hodgkin-Huxley neuron model. |
||
| 250 | |||
| 251 | @param[in] steps (uint): Number steps of simulations during simulation. |
||
| 252 | @param[in] time (double): Time of simulation. |
||
| 253 | @param[in] solution (solve_type): Type of solver for differential equations. |
||
| 254 | @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics. |
||
| 255 | |||
| 256 | @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time, |
||
| 257 | otherwise returns only last values (last step of simulation) of dynamic. |
||
| 258 | |||
| 259 | """ |
||
| 260 | |||
| 261 | return self.simulate_static(steps, time, solution, collect_dynamic); |
||
| 262 | |||
| 263 | |||
| 264 | def simulate_static(self, steps, time, solution = solve_type.RK4, collect_dynamic = False): |
||
| 265 | """! |
||
| 266 | @brief Performs static simulation of oscillatory network based on Hodgkin-Huxley neuron model. |
||
| 267 | |||
| 268 | @param[in] steps (uint): Number steps of simulations during simulation. |
||
| 269 | @param[in] time (double): Time of simulation. |
||
| 270 | @param[in] solution (solve_type): Type of solver for differential equations. |
||
| 271 | @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics. |
||
| 272 | |||
| 273 | @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time, |
||
| 274 | otherwise returns only last values (last step of simulation) of dynamic. |
||
| 275 | |||
| 276 | """ |
||
| 277 | |||
| 278 | self._membrane_dynamic_pointer = None; |
||
| 279 | |||
| 280 | # Check solver before simulation |
||
| 281 | if (solution == solve_type.FAST): |
||
| 282 | raise NameError("Solver FAST is not support due to low accuracy that leads to huge error.");
|
||
| 283 | elif (solution == solve_type.RKF45): |
||
| 284 | raise NameError("Solver RKF45 is not support in python version.");
|
||
| 285 | |||
| 286 | dyn_memb = None; |
||
| 287 | dyn_time = None; |
||
| 288 | |||
| 289 | # Store only excitatory of the oscillator |
||
| 290 | if (collect_dynamic == True): |
||
| 291 | dyn_memb = []; |
||
| 292 | dyn_time = []; |
||
| 293 | |||
| 294 | step = time / steps; |
||
| 295 | int_step = step / 10.0; |
||
| 296 | |||
| 297 | for t in numpy.arange(step, time + step, step): |
||
| 298 | # update states of oscillators |
||
| 299 | memb = self._calculate_states(solution, t, step, int_step); |
||
| 300 | |||
| 301 | # update states of oscillators |
||
| 302 | if (collect_dynamic == True): |
||
| 303 | dyn_memb.append(memb); |
||
| 304 | dyn_time.append(t); |
||
| 305 | else: |
||
| 306 | dyn_memb = memb; |
||
| 307 | dyn_time = t; |
||
| 308 | |||
| 309 | self._membrane_dynamic_pointer = dyn_memb; |
||
| 310 | return (dyn_time, dyn_memb); |
||
| 311 | |||
| 312 | |||
| 313 | def _calculate_states(self, solution, t, step, int_step): |
||
| 314 | """! |
||
| 315 | @brief Caclculates new state of each oscillator in the network. Returns only excitatory state of oscillators. |
||
| 316 | |||
| 317 | @param[in] solution (solve_type): Type solver of the differential equations. |
||
| 318 | @param[in] t (double): Current time of simulation. |
||
| 319 | @param[in] step (uint): Step of solution at the end of which states of oscillators should be calculated. |
||
| 320 | @param[in] int_step (double): Differentiation step that is used for solving differential equation. |
||
| 321 | |||
| 322 | @return (list) New states of membrance potentials for peripheral oscillators and for cental elements as a list where |
||
| 323 | the last two values correspond to central element 1 and 2. |
||
| 324 | |||
| 325 | """ |
||
| 326 | |||
| 327 | next_membrane = [0.0] * self._num_osc; |
||
| 328 | next_active_sodium = [0.0] * self._num_osc; |
||
| 329 | next_inactive_sodium = [0.0] * self._num_osc; |
||
| 330 | next_active_potassium = [0.0] * self._num_osc; |
||
| 331 | |||
| 332 | # Update states of oscillators |
||
| 333 | for index in range (0, self._num_osc, 1): |
||
| 334 | result = odeint(self.hnn_state, |
||
| 335 | [ self._membrane_potential[index], self._active_cond_sodium[index], self._inactive_cond_sodium[index], self._active_cond_potassium[index] ], |
||
| 336 | numpy.arange(t - step, t, int_step), |
||
| 337 | (index , )); |
||
| 338 | |||
| 339 | [ next_membrane[index], next_active_sodium[index], next_inactive_sodium[index], next_active_potassium[index] ] = result[len(result) - 1][0:4]; |
||
| 340 | |||
| 341 | next_cn_membrane = [0.0, 0.0]; |
||
| 342 | next_cn_active_sodium = [0.0, 0.0]; |
||
| 343 | next_cn_inactive_sodium = [0.0, 0.0]; |
||
| 344 | next_cn_active_potassium = [0.0, 0.0]; |
||
| 345 | |||
| 346 | # Update states of central elements |
||
| 347 | for index in range(0, len(self._central_element)): |
||
| 348 | result = odeint(self.hnn_state, |
||
| 349 | [ self._central_element[index].membrane_potential, self._central_element[index].active_cond_sodium, self._central_element[index].inactive_cond_sodium, self._central_element[index].active_cond_potassium ], |
||
| 350 | numpy.arange(t - step, t, int_step), |
||
| 351 | (self._num_osc + index , )); |
||
| 352 | |||
| 353 | [ next_cn_membrane[index], next_cn_active_sodium[index], next_cn_inactive_sodium[index], next_cn_active_potassium[index] ] = result[len(result) - 1][0:4]; |
||
| 354 | |||
| 355 | # Noise generation |
||
| 356 | self._noise = [ 1.0 + 0.01 * (random.random() * 2.0 - 1.0) for i in range(self._num_osc)]; |
||
| 357 | |||
| 358 | # Updating states of PNs |
||
| 359 | self.__update_peripheral_neurons(t, step, next_membrane, next_active_sodium, next_inactive_sodium, next_active_potassium); |
||
| 360 | |||
| 361 | # Updation states of CN |
||
| 362 | self.__update_central_neurons(t, next_cn_membrane, next_cn_active_sodium, next_cn_inactive_sodium, next_cn_active_potassium); |
||
| 363 | |||
| 364 | return next_membrane + next_cn_membrane; |
||
| 365 | |||
| 366 | |||
| 367 | def __update_peripheral_neurons(self, t, step, next_membrane, next_active_sodium, next_inactive_sodium, next_active_potassium): |
||
| 368 | """! |
||
| 369 | @brief Update peripheral neurons in line with new values of current in channels. |
||
| 370 | |||
| 371 | @param[in] t (doubles): Current time of simulation. |
||
| 372 | @param[in] step (uint): Step (time duration) during simulation when states of oscillators should be calculated. |
||
| 373 | @param[in] next_membrane (list): New values of membrane potentials for peripheral neurons. |
||
| 374 | @Param[in] next_active_sodium (list): New values of activation conductances of the sodium channels for peripheral neurons. |
||
| 375 | @param[in] next_inactive_sodium (list): New values of inactivaton conductances of the sodium channels for peripheral neurons. |
||
| 376 | @param[in] next_active_potassium (list): New values of activation conductances of the potassium channel for peripheral neurons. |
||
| 377 | |||
| 378 | """ |
||
| 379 | |||
| 380 | self._membrane_potential = next_membrane[:]; |
||
| 381 | self._active_cond_sodium = next_active_sodium[:]; |
||
| 382 | self._inactive_cond_sodium = next_inactive_sodium[:]; |
||
| 383 | self._active_cond_potassium = next_active_potassium[:]; |
||
| 384 | |||
| 385 | for index in range(0, self._num_osc): |
||
| 386 | if (self._pulse_generation[index] is False): |
||
| 387 | if (self._membrane_potential[index] > 0.0): |
||
| 388 | self._pulse_generation[index] = True; |
||
| 389 | self._pulse_generation_time[index].append(t); |
||
| 390 | else: |
||
| 391 | if (self._membrane_potential[index] < 0.0): |
||
| 392 | self._pulse_generation[index] = False; |
||
| 393 | |||
| 394 | # Update connection from CN2 to PN |
||
| 395 | if (self._link_weight3[index] == 0.0): |
||
| 396 | if ( (self._membrane_potential[index] > self._params.threshold) and (self._membrane_potential[index] > self._params.threshold) ): |
||
| 397 | self._link_pulse_counter[index] += step; |
||
| 398 | |||
| 399 | if (self._link_pulse_counter[index] >= 1 / self._params.eps): |
||
| 400 | self._link_weight3[index] = self._params.w3; |
||
| 401 | self._link_activation_time[index] = t; |
||
| 402 | else: |
||
| 403 | if ( not ((self._link_activation_time[index] < t) and (t < self._link_activation_time[index] + self._params.deltah)) ): |
||
| 404 | self._link_weight3[index] = 0.0; |
||
| 405 | self._link_pulse_counter[index] = 0.0; |
||
| 406 | |||
| 407 | |||
| 408 | def __update_central_neurons(self, t, next_cn_membrane, next_cn_active_sodium, next_cn_inactive_sodium, next_cn_active_potassium): |
||
| 409 | """! |
||
| 410 | @brief Update of central neurons in line with new values of current in channels. |
||
| 411 | |||
| 412 | @param[in] t (doubles): Current time of simulation. |
||
| 413 | @param[in] next_membrane (list): New values of membrane potentials for central neurons. |
||
| 414 | @Param[in] next_active_sodium (list): New values of activation conductances of the sodium channels for central neurons. |
||
| 415 | @param[in] next_inactive_sodium (list): New values of inactivaton conductances of the sodium channels for central neurons. |
||
| 416 | @param[in] next_active_potassium (list): New values of activation conductances of the potassium channel for central neurons. |
||
| 417 | |||
| 418 | """ |
||
| 419 | |||
| 420 | for index in range(0, len(self._central_element)): |
||
| 421 | self._central_element[index].membrane_potential = next_cn_membrane[index]; |
||
| 422 | self._central_element[index].active_cond_sodium = next_cn_active_sodium[index]; |
||
| 423 | self._central_element[index].inactive_cond_sodium = next_cn_inactive_sodium[index]; |
||
| 424 | self._central_element[index].active_cond_potassium = next_cn_active_potassium[index]; |
||
| 425 | |||
| 426 | if (self._central_element[index].pulse_generation is False): |
||
| 427 | if (self._central_element[index].membrane_potential > 0.0): |
||
| 428 | self._central_element[index].pulse_generation = True; |
||
| 429 | self._central_element[index].pulse_generation_time.append(t); |
||
| 430 | else: |
||
| 431 | if (self._central_element[index].membrane_potential < 0.0): |
||
| 432 | self._central_element[index].pulse_generation = False; |
||
| 433 | |||
| 434 | |||
| 435 | def hnn_state(self, inputs, t, argv): |
||
| 436 | """! |
||
| 437 | @brief Returns new values of excitatory and inhibitory parts of oscillator and potential of oscillator. |
||
| 438 | |||
| 439 | @param[in] inputs (list): States of oscillator for integration [v, m, h, n] (see description below). |
||
| 440 | @param[in] t (double): Current time of simulation. |
||
| 441 | @param[in] argv (tuple): Extra arguments that are not used for integration - index of oscillator. |
||
| 442 | |||
| 443 | @return (list) new values of oscillator [v, m, h, n], where: |
||
| 444 | v - membrane potantial of oscillator, |
||
| 445 | m - activation conductance of the sodium channel, |
||
| 446 | h - inactication conductance of the sodium channel, |
||
| 447 | n - activation conductance of the potassium channel. |
||
| 448 | |||
| 449 | """ |
||
| 450 | |||
| 451 | index = argv; |
||
| 452 | |||
| 453 | v = inputs[0]; # membrane potential (v). |
||
| 454 | m = inputs[1]; # activation conductance of the sodium channel (m). |
||
| 455 | h = inputs[2]; # inactivaton conductance of the sodium channel (h). |
||
| 456 | n = inputs[3]; # activation conductance of the potassium channel (n). |
||
| 457 | |||
| 458 | # Calculate ion current |
||
| 459 | # gNa * m[i]^3 * h * (v[i] - vNa) + gK * n[i]^4 * (v[i] - vK) + gL (v[i] - vL) |
||
| 460 | active_sodium_part = self._params.gNa * (m ** 3) * h * (v - self._params.vNa); |
||
| 461 | inactive_sodium_part = self._params.gK * (n ** 4) * (v - self._params.vK); |
||
| 462 | active_potassium_part = self._params.gL * (v - self._params.vL); |
||
| 463 | |||
| 464 | Iion = active_sodium_part + inactive_sodium_part + active_potassium_part; |
||
| 465 | |||
| 466 | Iext = 0.0; |
||
| 467 | Isyn = 0.0; |
||
| 468 | if (index < self._num_osc): |
||
| 469 | # PN - peripheral neuron - calculation of external current and synaptic current. |
||
| 470 | Iext = self._stimulus[index] * self._noise[index]; # probably noise can be pre-defined for reducting compexity |
||
| 471 | |||
| 472 | memory_impact1 = 0.0; |
||
| 473 | for i in range(0, len(self._central_element[0].pulse_generation_time)): |
||
| 474 | # TODO: alfa function shouldn't be calculated here (long procedure) |
||
| 475 | memory_impact1 += self.__alfa_function(t - self._central_element[0].pulse_generation_time[i], self._params.alfa_inhibitory, self._params.betta_inhibitory); |
||
| 476 | |||
| 477 | memory_impact2 = 0.0; |
||
| 478 | for i in range(0, len(self._central_element[1].pulse_generation_time)): |
||
| 479 | # TODO: alfa function shouldn't be calculated here (long procedure) |
||
| 480 | memory_impact2 += self.__alfa_function(t - self._central_element[1].pulse_generation_time[i], self._params.alfa_inhibitory, self._params.betta_inhibitory); |
||
| 481 | |||
| 482 | Isyn = self._params.w2 * (v - self._params.Vsyninh) * memory_impact1 + self._link_weight3[index] * (v - self._params.Vsyninh) * memory_impact2; |
||
| 483 | else: |
||
| 484 | # CN - central element. |
||
| 485 | central_index = index - self._num_osc; |
||
| 486 | if (central_index == 0): |
||
| 487 | Iext = self._params.Icn1; # CN1 |
||
| 488 | |||
| 489 | memory_impact = 0.0; |
||
| 490 | for index_oscillator in range(0, self._num_osc): |
||
| 491 | for index_generation in range(0, len(self._pulse_generation_time[index_oscillator])): |
||
| 492 | # TODO: alfa function shouldn't be calculated here (long procedure) |
||
| 493 | memory_impact += self.__alfa_function(t - self._pulse_generation_time[index_oscillator][index_generation], self._params.alfa_excitatory, self._params.betta_excitatory); |
||
| 494 | |||
| 495 | Isyn = self._params.w1 * (v - self._params.Vsynexc) * memory_impact; |
||
| 496 | |||
| 497 | elif (central_index == 1): |
||
| 498 | Iext = self._params.Icn2; # CN2 |
||
| 499 | Isyn = 0.0; |
||
| 500 | |||
| 501 | else: |
||
| 502 | assert 0; |
||
| 503 | |||
| 504 | |||
| 505 | # Membrane potential |
||
| 506 | dv = -Iion + Iext - Isyn; |
||
| 507 | |||
| 508 | # Calculate variables |
||
| 509 | potential = v - self._params.vRest; |
||
| 510 | am = (2.5 - 0.1 * potential) / (math.exp(2.5 - 0.1 * potential) - 1.0); |
||
| 511 | ah = 0.07 * math.exp(-potential / 20.0); |
||
| 512 | an = (0.1 - 0.01 * potential) / (math.exp(1.0 - 0.1 * potential) - 1.0); |
||
| 513 | |||
| 514 | bm = 4.0 * math.exp(-potential / 18.0); |
||
| 515 | bh = 1.0 / (math.exp(3.0 - 0.1 * potential) + 1.0); |
||
| 516 | bn = 0.125 * math.exp(-potential / 80.0); |
||
| 517 | |||
| 518 | dm = am * (1.0 - m) - bm * m; |
||
| 519 | dh = ah * (1.0 - h) - bh * h; |
||
| 520 | dn = an * (1.0 - n) - bn * n; |
||
| 521 | |||
| 522 | return [dv, dm, dh, dn]; |
||
| 523 | |||
| 524 | |||
| 525 | def allocate_sync_ensembles(self, tolerance = 0.1): |
||
| 526 | """! |
||
| 527 | @brief Allocates clusters in line with ensembles of synchronous oscillators where each. Synchronous ensemble corresponds to only one cluster. |
||
| 528 | |||
| 529 | @param[in] tolerance (double): maximum error for allocation of synchronous ensemble oscillators. |
||
| 530 | |||
| 531 | @return (list) Grours (lists) of indexes of synchronous oscillators. For example [ [index_osc1, index_osc3], [index_osc2], [index_osc4, index_osc5] ]. |
||
| 532 | |||
| 533 | """ |
||
| 534 | |||
| 535 | ignore = set(); |
||
| 536 | |||
| 537 | ignore.add(self._num_osc); |
||
| 538 | ignore.add(self._num_osc + 1); |
||
| 539 | |||
| 540 | return allocate_sync_ensembles(self._membrane_dynamic_pointer, tolerance, 20.0, ignore); |
||
| 541 | |||
| 542 | |||
| 543 | def __alfa_function(self, time, alfa, betta): |
||
| 544 | """! |
||
| 545 | @brief Calculates value of alfa-function for difference between spike generation time and current simulation time. |
||
| 546 | |||
| 547 | @param[in] time (double): Difference between last spike generation time and current time. |
||
| 548 | @param[in] alfa (double): Alfa parameter for alfa-function. |
||
| 549 | @param[in] betta (double): Betta parameter for alfa-function. |
||
| 550 | |||
| 551 | @return (double) Value of alfa-function. |
||
| 552 | |||
| 553 | """ |
||
| 554 | |||
| 555 | return alfa * time * math.exp(-betta * time); |
||
| 556 |