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