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