legion.py
1 """!
2 
3 @brief Neural Network: Local Excitatory Global Inhibitory Oscillatory Network (LEGION)
4 @details Implementation based on paper @cite article::legion::1, @cite article::legion::2.
5 
6 @authors Andrei Novikov (pyclustering@yandex.ru)
7 @date 2014-2019
8 @copyright GNU Public License
9 
10 @cond GNU_PUBLIC_LICENSE
11  PyClustering is free software: you can redistribute it and/or modify
12  it under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  PyClustering is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  GNU General Public License for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program. If not, see <http://www.gnu.org/licenses/>.
23 @endcond
24 
25 """
26 
27 import numpy
28 import random
29 
30 import pyclustering.core.legion_wrapper as wrapper
31 
32 from pyclustering.core.wrapper import ccore_library
33 
34 from pyclustering.nnet import *
35 
36 from pyclustering.utils import heaviside, allocate_sync_ensembles
37 
38 from scipy.integrate import odeint
39 
40 
42  """!
43  @brief Describes parameters of LEGION.
44  @details Contained parameters affect on output dynamic of each oscillator of the network.
45 
46  @see legion_network
47 
48  """
49 
50  def __init__(self):
51  """!
52  @brief Default constructor of parameters for LEGION (local excitatory global inhibitory oscillatory network).
53  @details Constructor initializes parameters by default non-zero values that can be
54  used for simple simulation.
55  """
56 
57 
58  self.eps = 0.02;
59 
60 
61  self.alpha = 0.005;
62 
63 
64  self.gamma = 6.0;
65 
66 
67  self.betta = 0.1;
68 
69 
70  self.lamda = 0.1;
71 
72 
73  self.teta = 0.9;
74 
75 
76  self.teta_x = -1.5;
77 
78 
79  self.teta_p = 1.5;
80 
81 
82  self.teta_xz = 0.1;
83 
84 
85  self.teta_zx = 0.1;
86 
87 
88  self.T = 2.0;
89 
90 
91  self.mu = 0.01;
92 
93 
94  self.Wz = 1.5;
95 
96 
97  self.Wt = 8.0;
98 
99 
100  self.fi = 3.0;
101 
102 
103  self.ro = 0.02;
104 
105 
106  self.I = 0.2;
107 
108 
109  self.ENABLE_POTENTIONAL = True;
110 
111 
113  """!
114  @brief Represents output dynamic of LEGION.
115 
116  """
117 
118  @property
119  def output(self):
120  """!
121  @brief Returns output dynamic of the network.
122 
123  """
124  if (self.__ccore_legion_dynamic_pointer is not None):
125  return wrapper.legion_dynamic_get_output(self.__ccore_legion_dynamic_pointer);
126 
127  return self.__output;
128 
129 
130  @property
131  def inhibitor(self):
132  """!
133  @brief Returns output dynamic of the global inhibitor of the network.
134 
135  """
136 
137  if (self.__ccore_legion_dynamic_pointer is not None):
138  return wrapper.legion_dynamic_get_inhibitory_output(self.__ccore_legion_dynamic_pointer);
139 
140  return self.__inhibitor;
141 
142 
143  @property
144  def time(self):
145  """!
146  @brief Returns simulation time.
147 
148  """
149  if (self.__ccore_legion_dynamic_pointer is not None):
150  return wrapper.legion_dynamic_get_time(self.__ccore_legion_dynamic_pointer);
151 
152  return list(range(len(self)));
153 
154 
155  def __init__(self, output, inhibitor, time, ccore = None):
156  """!
157  @brief Constructor of legion dynamic.
158 
159  @param[in] output (list): Output dynamic of the network represented by excitatory values of oscillators.
160  @param[in] inhibitor (list): Output dynamic of the global inhibitor of the network.
161  @param[in] time (list): Simulation time.
162  @param[in] ccore (POINTER): Pointer to CCORE legion_dynamic. If it is specified then others arguments can be omitted.
163 
164  """
165 
166  self.__output = output;
167  self.__inhibitor = inhibitor;
168  self._time = time;
169 
170  self.__ccore_legion_dynamic_pointer = ccore;
171 
172 
173  def __del__(self):
174  """!
175  @brief Destructor of the dynamic of the legion network.
176 
177  """
178  if (self.__ccore_legion_dynamic_pointer is not None):
179  wrapper.legion_dynamic_destroy(self.__ccore_legion_dynamic_pointer);
180 
181 
182  def __len__(self):
183  """!
184  @brief Returns length of output dynamic.
185 
186  """
187  if (self.__ccore_legion_dynamic_pointer is not None):
188  return wrapper.legion_dynamic_get_size(self.__ccore_legion_dynamic_pointer);
189 
190  return len(self._time);
191 
192 
193  def allocate_sync_ensembles(self, tolerance = 0.1):
194  """!
195  @brief Allocate clusters in line with ensembles of synchronous oscillators where each synchronous ensemble corresponds to only one cluster.
196 
197  @param[in] tolerance (double): Maximum error for allocation of synchronous ensemble oscillators.
198 
199  @return (list) Grours of indexes of synchronous oscillators, for example, [ [index_osc1, index_osc3], [index_osc2], [index_osc4, index_osc5] ].
200 
201  """
202 
203  if (self.__ccore_legion_dynamic_pointer is not None):
204  self.__output = wrapper.legion_dynamic_get_output(self.__ccore_legion_dynamic_pointer);
205 
206  return allocate_sync_ensembles(self.__output, tolerance);
207 
208 
210  """!
211  @brief Local excitatory global inhibitory oscillatory network (LEGION) that uses relaxation oscillator
212  based on Van der Pol model.
213 
214  @details The model uses global inhibitor to de-synchronize synchronous ensembles of oscillators.
215 
216  CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
217 
218  Example:
219  @code
220  # Create parameters of the network
221  parameters = legion_parameters();
222  parameters.Wt = 4.0;
223 
224  # Create stimulus
225  stimulus = [1, 1, 0, 0, 0, 1, 1, 1];
226 
227  # Create the network (use CCORE for fast solving)
228  net = legion_network(len(stimulus), parameters, conn_type.GRID_FOUR, ccore = True);
229 
230  # Simulate network - result of simulation is output dynamic of the network
231  output_dynamic = net.simulate(1000, 750, stimulus);
232 
233  # Draw output dynamic
234  draw_dynamics(output_dynamic.time, output_dynamic.output, x_title = "Time", y_title = "x(t)");
235  @endcode
236 
237  """
238 
239  def __init__(self, num_osc, parameters = None, type_conn = conn_type.ALL_TO_ALL, type_conn_represent = conn_represent.MATRIX, ccore = True):
240  """!
241  @brief Constructor of oscillatory network LEGION (local excitatory global inhibitory oscillatory network).
242 
243  @param[in] num_osc (uint): Number of oscillators in the network.
244  @param[in] parameters (legion_parameters): Parameters of the network that are defined by structure 'legion_parameters'.
245  @param[in] type_conn (conn_type): Type of connection between oscillators in the network.
246  @param[in] type_conn_represent (conn_represent): Internal representation of connection in the network: matrix or list.
247  @param[in] ccore (bool): If True then all interaction with object will be performed via CCORE library (C++ implementation of pyclustering).
248 
249  """
250 
251  self._params = None; # parameters of the network
252 
253  self.__ccore_legion_pointer = None;
254  self._params = parameters;
255 
256  # set parameters of the network
257  if (self._params is None):
258  self._params = legion_parameters();
259 
260  if ( (ccore is True) and ccore_library.workable() ):
261  self.__ccore_legion_pointer = wrapper.legion_create(num_osc, type_conn, self._params);
262 
263  else:
264  super().__init__(num_osc, type_conn, type_conn_represent);
265 
266  # initial states
267  self._excitatory = [ random.random() for _ in range(self._num_osc) ];
268  self._inhibitory = [0.0] * self._num_osc;
269  self._potential = [0.0] * self._num_osc;
270 
271  self._coupling_term = None; # coupling term of each oscillator
272  self._global_inhibitor = 0; # value of global inhibitory
273  self._stimulus = None; # stimulus of each oscillator
274 
275  self._dynamic_coupling = None; # dynamic connection between oscillators
276  self._coupling_term = [0.0] * self._num_osc;
277  self._buffer_coupling_term = [0.0] * self._num_osc;
278 
279  # generate first noises
280  self._noise = [random.random() * self._params.ro for i in range(self._num_osc)];
281 
282 
283  def __del__(self):
284  """!
285  @brief Default destructor of LEGION.
286 
287  """
288  if (self.__ccore_legion_pointer is not None):
289  wrapper.legion_destroy(self.__ccore_legion_pointer);
290  self.__ccore_legion_pointer = None;
291 
292 
293  def __len__(self):
294  """!
295  @brief (uint) Returns size of LEGION.
296 
297  """
298 
299  if (self.__ccore_legion_pointer is not None):
300  return wrapper.legion_get_size(self.__ccore_legion_pointer);
301 
302  return self._num_osc;
303 
304 
305  def __create_stimulus(self, stimulus):
306  """!
307  @brief Create stimulus for oscillators in line with stimulus map and parameters.
308 
309  @param[in] stimulus (list): Stimulus for oscillators that is represented by list, number of stimulus should be equal number of oscillators.
310 
311  """
312 
313  if (len(stimulus) != self._num_osc):
314  raise NameError("Number of stimulus should be equal number of oscillators in the network.");
315  else:
316  self._stimulus = [];
317 
318  for val in stimulus:
319  if (val > 0): self._stimulus.append(self._params.I);
320  else: self._stimulus.append(0);
321 
322 
323  def __create_dynamic_connections(self):
324  """!
325  @brief Create dynamic connection in line with input stimulus.
326 
327  """
328 
329  if (self._stimulus is None):
330  raise NameError("Stimulus should initialed before creation of the dynamic connections in the network.");
331 
332  self._dynamic_coupling = [ [0] * self._num_osc for i in range(self._num_osc)];
333 
334  for i in range(self._num_osc):
335  neighbors = self.get_neighbors(i);
336 
337  if ( (len(neighbors) > 0) and (self._stimulus[i] > 0) ):
338  number_stimulated_neighbors = 0.0;
339  for j in neighbors:
340  if (self._stimulus[j] > 0):
341  number_stimulated_neighbors += 1.0;
342 
343  if (number_stimulated_neighbors > 0):
344  dynamic_weight = self._params.Wt / number_stimulated_neighbors;
345 
346  for j in neighbors:
347  self._dynamic_coupling[i][j] = dynamic_weight;
348 
349 
350  def simulate(self, steps, time, stimulus, solution = solve_type.RK4, collect_dynamic = True):
351  """!
352  @brief Performs static simulation of LEGION oscillatory network.
353 
354  @param[in] steps (uint): Number steps of simulations during simulation.
355  @param[in] time (double): Time of simulation.
356  @param[in] stimulus (list): Stimulus for oscillators, number of stimulus should be equal to number of oscillators,
357  example of stimulus for 5 oscillators [0, 0, 1, 1, 0], value of stimulus is defined by parameter 'I'.
358  @param[in] solution (solve_type): Method that is used for differential equation.
359  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
360 
361  @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
362  otherwise returns only last values (last step of simulation) of dynamic.
363 
364  """
365 
366  if (self.__ccore_legion_pointer is not None):
367  pointer_dynamic = wrapper.legion_simulate(self.__ccore_legion_pointer, steps, time, solution, collect_dynamic, stimulus);
368  return legion_dynamic(None, None, None, pointer_dynamic);
369 
370  # Check solver before simulation
371  if (solution == solve_type.FAST):
372  raise NameError("Solver FAST is not support due to low accuracy that leads to huge error.");
373 
374  elif (solution == solve_type.RKF45):
375  raise NameError("Solver RKF45 is not support in python version. RKF45 is supported in CCORE implementation.");
376 
377  # set stimulus
378  self.__create_stimulus(stimulus);
379 
380  # calculate dynamic weights
382 
383  dyn_exc = None;
384  dyn_time = None;
385  dyn_ginh = None;
386 
387  # Store only excitatory of the oscillator
388  if (collect_dynamic == True):
389  dyn_exc = [];
390  dyn_time = [];
391  dyn_ginh = [];
392 
393  step = time / steps;
394  int_step = step / 10.0;
395 
396  for t in numpy.arange(step, time + step, step):
397  # update states of oscillators
398  self._calculate_states(solution, t, step, int_step);
399 
400  # update states of oscillators
401  if (collect_dynamic == True):
402  dyn_exc.append(self._excitatory);
403  dyn_time.append(t);
404  dyn_ginh.append(self._global_inhibitor);
405  else:
406  dyn_exc = self._excitatory;
407  dyn_time = t;
408  dyn_ginh = self._global_inhibitor;
409 
410  return legion_dynamic(dyn_exc, dyn_ginh, dyn_time);
411 
412 
413  def _calculate_states(self, solution, t, step, int_step):
414  """!
415  @brief Calculates new state of each oscillator in the network.
416 
417  @param[in] solution (solve_type): Type solver of the differential equation.
418  @param[in] t (double): Current time of simulation.
419  @param[in] step (double): Step of solution at the end of which states of oscillators should be calculated.
420  @param[in] int_step (double): Step differentiation that is used for solving differential equation.
421 
422  """
423 
424  next_excitatory = [0.0] * self._num_osc;
425  next_inhibitory = [0.0] * self._num_osc;
426 
427  next_potential = [];
428  if (self._params.ENABLE_POTENTIONAL is True):
429  next_potential = [0.0] * self._num_osc;
430 
431  # Update states of oscillators
432  for index in range (0, self._num_osc, 1):
433  if (self._params.ENABLE_POTENTIONAL is True):
434  result = odeint(self._legion_state, [self._excitatory[index], self._inhibitory[index], self._potential[index]], numpy.arange(t - step, t, int_step), (index , ));
435  [ next_excitatory[index], next_inhibitory[index], next_potential[index] ] = result[len(result) - 1][0:3];
436 
437  else:
438  result = odeint(self._legion_state_simplify, [self._excitatory[index], self._inhibitory[index] ], numpy.arange(t - step, t, int_step), (index , ));
439  [ next_excitatory[index], next_inhibitory[index] ] = result[len(result) - 1][0:2];
440 
441  # Update coupling term
442  neighbors = self.get_neighbors(index);
443 
444  coupling = 0
445  for index_neighbor in neighbors:
446  coupling += self._dynamic_coupling[index][index_neighbor] * heaviside(self._excitatory[index_neighbor] - self._params.teta_x);
447 
448  self._buffer_coupling_term[index] = coupling - self._params.Wz * heaviside(self._global_inhibitor - self._params.teta_xz);
449 
450  # Update state of global inhibitory
451  result = odeint(self._global_inhibitor_state, self._global_inhibitor, numpy.arange(t - step, t, int_step), (None, ));
452  self._global_inhibitor = result[len(result) - 1][0];
453 
454  self._noise = [random.random() * self._params.ro for i in range(self._num_osc)];
455  self._coupling_term = self._buffer_coupling_term[:];
456  self._inhibitory = next_inhibitory[:];
457  self._excitatory = next_excitatory[:];
458 
459  if (self._params.ENABLE_POTENTIONAL is True):
460  self._potential = next_potential[:];
461 
462 
463 
464  def _global_inhibitor_state(self, z, t, argv):
465  """!
466  @brief Returns new value of global inhibitory
467 
468  @param[in] z (dobule): Current value of inhibitory.
469  @param[in] t (double): Current time of simulation.
470  @param[in] argv (tuple): It's not used, can be ignored.
471 
472  @return (double) New value if global inhibitory (not assign).
473 
474  """
475 
476  sigma = 0.0;
477 
478  for x in self._excitatory:
479  if (x > self._params.teta_zx):
480  sigma = 1.0;
481  break;
482 
483  return self._params.fi * (sigma - z);
484 
485 
486  def _legion_state_simplify(self, inputs, t, argv):
487  """!
488  @brief Returns new values of excitatory and inhibitory parts of oscillator of oscillator.
489  @details Simplify model doesn't consider oscillator potential.
490 
491  @param[in] inputs (list): Initial values (current) of oscillator [excitatory, inhibitory].
492  @param[in] t (double): Current time of simulation.
493  @param[in] argv (uint): Extra arguments that are not used for integration - index of oscillator.
494 
495  @return (list) New values of excitatoty and inhibitory part of oscillator (not assign).
496 
497  """
498 
499  index = argv;
500 
501  x = inputs[0]; # excitatory
502  y = inputs[1]; # inhibitory
503 
504  dx = 3.0 * x - x ** 3.0 + 2.0 - y + self._stimulus[index] + self._coupling_term[index] + self._noise[index];
505  dy = self._params.eps * (self._params.gamma * (1.0 + math.tanh(x / self._params.betta)) - y);
506 
507  neighbors = self.get_neighbors(index);
508  potential = 0.0;
509 
510  for index_neighbor in neighbors:
511  potential += self._params.T * heaviside(self._excitatory[index_neighbor] - self._params.teta_x);
512 
513  return [dx, dy];
514 
515 
516  def _legion_state(self, inputs, t, argv):
517  """!
518  @brief Returns new values of excitatory and inhibitory parts of oscillator and potential of oscillator.
519 
520  @param[in] inputs (list): Initial values (current) of oscillator [excitatory, inhibitory, potential].
521  @param[in] t (double): Current time of simulation.
522  @param[in] argv (uint): Extra arguments that are not used for integration - index of oscillator.
523 
524  @return (list) New values of excitatoty and inhibitory part of oscillator and new value of potential (not assign).
525 
526  """
527 
528  index = argv;
529 
530  x = inputs[0]; # excitatory
531  y = inputs[1]; # inhibitory
532  p = inputs[2]; # potential
533 
534  potential_influence = heaviside(p + math.exp(-self._params.alpha * t) - self._params.teta);
535 
536  dx = 3.0 * x - x ** 3.0 + 2.0 - y + self._stimulus[index] * potential_influence + self._coupling_term[index] + self._noise[index];
537  dy = self._params.eps * (self._params.gamma * (1.0 + math.tanh(x / self._params.betta)) - y);
538 
539  neighbors = self.get_neighbors(index);
540  potential = 0.0;
541 
542  for index_neighbor in neighbors:
543  potential += self._params.T * heaviside(self._excitatory[index_neighbor] - self._params.teta_x);
544 
545  dp = self._params.lamda * (1.0 - p) * heaviside(potential - self._params.teta_p) - self._params.mu * p;
546 
547  return [dx, dy, dp];
I
Value of external stimulus.
Definition: legion.py:106
def __init__(self)
Default constructor of parameters for LEGION (local excitatory global inhibitory oscillatory network)...
Definition: legion.py:50
teta
Threshold that should be exceeded by a potential to switch on potential.
Definition: legion.py:73
Local excitatory global inhibitory oscillatory network (LEGION) that uses relaxation oscillator based...
Definition: legion.py:209
def __create_dynamic_connections(self)
Create dynamic connection in line with input stimulus.
Definition: legion.py:323
betta
Coefficient that affects on intrinsic inhibitor of each oscillator.
Definition: legion.py:67
def allocate_sync_ensembles(self, tolerance=0.1)
Allocate clusters in line with ensembles of synchronous oscillators where each synchronous ensemble c...
Definition: legion.py:193
gamma
Coefficient that is used to control the ratio of the times that the solution spends in these two phas...
Definition: legion.py:64
def __len__(self)
Returns length of output dynamic.
Definition: legion.py:182
def simulate(self, steps, time, stimulus, solution=solve_type.RK4, collect_dynamic=True)
Performs static simulation of LEGION oscillatory network.
Definition: legion.py:350
def __create_stimulus(self, stimulus)
Create stimulus for oscillators in line with stimulus map and parameters.
Definition: legion.py:305
Utils that are used by modules of pyclustering.
Definition: __init__.py:1
teta_xz
Threshold that should be exceeded by any oscillator to activate global inhibitor. ...
Definition: legion.py:82
def inhibitor(self)
Returns output dynamic of the global inhibitor of the network.
Definition: legion.py:131
lamda
Scale coefficient that is used by potential, should be greater than 0.
Definition: legion.py:70
Describes parameters of LEGION.
Definition: legion.py:41
def __init__(self, num_osc, parameters=None, type_conn=conn_type.ALL_TO_ALL, type_conn_represent=conn_represent.MATRIX, ccore=True)
Constructor of oscillatory network LEGION (local excitatory global inhibitory oscillatory network)...
Definition: legion.py:239
def __init__(self, output, inhibitor, time, ccore=None)
Constructor of legion dynamic.
Definition: legion.py:155
ro
Multiplier of oscillator noise.
Definition: legion.py:103
def get_neighbors(self, index)
Finds neighbors of the oscillator with specified index.
Definition: __init__.py:409
def __del__(self)
Destructor of the dynamic of the legion network.
Definition: legion.py:173
ENABLE_POTENTIONAL
Defines whether to use potentional of oscillator or not.
Definition: legion.py:109
def __del__(self)
Default destructor of LEGION.
Definition: legion.py:283
Represents output dynamic of LEGION.
Definition: legion.py:112
Common network description that consists of information about oscillators and connection between them...
Definition: __init__.py:97
def time(self)
Returns simulation time.
Definition: legion.py:144
teta_zx
Threshold that should be exceeded to affect on a oscillator by the global inhibitor.
Definition: legion.py:85
def _global_inhibitor_state(self, z, t, argv)
Returns new value of global inhibitory.
Definition: legion.py:464
Wt
Total dynamic weights to a single oscillator from neighbors.
Definition: legion.py:97
Wz
Weight of global inhibitory connections.
Definition: legion.py:94
def output(self)
Returns output dynamic of the network.
Definition: legion.py:119
def _legion_state(self, inputs, t, argv)
Returns new values of excitatory and inhibitory parts of oscillator and potential of oscillator...
Definition: legion.py:516
def __len__(self)
(uint) Returns size of LEGION.
Definition: legion.py:293
alpha
Coefficient is chosen to be on the same order of magnitude as &#39;eps&#39;.
Definition: legion.py:61
def _calculate_states(self, solution, t, step, int_step)
Calculates new state of each oscillator in the network.
Definition: legion.py:413
fi
Rate at which the global inhibitor reacts to the stimulation from the oscillator network.
Definition: legion.py:100
teta_p
Threshold that should be exceeded to activate potential.
Definition: legion.py:79
mu
Defines time scaling of relaxing of oscillator potential.
Definition: legion.py:91
eps
Coefficient that affects intrinsic inhibitor of each oscillator.
Definition: legion.py:58
teta_x
Threshold that should be exceeded by a single oscillator to affect its neighbors. ...
Definition: legion.py:76
T
Weight of permanent connections.
Definition: legion.py:88
def _legion_state_simplify(self, inputs, t, argv)
Returns new values of excitatory and inhibitory parts of oscillator of oscillator.
Definition: legion.py:486
Neural and oscillatory network module.
Definition: __init__.py:1