syncnet.py
1 """!
2 
3 @brief Cluster analysis algorithm: Sync
4 @details Implementation based on paper @cite article::syncnet::1.
5 
6 @authors Andrei Novikov (pyclustering@yandex.ru)
7 @date 2014-2018
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 warnings
29 
30 try:
31  import matplotlib.pyplot as plt
32  import matplotlib.animation as animation
33 except Exception as error_instance:
34  warnings.warn("Impossible to import matplotlib (please, install 'matplotlib'), pyclustering's visualization "
35  "functionality is not available (details: '%s')." % str(error_instance))
36 
37 from pyclustering.cluster.encoder import type_encoding
38 from pyclustering.cluster import cluster_visualizer
39 
40 from pyclustering.core.syncnet_wrapper import syncnet_create_network, syncnet_process, syncnet_destroy_network, syncnet_analyser_destroy
41 from pyclustering.core.sync_wrapper import sync_connectivity_matrix
42 from pyclustering.core.wrapper import ccore_library
43 
44 from pyclustering.nnet.sync import sync_dynamic, sync_network, sync_visualizer
45 from pyclustering.nnet import conn_represent, initial_type, conn_type, solve_type
46 
47 from pyclustering.utils import euclidean_distance
48 
49 
51  """!
52  @brief Performs analysis of output dynamic of the oscillatory network syncnet to extract information about cluster allocation.
53 
54  """
55 
56  def __init__(self, phase, time, pointer_sync_analyser):
57  """!
58  @brief Constructor of the analyser.
59 
60  @param[in] phase (list): Output dynamic of the oscillatory network, where one iteration consists of all phases of oscillators.
61  @param[in] time (list): Simulation time.
62  @param[in] pointer_sync_analyser (POINTER): Pointer to CCORE analyser, if specified then other arguments can be omitted.
63 
64  """
65  super().__init__(phase, time, pointer_sync_analyser);
66 
67 
68  def __del__(self):
69  """!
70  @brief Desctructor of the analyser.
71 
72  """
73 
74  if (self._ccore_sync_dynamic_pointer is not None):
75  syncnet_analyser_destroy(self._ccore_sync_dynamic_pointer);
76  self._ccore_sync_dynamic_pointer = None;
77 
78 
79  def allocate_clusters(self, eps = 0.01, indexes = None, iteration = None):
80  """!
81  @brief Returns list of clusters in line with state of ocillators (phases).
82 
83  @param[in] eps (double): Tolerance level that define maximal difference between phases of oscillators in one cluster.
84  @param[in] indexes (list): List of real object indexes and it should be equal to amount of oscillators (in case of 'None' - indexes are in range [0; amount_oscillators]).
85  @param[in] iteration (uint): Iteration of simulation that should be used for allocation.
86 
87  @return (list) List of clusters, for example [ [cluster1], [cluster2], ... ].)
88 
89  """
90 
91  return self.allocate_sync_ensembles(eps, indexes, iteration);
92 
93 
95  """!
96  @brief Returns clustering result representation type that indicate how clusters are encoded.
97 
98  @return (type_encoding) Clustering result representation.
99 
100  @see get_clusters()
101 
102  """
103 
104  return type_encoding.CLUSTER_INDEX_LIST_SEPARATION;
105 
106 
107 
109  """!
110  @brief Visualizer of output dynamic of oscillatory network 'syncnet' for cluster analysis.
111 
112  """
113 
114  @staticmethod
115  def animate_cluster_allocation(dataset, analyser, animation_velocity = 75, tolerance = 0.1, save_movie = None, title = None):
116  """!
117  @brief Shows animation of output dynamic (output of each oscillator) during simulation on a circle from [0; 2pi].
118 
119  @param[in] dataset (list): Input data that was used for processing by the network.
120  @param[in] analyser (syncnet_analyser): Output dynamic analyser of the Sync network.
121  @param[in] animation_velocity (uint): Interval between frames in milliseconds.
122  @param[in] tolerance (double): Tolerance level that define maximal difference between phases of oscillators in one cluster.
123  @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
124  @param[in] title (string): If it is specified then title will be displayed on the animation plot.
125 
126  """
127 
128  figure = plt.figure();
129 
130  def init_frame():
131  return frame_generation(0);
132 
133  def frame_generation(index_dynamic):
134  figure.clf();
135  if (title is not None):
136  figure.suptitle(title, fontsize = 26, fontweight = 'bold');
137 
138  ax1 = figure.add_subplot(121, projection='polar');
139 
140  clusters = analyser.allocate_clusters(eps = tolerance, iteration = index_dynamic);
141  dynamic = analyser.output[index_dynamic];
142 
143  visualizer = cluster_visualizer(size_row = 2);
144  visualizer.append_clusters(clusters, dataset);
145 
146  artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
147 
148  visualizer.show(figure, display = False);
149  artist2 = figure.gca();
150 
151  return [ artist1, artist2 ];
152 
153  cluster_animation = animation.FuncAnimation(figure, frame_generation, len(analyser), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
154 
155  if (save_movie is not None):
156 # plt.rcParams['animation.ffmpeg_path'] = 'D:\\Program Files\\ffmpeg-3.3.1-win64-static\\bin\\ffmpeg.exe';
157 # ffmpeg_writer = animation.FFMpegWriter(fps = 15);
158 # cluster_animation.save(save_movie, writer = ffmpeg_writer);
159  cluster_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
160  else:
161  plt.show();
162 
163 
165  """!
166  @brief Class represents clustering algorithm SyncNet.
167  @details SyncNet is bio-inspired algorithm that is based on oscillatory network that uses modified Kuramoto model. Each attribute of a data object
168  is considered as a phase oscillator.
169 
170  CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
171 
172  Example:
173  @code
174  # read sample for clustering from some file
175  sample = read_sample(path_to_file);
176 
177  # create oscillatory network with connectivity radius 0.5 using CCORE (C++ implementation of pyclustering)
178  network = syncnet(sample, 0.5, ccore = True);
179 
180  # run cluster analysis and collect output dynamic of the oscillatory network,
181  # network simulation is performed by Runge Kutta Fehlberg 45.
182  (dyn_time, dyn_phase) = network.process(0.998, solve_type.RFK45, True);
183 
184  # show oscillatory network
185  network.show_network();
186 
187  # obtain clustering results
188  clusters = network.get_clusters();
189 
190  # show clusters
191  draw_clusters(sample, clusters);
192  @endcode
193 
194  """
195 
196  def __init__(self, sample, radius, conn_repr = conn_represent.MATRIX, initial_phases = initial_type.RANDOM_GAUSSIAN, enable_conn_weight = False, ccore = True):
197  """!
198  @brief Contructor of the oscillatory network SYNC for cluster analysis.
199 
200  @param[in] sample (list): Input data that is presented as list of points (objects), each point should be represented by list or tuple.
201  @param[in] radius (double): Connectivity radius between points, points should be connected if distance between them less then the radius.
202  @param[in] conn_repr (conn_represent): Internal representation of connection in the network: matrix or list. Ignored in case of usage of CCORE library.
203  @param[in] initial_phases (initial_type): Type of initialization of initial phases of oscillators (random, uniformly distributed, etc.).
204  @param[in] enable_conn_weight (bool): If True - enable mode when strength between oscillators depends on distance between two oscillators.
205  If False - all connection between oscillators have the same strength that equals to 1 (True).
206  @param[in] ccore (bool): Defines should be CCORE C++ library used instead of Python code or not.
207 
208  """
209 
210  self._ccore_network_pointer = None;
211  self._osc_loc = sample;
212  self._num_osc = len(sample);
213 
214  if ( (ccore is True) and ccore_library.workable() ):
215  self._ccore_network_pointer = syncnet_create_network(sample, radius, initial_phases, enable_conn_weight);
216 
217  # Default representation that is returned by CCORE is matrix.
218  self._conn_represent = conn_represent.MATRIX;
219 
220  else:
221  super().__init__(len(sample), 1, 0, conn_type.DYNAMIC, conn_repr, initial_phases, False);
222 
223  self._conn_weight = None;
224  self._ena_conn_weight = enable_conn_weight;
225 
226  # Create connections.
227  if (radius is not None):
228  self._create_connections(radius);
229 
230 
231  def __del__(self):
232  """!
233  @brief Destructor of oscillatory network is based on Kuramoto model.
234 
235  """
236 
237  if (self._ccore_network_pointer is not None):
238  syncnet_destroy_network(self._ccore_network_pointer);
239  self._ccore_network_pointer = None;
240 
241 
242  def _create_connections(self, radius):
243  """!
244  @brief Create connections between oscillators in line with input radius of connectivity.
245 
246  @param[in] radius (double): Connectivity radius between oscillators.
247 
248  """
249 
250  if (self._ena_conn_weight is True):
251  self._conn_weight = [[0] * self._num_osc for _ in range(0, self._num_osc, 1)];
252 
253  maximum_distance = 0;
254  minimum_distance = float('inf');
255 
256  # Create connections
257  for i in range(0, self._num_osc, 1):
258  for j in range(i + 1, self._num_osc, 1):
259  dist = euclidean_distance(self._osc_loc[i], self._osc_loc[j]);
260 
261  if (self._ena_conn_weight is True):
262  self._conn_weight[i][j] = dist;
263  self._conn_weight[j][i] = dist;
264 
265  if (dist > maximum_distance): maximum_distance = dist;
266  if (dist < minimum_distance): minimum_distance = dist;
267 
268  if (dist <= radius):
269  self.set_connection(i, j);
270 
271  if (self._ena_conn_weight is True):
272  multiplier = 1;
273  subtractor = 0;
274 
275  if (maximum_distance != minimum_distance):
276  multiplier = (maximum_distance - minimum_distance);
277  subtractor = minimum_distance;
278 
279  for i in range(0, self._num_osc, 1):
280  for j in range(i + 1, self._num_osc, 1):
281  value_conn_weight = (self._conn_weight[i][j] - subtractor) / multiplier;
282 
283  self._conn_weight[i][j] = value_conn_weight;
284  self._conn_weight[j][i] = value_conn_weight;
285 
286 
287  def process(self, order = 0.998, solution = solve_type.FAST, collect_dynamic = True):
288  """!
289  @brief Peforms cluster analysis using simulation of the oscillatory network.
290 
291  @param[in] order (double): Order of synchronization that is used as indication for stopping processing.
292  @param[in] solution (solve_type): Specified type of solving diff. equation.
293  @param[in] collect_dynamic (bool): Specified requirement to collect whole dynamic of the network.
294 
295  @return (syncnet_analyser) Returns analyser of results of clustering.
296 
297  """
298 
299  if (self._ccore_network_pointer is not None):
300  pointer_output_dynamic = syncnet_process(self._ccore_network_pointer, order, solution, collect_dynamic);
301  return syncnet_analyser(None, None, pointer_output_dynamic);
302  else:
303  output_sync_dynamic = self.simulate_dynamic(order, solution, collect_dynamic);
304  return syncnet_analyser(output_sync_dynamic.output, output_sync_dynamic.time, None);
305 
306 
307  def _phase_kuramoto(self, teta, t, argv):
308  """!
309  @brief Overrided method for calculation of oscillator phase.
310 
311  @param[in] teta (double): Current value of phase.
312  @param[in] t (double): Time (can be ignored).
313  @param[in] argv (uint): Index of oscillator whose phase represented by argument teta.
314 
315  @return (double) New value of phase of oscillator with index 'argv'.
316 
317  """
318 
319  index = argv; # index of oscillator
320  phase = 0.0; # phase of a specified oscillator that will calculated in line with current env. states.
321 
322  neighbors = self.get_neighbors(index);
323  for k in neighbors:
324  conn_weight = 1.0;
325  if (self._ena_conn_weight is True):
326  conn_weight = self._conn_weight[index][k];
327 
328  phase += conn_weight * self._weight * math.sin(self._phases[k] - teta);
329 
330  divider = len(neighbors);
331  if (divider == 0):
332  divider = 1.0;
333 
334  return ( self._freq[index] + (phase / divider) );
335 
336 
337  def show_network(self):
338  """!
339  @brief Shows connections in the network. It supports only 2-d and 3-d representation.
340 
341  """
342 
343  if ( (self._ccore_network_pointer is not None) and (self._osc_conn is None) ):
344  self._osc_conn = sync_connectivity_matrix(self._ccore_network_pointer);
345 
346  dimension = len(self._osc_loc[0]);
347  if ( (dimension != 3) and (dimension != 2) ):
348  raise NameError('Network that is located in different from 2-d and 3-d dimensions can not be represented');
349 
350  from matplotlib.font_manager import FontProperties;
351  from matplotlib import rcParams;
352 
353  rcParams['font.sans-serif'] = ['Arial'];
354  rcParams['font.size'] = 12;
355 
356  fig = plt.figure();
357  axes = None;
358  if (dimension == 2):
359  axes = fig.add_subplot(111);
360  elif (dimension == 3):
361  axes = fig.gca(projection='3d');
362 
363  surface_font = FontProperties();
364  surface_font.set_name('Arial');
365  surface_font.set_size('12');
366 
367  for i in range(0, self._num_osc, 1):
368  if (dimension == 2):
369  axes.plot(self._osc_loc[i][0], self._osc_loc[i][1], 'bo');
370  if (self._conn_represent == conn_represent.MATRIX):
371  for j in range(i, self._num_osc, 1): # draw connection between two points only one time
372  if (self.has_connection(i, j) == True):
373  axes.plot([self._osc_loc[i][0], self._osc_loc[j][0]], [self._osc_loc[i][1], self._osc_loc[j][1]], 'b-', linewidth = 0.5);
374 
375  else:
376  for j in self.get_neighbors(i):
377  if ( (self.has_connection(i, j) == True) and (i > j) ): # draw connection between two points only one time
378  axes.plot([self._osc_loc[i][0], self._osc_loc[j][0]], [self._osc_loc[i][1], self._osc_loc[j][1]], 'b-', linewidth = 0.5);
379 
380  elif (dimension == 3):
381  axes.scatter(self._osc_loc[i][0], self._osc_loc[i][1], self._osc_loc[i][2], c = 'b', marker = 'o');
382 
383  if (self._conn_represent == conn_represent.MATRIX):
384  for j in range(i, self._num_osc, 1): # draw connection between two points only one time
385  if (self.has_connection(i, j) == True):
386  axes.plot([self._osc_loc[i][0], self._osc_loc[j][0]], [self._osc_loc[i][1], self._osc_loc[j][1]], [self._osc_loc[i][2], self._osc_loc[j][2]], 'b-', linewidth = 0.5);
387 
388  else:
389  for j in self.get_neighbors(i):
390  if ( (self.has_connection(i, j) == True) and (i > j) ): # draw connection between two points only one time
391  axes.plot([self._osc_loc[i][0], self._osc_loc[j][0]], [self._osc_loc[i][1], self._osc_loc[j][1]], [self._osc_loc[i][2], self._osc_loc[j][2]], 'b-', linewidth = 0.5);
392 
393  plt.grid();
394  plt.show();
Common visualizer of clusters on 1D, 2D or 3D surface.
Definition: __init__.py:359
pyclustering module for cluster analysis.
Definition: __init__.py:1
def simulate_dynamic(self, order=0.998, solution=solve_type.FAST, 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: sync.py:871
def allocate_clusters(self, eps=0.01, indexes=None, iteration=None)
Returns list of clusters in line with state of ocillators (phases).
Definition: syncnet.py:79
def get_cluster_encoding(self)
Returns clustering result representation type that indicate how clusters are encoded.
Definition: syncnet.py:94
Visualizer of output dynamic of oscillatory network &#39;syncnet&#39; for cluster analysis.
Definition: syncnet.py:108
Represents output dynamic of Sync.
Definition: sync.py:113
def process(self, order=0.998, solution=solve_type.FAST, collect_dynamic=True)
Peforms cluster analysis using simulation of the oscillatory network.
Definition: syncnet.py:287
Utils that are used by modules of pyclustering.
Definition: __init__.py:1
Performs analysis of output dynamic of the oscillatory network syncnet to extract information about c...
Definition: syncnet.py:50
def __del__(self)
Destructor of oscillatory network is based on Kuramoto model.
Definition: syncnet.py:231
Module for representing clustering results.
Definition: encoder.py:1
def __init__(self, phase, time, pointer_sync_analyser)
Constructor of the analyser.
Definition: syncnet.py:56
def allocate_sync_ensembles(self, tolerance=0.01, indexes=None, iteration=None)
Allocate clusters in line with ensembles of synchronous oscillators where each synchronous ensemble c...
Definition: sync.py:194
def set_connection(self, i, j)
Couples two specified oscillators in the network with dynamic connections.
Definition: __init__.py:387
Class represents clustering algorithm SyncNet.
Definition: syncnet.py:164
def has_connection(self, i, j)
Returns True if there is connection between i and j oscillators and False - if connection doesn&#39;t exi...
Definition: __init__.py:366
def get_neighbors(self, index)
Finds neighbors of the oscillator with specified index.
Definition: __init__.py:409
Neural Network: Oscillatory Neural Network based on Kuramoto model.
Definition: sync.py:1
def animate_cluster_allocation(dataset, analyser, animation_velocity=75, tolerance=0.1, save_movie=None, title=None)
Shows animation of output dynamic (output of each oscillator) during simulation on a circle from [0; ...
Definition: syncnet.py:115
def __del__(self)
Desctructor of the analyser.
Definition: syncnet.py:68
def __init__(self, sample, radius, conn_repr=conn_represent.MATRIX, initial_phases=initial_type.RANDOM_GAUSSIAN, enable_conn_weight=False, ccore=True)
Contructor of the oscillatory network SYNC for cluster analysis.
Definition: syncnet.py:196
Visualizer of output dynamic of sync network (Sync).
Definition: sync.py:433
def show_network(self)
Shows connections in the network.
Definition: syncnet.py:337
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
def _create_connections(self, radius)
Create connections between oscillators in line with input radius of connectivity. ...
Definition: syncnet.py:242