syncpr.py
1 """!
2 
3 @brief Phase oscillatory network for patten recognition based on modified Kuramoto model.
4 @details Implementation based on paper @cite article::nnet::syncpr::1.
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 math
28 import cmath
29 import numpy
30 import warnings
31 
32 from pyclustering.nnet import solve_type, initial_type, conn_type,conn_represent
33 from pyclustering.nnet.sync import sync_network, sync_dynamic, sync_visualizer
34 
35 import pyclustering.core.syncpr_wrapper as wrapper
36 
37 from pyclustering.core.wrapper import ccore_library
38 
39 try:
40  from PIL import Image
41 except Exception as error_instance:
42  warnings.warn("Impossible to import PIL (please, install 'PIL'), pyclustering's visualization "
43  "functionality is partially not available (details: '%s')." % str(error_instance))
44 
45 try:
46  import matplotlib.pyplot as plt
47  import matplotlib.animation as animation
48 except Exception as error_instance:
49  warnings.warn("Impossible to import matplotlib (please, install 'matplotlib'), pyclustering's visualization "
50  "functionality is not available (details: '%s')." % str(error_instance))
51 
52 
54  """!
55  @brief Represents output dynamic of syncpr (Sync for Pattern Recognition).
56 
57  """
58 
59  def __init__(self, phase, time, ccore):
60  """!
61  @brief Constructor of syncpr dynamic.
62 
63  @param[in] phase (list): Dynamic of oscillators on each step of simulation. If ccore pointer is specified than it can be ignored.
64  @param[in] time (list): Simulation time.
65  @param[in] ccore (ctypes.pointer): Pointer to CCORE sync_dynamic instance in memory.
66 
67  """
68  super().__init__(phase, time, ccore);
69 
70 
72  """!
73  @brief Visualizer of output dynamic of syncpr network (Sync for Pattern Recognition).
74 
75  """
76 
77  @staticmethod
78  def show_pattern(syncpr_output_dynamic, image_height, image_width):
79  """!
80  @brief Displays evolution of phase oscillators as set of patterns where the last one means final result of recognition.
81 
82  @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
83  @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
84  @param[in] image_width (uint): Width of the pattern.
85 
86  """
87  number_pictures = len(syncpr_output_dynamic);
88  iteration_math_step = 1.0;
89  if (number_pictures > 50):
90  iteration_math_step = number_pictures / 50.0;
91  number_pictures = 50;
92 
93  number_cols = int(numpy.ceil(number_pictures ** 0.5));
94  number_rows = int(numpy.ceil(number_pictures / number_cols));
95 
96  real_index = 0, 0;
97  double_indexer = True;
98  if ( (number_cols == 1) or (number_rows == 1) ):
99  real_index = 0;
100  double_indexer = False;
101 
102  (_, axarr) = plt.subplots(number_rows, number_cols);
103 
104  if (number_pictures > 1):
105  plt.setp([ax for ax in axarr], visible = False);
106 
107  iteration_display = 0.0;
108  for iteration in range(len(syncpr_output_dynamic)):
109  if (iteration >= iteration_display):
110  iteration_display += iteration_math_step;
111 
112  ax_handle = axarr;
113  if (number_pictures > 1):
114  ax_handle = axarr[real_index];
115 
116  syncpr_visualizer.__show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration);
117 
118  if (double_indexer is True):
119  real_index = real_index[0], real_index[1] + 1;
120  if (real_index[1] >= number_cols):
121  real_index = real_index[0] + 1, 0;
122  else:
123  real_index += 1;
124 
125  plt.show();
126 
127 
128  @staticmethod
129  def animate_pattern_recognition(syncpr_output_dynamic, image_height, image_width, animation_velocity = 75, title = None, save_movie = None):
130  """!
131  @brief Shows animation of pattern recognition process that has been preformed by the oscillatory network.
132 
133  @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
134  @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
135  @param[in] image_width (uint): Width of the pattern.
136  @param[in] animation_velocity (uint): Interval between frames in milliseconds.
137  @param[in] title (string): Title of the animation that is displayed on a figure if it is specified.
138  @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
139 
140  """
141  figure = plt.figure();
142 
143  def init_frame():
144  return frame_generation(0);
145 
146  def frame_generation(index_dynamic):
147  figure.clf();
148 
149  if (title is not None):
150  figure.suptitle(title, fontsize = 26, fontweight = 'bold')
151 
152  ax1 = figure.add_subplot(121, projection='polar');
153  ax2 = figure.add_subplot(122);
154 
155  dynamic = syncpr_output_dynamic.output[index_dynamic];
156 
157  artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
158  artist2 = syncpr_visualizer.__show_pattern(ax2, syncpr_output_dynamic, image_height, image_width, index_dynamic);
159 
160  return [ artist1, artist2 ];
161 
162  cluster_animation = animation.FuncAnimation(figure, frame_generation, len(syncpr_output_dynamic), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
163 
164  if (save_movie is not None):
165 # plt.rcParams['animation.ffmpeg_path'] = 'C:\\Users\\annoviko\\programs\\ffmpeg-win64-static\\bin\\ffmpeg.exe';
166 # ffmpeg_writer = animation.FFMpegWriter();
167 # cluster_animation.save(save_movie, writer = ffmpeg_writer, fps = 15);
168  cluster_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
169  else:
170  plt.show();
171 
172 
173  @staticmethod
174  def __show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration):
175  """!
176  @brief Draws pattern on specified ax.
177 
178  @param[in] ax_handle (Axis): Axis where pattern should be drawn.
179  @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
180  @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
181  @param[in] image_width (uint): Width of the pattern.
182  @param[in] iteration (uint): Simulation iteration that should be used for extracting pattern.
183 
184  @return (matplotlib.artist) Artist (pattern) that is rendered in the canvas.
185 
186  """
187 
188  current_dynamic = syncpr_output_dynamic.output[iteration];
189  stage_picture = [(255, 255, 255)] * (image_height * image_width);
190  for index_phase in range(len(current_dynamic)):
191  phase = current_dynamic[index_phase];
192 
193  pixel_color = math.floor( phase * (255 / (2 * math.pi)) );
194  stage_picture[index_phase] = (pixel_color, pixel_color, pixel_color);
195 
196  stage = numpy.array(stage_picture, numpy.uint8);
197  stage = numpy.reshape(stage, (image_height, image_width) + ((3),)); # ((3),) it's size of RGB - third dimension.
198 
199  image_cluster = Image.fromarray(stage);
200 
201  artist = ax_handle.imshow(image_cluster, interpolation = 'none');
202  plt.setp(ax_handle, visible = True);
203 
204  ax_handle.xaxis.set_ticklabels([]);
205  ax_handle.yaxis.set_ticklabels([]);
206  ax_handle.xaxis.set_ticks_position('none');
207  ax_handle.yaxis.set_ticks_position('none');
208 
209  return artist;
210 
211 
213  """!
214  @brief Model of phase oscillatory network for pattern recognition that is based on the Kuramoto model.
215  @details The model uses second-order and third-order modes of the Fourier components.
216 
217  CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
218 
219  Example:
220  @code
221  # Network size should be equal to size of pattern for learning.
222  net = syncpr(size_network, 0.3, 0.3);
223 
224  # Train network using list of patterns (input images).
225  net.train(image_samples);
226 
227  # Recognize image using 10 steps during 10 seconds of simulation.
228  sync_output_dynamic = net.simulate(10, 10, pattern, solve_type.RK4, True);
229 
230  # Display output dynamic.
231  syncpr_visualizer.show_output_dynamic(sync_output_dynamic);
232 
233  # Display evolution of recognition of the pattern.
234  syncpr_visualizer.show_pattern(sync_output_dynamic, image_height, image_width);
235 
236  @endcode
237 
238  """
239 
240  def __init__(self, num_osc, increase_strength1, increase_strength2, ccore = True):
241  """!
242  @brief Constructor of oscillatory network for pattern recognition based on Kuramoto model.
243 
244  @param[in] num_osc (uint): Number of oscillators in the network.
245  @param[in] increase_strength1 (double): Parameter for increasing strength of the second term of the Fourier component.
246  @param[in] increase_strength2 (double): Parameter for increasing strength of the third term of the Fourier component.
247  @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).
248 
249  """
250 
251  if ( (ccore is True) and ccore_library.workable() ):
252  self._ccore_network_pointer = wrapper.syncpr_create(num_osc, increase_strength1, increase_strength2);
253 
254  else:
255  self._increase_strength1 = increase_strength1;
256  self._increase_strength2 = increase_strength2;
257  self._coupling = [ [0.0 for i in range(num_osc)] for j in range(num_osc) ];
258 
259  super().__init__(num_osc, 1, 0, conn_type.ALL_TO_ALL, conn_represent.MATRIX, initial_type.RANDOM_GAUSSIAN, ccore)
260 
261 
262  def __del__(self):
263  """!
264  @brief Default destructor of syncpr.
265 
266  """
267 
268  if (self._ccore_network_pointer is not None):
269  wrapper.syncpr_destroy(self._ccore_network_pointer);
270  self._ccore_network_pointer = None;
271 
272 
273  def __len__(self):
274  """!
275  @brief Returns size of the network.
276 
277  """
278  if (self._ccore_network_pointer is not None):
279  return wrapper.syncpr_get_size(self._ccore_network_pointer);
280 
281  else:
282  return self._num_osc;
283 
284 
285  def train(self, samples):
286  """!
287  @brief Trains syncpr network using Hebbian rule for adjusting strength of connections between oscillators during training.
288 
289  @param[in] samples (list): list of patterns where each pattern is represented by list of features that are equal to [-1; 1].
290 
291  """
292 
293  # Verify pattern for learning
294  for pattern in samples:
295  self.__validate_pattern(pattern);
296 
297  if (self._ccore_network_pointer is not None):
298  return wrapper.syncpr_train(self._ccore_network_pointer, samples);
299 
300  length = len(self);
301  number_samples = len(samples);
302 
303  for i in range(length):
304  for j in range(i + 1, len(self), 1):
305 
306  # go through via all patterns
307  for p in range(number_samples):
308  value1 = samples[p][i];
309  value2 = samples[p][j];
310 
311  self._coupling[i][j] += value1 * value2;
312 
313  self._coupling[i][j] /= length;
314  self._coupling[j][i] = self._coupling[i][j];
315 
316 
317  def simulate(self, steps, time, pattern, solution = solve_type.RK4, collect_dynamic = True):
318  """!
319  @brief Performs static simulation of syncpr oscillatory network.
320  @details In other words network performs pattern recognition during simulation.
321 
322  @param[in] steps (uint): Number steps of simulations during simulation.
323  @param[in] time (double): Time of simulation.
324  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
325  @param[in] solution (solve_type): Type of solver that should be used for simulation.
326  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
327 
328  @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
329  otherwise returns only last values (last step of simulation) of dynamic.
330 
331  @see simulate_dynamic()
332  @see simulate_static()
333 
334  """
335 
336  return self.simulate_static(steps, time, pattern, solution, collect_dynamic);
337 
338 
339  def simulate_dynamic(self, pattern, order = 0.998, solution = solve_type.RK4, collect_dynamic = False, step = 0.1, int_step = 0.01, threshold_changes = 0.0000001):
340  """!
341  @brief Performs dynamic simulation of the network until stop condition is not reached.
342  @details In other words network performs pattern recognition during simulation.
343  Stop condition is defined by input argument 'order' that represents memory order, but
344  process of simulation can be stopped if convergance rate is low whose threshold is defined
345  by the argument 'threshold_changes'.
346 
347  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
348  @param[in] order (double): Order of process synchronization, distributed 0..1.
349  @param[in] solution (solve_type): Type of solution.
350  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
351  @param[in] step (double): Time step of one iteration of simulation.
352  @param[in] int_step (double): Integration step, should be less than step.
353  @param[in] threshold_changes (double): Additional stop condition that helps prevent infinite simulation, defines limit of changes of oscillators between current and previous steps.
354 
355  @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
356  otherwise returns only last values (last step of simulation) of dynamic.
357 
358  @see simulate()
359  @see simulate_static()
360 
361  """
362 
363  self.__validate_pattern(pattern);
364 
365  if (self._ccore_network_pointer is not None):
366  ccore_instance_dynamic = wrapper.syncpr_simulate_dynamic(self._ccore_network_pointer, pattern, order, solution, collect_dynamic, step);
367  return syncpr_dynamic(None, None, ccore_instance_dynamic);
368 
369  for i in range(0, len(pattern), 1):
370  if (pattern[i] > 0.0):
371  self._phases[i] = 0.0;
372  else:
373  self._phases[i] = math.pi / 2.0;
374 
375  # For statistics and integration
376  time_counter = 0;
377 
378  # Prevent infinite loop. It's possible when required state cannot be reached.
379  previous_order = 0;
380  current_order = self.__calculate_memory_order(pattern);
381 
382  # If requested input dynamics
383  dyn_phase = [];
384  dyn_time = [];
385  if (collect_dynamic == True):
386  dyn_phase.append(self._phases);
387  dyn_time.append(0);
388 
389  # Execute until sync state will be reached
390  while (current_order < order):
391  # update states of oscillators
392  self._phases = self._calculate_phases(solution, time_counter, step, int_step);
393 
394  # update time
395  time_counter += step;
396 
397  # if requested input dynamic
398  if (collect_dynamic == True):
399  dyn_phase.append(self._phases);
400  dyn_time.append(time_counter);
401 
402  # update orders
403  previous_order = current_order;
404  current_order = self.__calculate_memory_order(pattern);
405 
406  # hang prevention
407  if (abs(current_order - previous_order) < threshold_changes):
408  break;
409 
410  if (collect_dynamic != True):
411  dyn_phase.append(self._phases);
412  dyn_time.append(time_counter);
413 
414  output_sync_dynamic = syncpr_dynamic(dyn_phase, dyn_time, None);
415  return output_sync_dynamic;
416 
417 
418  def simulate_static(self, steps, time, pattern, solution = solve_type.FAST, collect_dynamic = False):
419  """!
420  @brief Performs static simulation of syncpr oscillatory network.
421  @details In other words network performs pattern recognition during simulation.
422 
423  @param[in] steps (uint): Number steps of simulations during simulation.
424  @param[in] time (double): Time of simulation.
425  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
426  @param[in] solution (solve_type): Type of solution.
427  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
428 
429  @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
430  otherwise returns only last values (last step of simulation) of dynamic.
431 
432  @see simulate()
433  @see simulate_dynamic()
434 
435  """
436 
437  self.__validate_pattern(pattern);
438 
439  if (self._ccore_network_pointer is not None):
440  ccore_instance_dynamic = wrapper.syncpr_simulate_static(self._ccore_network_pointer, steps, time, pattern, solution, collect_dynamic);
441  return syncpr_dynamic(None, None, ccore_instance_dynamic);
442 
443  for i in range(0, len(pattern), 1):
444  if (pattern[i] > 0.0):
445  self._phases[i] = 0.0;
446  else:
447  self._phases[i] = math.pi / 2.0;
448 
449  return super().simulate_static(steps, time, solution, collect_dynamic);
450 
451 
452  def memory_order(self, pattern):
453  """!
454  @brief Calculates function of the memorized pattern.
455  @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
456 
457  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
458 
459  @return (double) Order of memory for the specified pattern.
460 
461  """
462 
463  self.__validate_pattern(pattern);
464 
465  if (self._ccore_network_pointer is not None):
466  return wrapper.syncpr_memory_order(self._ccore_network_pointer, pattern);
467 
468  else:
469  return self.__calculate_memory_order(pattern);
470 
471 
472  def __calculate_memory_order(self, pattern):
473  """!
474  @brief Calculates function of the memorized pattern without any pattern validation.
475 
476  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
477 
478  @return (double) Order of memory for the specified pattern.
479 
480  """
481 
482  memory_order = 0.0;
483  for index in range(len(self)):
484  memory_order += pattern[index] * cmath.exp( 1j * self._phases[index] );
485 
486  memory_order /= len(self);
487  return abs(memory_order);
488 
489 
490  def _phase_kuramoto(self, teta, t, argv):
491  """!
492  @brief Returns result of phase calculation for specified oscillator in the network.
493 
494  @param[in] teta (double): Phase of the oscillator that is differentiated.
495  @param[in] t (double): Current time of simulation.
496  @param[in] argv (tuple): Index of the oscillator in the list.
497 
498  @return (double) New phase for specified oscillator (don't assign it here).
499 
500  """
501 
502  index = argv;
503 
504  phase = 0.0;
505  term = 0.0;
506 
507  for k in range(0, self._num_osc):
508  if (k != index):
509  phase_delta = self._phases[k] - teta;
510 
511  phase += self._coupling[index][k] * math.sin(phase_delta);
512 
513  term1 = self._increase_strength1 * math.sin(2.0 * phase_delta);
514  term2 = self._increase_strength2 * math.sin(3.0 * phase_delta);
515 
516  term += (term1 - term2);
517 
518  return ( phase + term / len(self) );
519 
520 
521  def __validate_pattern(self, pattern):
522  """!
523  @brief Validates pattern.
524  @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
525 
526  @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
527 
528  """
529  if (len(pattern) != len(self)):
530  raise NameError('syncpr: length of the pattern (' + len(pattern) + ') should be equal to size of the network');
531 
532  for feature in pattern:
533  if ( (feature != -1.0) and (feature != 1.0) ):
534  raise NameError('syncpr: patten feature (' + feature + ') should be distributed in [-1; 1]');
def _calculate_phases(self, solution, t, step, int_step)
Calculates new phases for oscillators in the network in line with current step.
Definition: sync.py:986
def __init__(self, num_osc, increase_strength1, increase_strength2, ccore=True)
Constructor of oscillatory network for pattern recognition based on Kuramoto model.
Definition: syncpr.py:240
Represents output dynamic of Sync.
Definition: sync.py:113
def simulate_static(self, steps, time, solution=solve_type.FAST, collect_dynamic=False)
Performs static simulation of oscillatory network.
Definition: sync.py:938
def animate_pattern_recognition(syncpr_output_dynamic, image_height, image_width, animation_velocity=75, title=None, save_movie=None)
Shows animation of pattern recognition process that has been preformed by the oscillatory network...
Definition: syncpr.py:129
def __init__(self, phase, time, ccore)
Constructor of syncpr dynamic.
Definition: syncpr.py:59
def __validate_pattern(self, pattern)
Validates pattern.
Definition: syncpr.py:521
def __calculate_memory_order(self, pattern)
Calculates function of the memorized pattern without any pattern validation.
Definition: syncpr.py:472
Neural Network: Oscillatory Neural Network based on Kuramoto model.
Definition: sync.py:1
Model of phase oscillatory network for pattern recognition that is based on the Kuramoto model...
Definition: syncpr.py:212
Represents output dynamic of syncpr (Sync for Pattern Recognition).
Definition: syncpr.py:53
def simulate(self, steps, time, pattern, solution=solve_type.RK4, collect_dynamic=True)
Performs static simulation of syncpr oscillatory network.
Definition: syncpr.py:317
def memory_order(self, pattern)
Calculates function of the memorized pattern.
Definition: syncpr.py:452
def __len__(self)
Returns size of the network.
Definition: syncpr.py:273
def train(self, samples)
Trains syncpr network using Hebbian rule for adjusting strength of connections between oscillators du...
Definition: syncpr.py:285
Visualizer of output dynamic of sync network (Sync).
Definition: sync.py:433
def simulate_static(self, steps, time, pattern, solution=solve_type.FAST, collect_dynamic=False)
Performs static simulation of syncpr oscillatory network.
Definition: syncpr.py:418
Visualizer of output dynamic of syncpr network (Sync for Pattern Recognition).
Definition: syncpr.py:71
def simulate_dynamic(self, pattern, order=0.998, solution=solve_type.RK4, collect_dynamic=False, step=0.1, int_step=0.01, threshold_changes=0.0000001)
Performs dynamic simulation of the network until stop condition is not reached.
Definition: syncpr.py:339
def show_pattern(syncpr_output_dynamic, image_height, image_width)
Displays evolution of phase oscillators as set of patterns where the last one means final result of r...
Definition: syncpr.py:78
def __del__(self)
Default destructor of syncpr.
Definition: syncpr.py:262
Neural and oscillatory network module.
Definition: __init__.py:1
Model of oscillatory network that is based on the Kuramoto model of synchronization.
Definition: sync.py:722