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-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 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  Example:
171  @code
172  from pyclustering.cluster import cluster_visualizer
173  from pyclustering.cluster.syncnet import syncnet, solve_type
174  from pyclustering.samples.definitions import SIMPLE_SAMPLES
175  from pyclustering.utils import read_sample
176 
177  # Read sample for clustering from some file.
178  sample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3)
179 
180  # Create oscillatory network with connectivity radius 1.0.
181  network = syncnet(sample, 1.0)
182 
183  # Run cluster analysis and collect output dynamic of the oscillatory network.
184  # Network simulation is performed by Runge Kutta 4.
185  analyser = network.process(0.998, solve_type.RK4)
186 
187  # Show oscillatory network.
188  network.show_network()
189 
190  # Obtain clustering results.
191  clusters = analyser.allocate_clusters()
192 
193  # Visualize clustering results.
194  visualizer = cluster_visualizer()
195  visualizer.append_clusters(clusters, sample)
196  visualizer.show()
197  @endcode
198 
199  """
200 
201  def __init__(self, sample, radius, conn_repr = conn_represent.MATRIX, initial_phases = initial_type.RANDOM_GAUSSIAN, enable_conn_weight = False, ccore = True):
202  """!
203  @brief Contructor of the oscillatory network SYNC for cluster analysis.
204 
205  @param[in] sample (list): Input data that is presented as list of points (objects), each point should be represented by list or tuple.
206  @param[in] radius (double): Connectivity radius between points, points should be connected if distance between them less then the radius.
207  @param[in] conn_repr (conn_represent): Internal representation of connection in the network: matrix or list. Ignored in case of usage of CCORE library.
208  @param[in] initial_phases (initial_type): Type of initialization of initial phases of oscillators (random, uniformly distributed, etc.).
209  @param[in] enable_conn_weight (bool): If True - enable mode when strength between oscillators depends on distance between two oscillators.
210  If False - all connection between oscillators have the same strength that equals to 1 (True).
211  @param[in] ccore (bool): Defines should be CCORE C++ library used instead of Python code or not.
212 
213  """
214 
215  self._ccore_network_pointer = None;
216  self._osc_loc = sample;
217  self._num_osc = len(sample);
218 
219  if ( (ccore is True) and ccore_library.workable() ):
220  self._ccore_network_pointer = syncnet_create_network(sample, radius, initial_phases, enable_conn_weight);
221 
222  # Default representation that is returned by CCORE is matrix.
223  self._conn_represent = conn_represent.MATRIX;
224 
225  else:
226  super().__init__(len(sample), 1, 0, conn_type.DYNAMIC, conn_repr, initial_phases, False);
227 
228  self._conn_weight = None;
229  self._ena_conn_weight = enable_conn_weight;
230 
231  # Create connections.
232  if (radius is not None):
233  self._create_connections(radius);
234 
235 
236  def __del__(self):
237  """!
238  @brief Destructor of oscillatory network is based on Kuramoto model.
239 
240  """
241 
242  if (self._ccore_network_pointer is not None):
243  syncnet_destroy_network(self._ccore_network_pointer);
244  self._ccore_network_pointer = None;
245 
246 
247  def _create_connections(self, radius):
248  """!
249  @brief Create connections between oscillators in line with input radius of connectivity.
250 
251  @param[in] radius (double): Connectivity radius between oscillators.
252 
253  """
254 
255  if (self._ena_conn_weight is True):
256  self._conn_weight = [[0] * self._num_osc for _ in range(0, self._num_osc, 1)];
257 
258  maximum_distance = 0;
259  minimum_distance = float('inf');
260 
261  # Create connections
262  for i in range(0, self._num_osc, 1):
263  for j in range(i + 1, self._num_osc, 1):
264  dist = euclidean_distance(self._osc_loc[i], self._osc_loc[j]);
265 
266  if (self._ena_conn_weight is True):
267  self._conn_weight[i][j] = dist;
268  self._conn_weight[j][i] = dist;
269 
270  if (dist > maximum_distance): maximum_distance = dist;
271  if (dist < minimum_distance): minimum_distance = dist;
272 
273  if (dist <= radius):
274  self.set_connection(i, j);
275 
276  if (self._ena_conn_weight is True):
277  multiplier = 1;
278  subtractor = 0;
279 
280  if (maximum_distance != minimum_distance):
281  multiplier = (maximum_distance - minimum_distance);
282  subtractor = minimum_distance;
283 
284  for i in range(0, self._num_osc, 1):
285  for j in range(i + 1, self._num_osc, 1):
286  value_conn_weight = (self._conn_weight[i][j] - subtractor) / multiplier;
287 
288  self._conn_weight[i][j] = value_conn_weight;
289  self._conn_weight[j][i] = value_conn_weight;
290 
291 
292  def process(self, order = 0.998, solution = solve_type.FAST, collect_dynamic = True):
293  """!
294  @brief Peforms cluster analysis using simulation of the oscillatory network.
295 
296  @param[in] order (double): Order of synchronization that is used as indication for stopping processing.
297  @param[in] solution (solve_type): Specified type of solving diff. equation.
298  @param[in] collect_dynamic (bool): Specified requirement to collect whole dynamic of the network.
299 
300  @return (syncnet_analyser) Returns analyser of results of clustering.
301 
302  """
303 
304  if (self._ccore_network_pointer is not None):
305  pointer_output_dynamic = syncnet_process(self._ccore_network_pointer, order, solution, collect_dynamic);
306  return syncnet_analyser(None, None, pointer_output_dynamic);
307  else:
308  output_sync_dynamic = self.simulate_dynamic(order, solution, collect_dynamic);
309  return syncnet_analyser(output_sync_dynamic.output, output_sync_dynamic.time, None);
310 
311 
312  def _phase_kuramoto(self, teta, t, argv):
313  """!
314  @brief Overrided method for calculation of oscillator phase.
315 
316  @param[in] teta (double): Current value of phase.
317  @param[in] t (double): Time (can be ignored).
318  @param[in] argv (uint): Index of oscillator whose phase represented by argument teta.
319 
320  @return (double) New value of phase of oscillator with index 'argv'.
321 
322  """
323 
324  index = argv; # index of oscillator
325  phase = 0.0; # phase of a specified oscillator that will calculated in line with current env. states.
326 
327  neighbors = self.get_neighbors(index);
328  for k in neighbors:
329  conn_weight = 1.0;
330  if (self._ena_conn_weight is True):
331  conn_weight = self._conn_weight[index][k];
332 
333  phase += conn_weight * self._weight * math.sin(self._phases[k] - teta);
334 
335  divider = len(neighbors);
336  if (divider == 0):
337  divider = 1.0;
338 
339  return ( self._freq[index] + (phase / divider) );
340 
341 
342  def show_network(self):
343  """!
344  @brief Shows connections in the network. It supports only 2-d and 3-d representation.
345 
346  """
347 
348  if ( (self._ccore_network_pointer is not None) and (self._osc_conn is None) ):
349  self._osc_conn = sync_connectivity_matrix(self._ccore_network_pointer);
350 
351  dimension = len(self._osc_loc[0]);
352  if ( (dimension != 3) and (dimension != 2) ):
353  raise NameError('Network that is located in different from 2-d and 3-d dimensions can not be represented');
354 
355  from matplotlib.font_manager import FontProperties;
356  from matplotlib import rcParams;
357 
358  rcParams['font.sans-serif'] = ['Arial'];
359  rcParams['font.size'] = 12;
360 
361  fig = plt.figure();
362  axes = None;
363  if (dimension == 2):
364  axes = fig.add_subplot(111);
365  elif (dimension == 3):
366  axes = fig.gca(projection='3d');
367 
368  surface_font = FontProperties();
369  surface_font.set_name('Arial');
370  surface_font.set_size('12');
371 
372  for i in range(0, self._num_osc, 1):
373  if (dimension == 2):
374  axes.plot(self._osc_loc[i][0], self._osc_loc[i][1], 'bo');
375  if (self._conn_represent == conn_represent.MATRIX):
376  for j in range(i, self._num_osc, 1): # draw connection between two points only one time
377  if (self.has_connection(i, j) == True):
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  else:
381  for j in self.get_neighbors(i):
382  if ( (self.has_connection(i, j) == True) and (i > j) ): # draw connection between two points only one time
383  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);
384 
385  elif (dimension == 3):
386  axes.scatter(self._osc_loc[i][0], self._osc_loc[i][1], self._osc_loc[i][2], c = 'b', marker = 'o');
387 
388  if (self._conn_represent == conn_represent.MATRIX):
389  for j in range(i, self._num_osc, 1): # draw connection between two points only one time
390  if (self.has_connection(i, j) == True):
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  else:
394  for j in self.get_neighbors(i):
395  if ( (self.has_connection(i, j) == True) and (i > j) ): # draw connection between two points only one time
396  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);
397 
398  plt.grid();
399  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:292
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:236
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:201
Visualizer of output dynamic of sync network (Sync).
Definition: sync.py:433
def show_network(self)
Shows connections in the network.
Definition: syncnet.py:342
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:247