Completed
Push — master ( 6ed467...f3f897 )
by Andrei
01:29
created

syncnet_analyser.get_cluster_encoding()   A

Complexity

Conditions 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
"""!
2
3
@brief Cluster analysis algorithm: Sync
4
@details Based on article description:
5
         - T.Miyano, T.Tsutsui. Data Synchronization as a Method of Data Mining. 2007.
6
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2016
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
29
import matplotlib.pyplot as plt;
0 ignored issues
show
Configuration introduced by
The import matplotlib.pyplot could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
30
import matplotlib.animation as animation;
0 ignored issues
show
Configuration introduced by
The import matplotlib.animation could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
31
32
import math;
33
34
from pyclustering.cluster.encoder import type_encoding;
35
from pyclustering.cluster import cluster_visualizer;
36
37
from pyclustering.core.syncnet_wrapper import syncnet_create_network, syncnet_process, syncnet_destroy_network, syncnet_analyser_destroy;
38
39
from pyclustering.nnet.sync import sync_dynamic, sync_network, sync_visualizer;
40
from pyclustering.nnet import conn_represent, initial_type, conn_type, solve_type;
41
42
from pyclustering.utils import euclidean_distance;
43
44
45
class syncnet_analyser(sync_dynamic):
46
    """!
47
    @brief Performs analysis of output dynamic of the oscillatory network syncnet to extract information about cluster allocation.
48
    
49
    """
50
    
51
    def __init__(self, phase, time, pointer_sync_analyser):
52
        """!
53
        @brief Constructor of the analyser.
54
        
55
        @param[in] phase (list): Output dynamic of the oscillatory network, where one iteration consists of all phases of oscillators.
56
        @param[in] time (list): Simulation time.
57
        @param[in] pointer_sync_analyser (POINTER): Pointer to CCORE analyser, if specified then other arguments can be omitted.
58
        
59
        """
60
        super().__init__(phase, time, pointer_sync_analyser);
61
    
62
    
63
    def __del__(self):
64
        """!
65
        @brief Desctructor of the analyser.
66
        
67
        """
68
        
69
        if (self._ccore_sync_dynamic_pointer is not None):
70
            syncnet_analyser_destroy(self._ccore_sync_dynamic_pointer);
71
            self._ccore_sync_dynamic_pointer = None;
72
    
73
    
74
    def allocate_clusters(self, eps = 0.01, indexes = None, iteration = None):
75
        """!
76
        @brief Returns list of clusters in line with state of ocillators (phases).
77
        
78
        @param[in] eps (double): Tolerance level that define maximal difference between phases of oscillators in one cluster.
79
        @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]).
80
        @param[in] iteration (uint): Iteration of simulation that should be used for allocation.
81
        
82
        @return (list) List of clusters, for example [ [cluster1], [cluster2], ... ].
83
        
84
        @see allocate_noise()
85
        
86
        """
87
        
88
        return self.allocate_sync_ensembles(eps, indexes, iteration);
89
90
91
    def get_cluster_encoding(self):
92
        """!
93
        @brief Returns clustering result representation type that indicate how clusters are encoded.
94
        
95
        @return (type_encoding) Clustering result representation.
96
        
97
        @see get_clusters()
98
        
99
        """
100
        
101
        return type_encoding.CLUSTER_INDEX_LIST_SEPARATION;
102
103
104
    def allocate_noise(self):
105
        """!
106
        @brief Returns allocated noise.
107
        
108
        @remark Allocated noise can be returned only after data processing (use method process() before). Otherwise empty list is returned.
109
        
110
        @return (list) List of indexes that are marked as a noise.
111
        
112
        @see allocate_clusters()
113
        
114
        """         
115
        return [];
116
117
118
class syncnet_visualizer(sync_visualizer):
119
    """!
120
    @brief Visualizer of output dynamic of oscillatory network 'syncnet' for cluster analysis.
121
    
122
    """
123
    
124
    @staticmethod
125
    def animate_cluster_allocation(dataset, analyser, animation_velocity = 75, tolerance = 0.1, save_movie = None):
126
        """!
127
        @brief Shows animation of output dynamic (output of each oscillator) during simulation on a circle from [0; 2pi].
128
        
129
        @param[in] dataset (list): Input data that was used for processing by the network.
130
        @param[in] analyser (syncnet_analyser): Output dynamic analyser of the Sync network.
131
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
132
        @param[in] tolerance (double): Tolerance level that define maximal difference between phases of oscillators in one cluster.
133
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
134
        
135
        """
136
        
137
        figure = plt.figure();
138
        
139
        def init_frame():
140
            return frame_generation(0);
141
        
142
        def frame_generation(index_dynamic):
143
            figure.clf();
144
            ax1 = figure.add_subplot(121, projection='polar');
145
            
146
            clusters = analyser.allocate_clusters(eps = tolerance, iteration = index_dynamic);
147
            dynamic = analyser.output[index_dynamic];
148
            
149
            visualizer = cluster_visualizer(size_row = 2);
150
            visualizer.append_clusters(clusters, dataset);
151
            
152
            artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
153
            
154
            visualizer.show(figure, display = False);
155
            artist2 = figure.gca();
156
            
157
            return [ artist1, artist2 ];
158
        
159
        cluster_animation = animation.FuncAnimation(figure, frame_generation, len(analyser), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
160
161
        if (save_movie is not None):
162
#             plt.rcParams['animation.ffmpeg_path'] = 'C:\\Users\\annoviko\\programs\\ffmpeg-win64-static\\bin\\ffmpeg.exe';
163
#             ffmpeg_writer = animation.FFMpegWriter();
164
#             cluster_animation.save(save_movie, writer = ffmpeg_writer, fps = 15);
165
            cluster_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
166
        else:
167
            plt.show();
168
169
170
class syncnet(sync_network):
171
    """!
172
    @brief Class represents clustering algorithm SyncNet. 
173
    @details SyncNet is bio-inspired algorithm that is based on oscillatory network that uses modified Kuramoto model. Each attribute of a data object
174
             is considered as a phase oscillator.
175
    
176
    Example:
177
    @code
178
        # read sample for clustering from some file
179
        sample = read_sample(path_to_file);
180
        
181
        # create oscillatory network with connectivity radius 0.5 using CCORE (C++ implementation of pyclustering)
182
        network = syncnet(sample, 0.5, ccore = True);
183
        
184
        # run cluster analysis and collect output dynamic of the oscillatory network, 
185
        # network simulation is performed by Runge Kutta Fehlberg 45.
186
        (dyn_time, dyn_phase) = network.process(0.998, solve_type.RFK45, True);
187
        
188
        # show oscillatory network
189
        network.show_network();
190
        
191
        # obtain clustering results
192
        clusters = network.get_clusters();
193
        
194
        # show clusters
195
        draw_clusters(sample, clusters);
196
    @endcode
197
    
198
    """
199
    
200
    def __init__(self, sample, radius, conn_repr = conn_represent.MATRIX, initial_phases = initial_type.RANDOM_GAUSSIAN, enable_conn_weight = False, ccore = False):
201
        """!
202
        @brief Contructor of the oscillatory network SYNC for cluster analysis.
203
        
204
        @param[in] sample (list): Input data that is presented as list of points (objects), each point should be represented by list or tuple.
205
        @param[in] radius (double): Connectivity radius between points, points should be connected if distance between them less then the radius.
206
        @param[in] conn_repr (conn_represent): Internal representation of connection in the network: matrix or list. Ignored in case of usage of CCORE library.
207
        @param[in] initial_phases (initial_type): Type of initialization of initial phases of oscillators (random, uniformly distributed, etc.).
208
        @param[in] enable_conn_weight (bool): If True - enable mode when strength between oscillators depends on distance between two oscillators.
209
              If False - all connection between oscillators have the same strength that equals to 1 (True).
210
        @param[in] ccore (bool): Defines should be CCORE C++ library used instead of Python code or not.
211
        
212
        """
213
        
214
        self.__ccore_network_pointer = None;
215
        
216
        if (ccore is True):
217
            self.__ccore_network_pointer = syncnet_create_network(sample, radius, initial_phases, enable_conn_weight);
218
        else:
219
            super().__init__(len(sample), 1, 0, conn_type.DYNAMIC, initial_phases);
220
            
221
            self._conn_weight = None;
222
            self._ena_conn_weight = enable_conn_weight;
223
            self._osc_loc = sample;
224
            self._conn_represent = conn_repr;
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
        else:
241
            self._osc_loc = None;   # pointer to external object
242
243
244
    def _create_connections(self, radius):
245
        """!
246
        @brief Create connections between oscillators in line with input radius of connectivity.
247
        
248
        @param[in] radius (double): Connectivity radius between oscillators.
249
        
250
        """
251
        
252
        if (self._ena_conn_weight is True):
253
            self._conn_weight = [[0] * self._num_osc for index in range(0, self._num_osc, 1)];
254
        
255
        maximum_distance = 0;
256
        minimum_distance = float('inf');
257
        
258
        # Create connections
259
        for i in range(0, self._num_osc, 1):
260
            for j in range(i + 1, self._num_osc, 1):
261
                    dist = euclidean_distance(self._osc_loc[i], self._osc_loc[j]);
262
                    
263
                    if (self._ena_conn_weight is True):
264
                        self._conn_weight[i][j] = dist;
265
                        self._conn_weight[j][i] = dist;
266
                        
267
                        if (dist > maximum_distance): maximum_distance = dist;
268
                        if (dist < minimum_distance): minimum_distance = dist;
269
                    
270
                    if (dist <= radius):
271
                        self.set_connection(i, j);
272
        
273
        if (self._ena_conn_weight is True):
274
            multiplier = 1; 
275
            subtractor = 0;
276
            
277
            if (maximum_distance != minimum_distance):
278
                multiplier = (maximum_distance - minimum_distance);
279
                subtractor = minimum_distance;
280
            
281
            for i in range(0, self._num_osc, 1):
282
                for j in range(i + 1, self._num_osc, 1):
283
                    value_conn_weight = (self._conn_weight[i][j] - subtractor) / multiplier;
284
                    
285
                    self._conn_weight[i][j] = value_conn_weight;
286
                    self._conn_weight[j][i] = value_conn_weight;
287
288
289
    def process(self, order = 0.998, solution = solve_type.FAST, collect_dynamic = True):
290
        """!
291
        @brief Peforms cluster analysis using simulation of the oscillatory network.
292
        
293
        @param[in] order (double): Order of synchronization that is used as indication for stopping processing.
294
        @param[in] solution (solve_type): Specified type of solving diff. equation.
295
        @param[in] collect_dynamic (bool): Specified requirement to collect whole dynamic of the network.
296 View Code Duplication
        
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
297
        @return (syncnet_analyser) Returns analyser of results of clustering.
298
        
299
        """
300
        
301 View Code Duplication
        if (self.__ccore_network_pointer is not None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
302
            pointer_output_dynamic = syncnet_process(self.__ccore_network_pointer, order, solution, collect_dynamic);
303
            return syncnet_analyser(None, None, pointer_output_dynamic);
304
        else:
305
            output_sync_dynamic = self.simulate_dynamic(order, solution, collect_dynamic);
306
            return syncnet_analyser(output_sync_dynamic.output, output_sync_dynamic.time, None);
307
    
308
    
309 View Code Duplication
    def _phase_kuramoto(self, teta, t, argv):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
310
        """!
311
        @brief Overrided method for calculation of oscillator phase.
312
        
313
        @param[in] teta (double): Current value of phase.
314 View Code Duplication
        @param[in] t (double): Time (can be ignored).
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
315
        @param[in] argv (uint): Index of oscillator whose phase represented by argument teta.
316
        
317
        @return (double) New value of phase of oscillator with index 'argv'.
318
        
319
        """
320
        
321
        index = argv;   # index of oscillator
322
        phase = 0.0;      # phase of a specified oscillator that will calculated in line with current env. states.
323
        
324
        neighbors = self.get_neighbors(index);
325
        for k in neighbors:
326
            conn_weight = 1.0;
327
            if (self._ena_conn_weight is True):
328
                conn_weight = self._conn_weight[index][k];
329
                
330
            phase += conn_weight * self._weight * math.sin(self._phases[k] - teta);
331
        
332
        divider = len(neighbors);
333
        if (divider == 0):
334
            divider = 1.0;
335
            
336
        return ( self._freq[index] + (phase / divider) );   
337
    
338
    
339
    def show_network(self):
340
        """!
341
        @brief Shows connections in the network. It supports only 2-d and 3-d representation.
342
        
343
        """
344
        
345
        if (self.__ccore_network_pointer is not None):
346
            raise NameError("Not supported for CCORE");
347
        
348
        dimension = len(self._osc_loc[0]);
349
        if ( (dimension != 3) and (dimension != 2) ):
350
            raise NameError('Network that is located in different from 2-d and 3-d dimensions can not be represented');
351
        
352
        from matplotlib.font_manager import FontProperties;
0 ignored issues
show
Configuration introduced by
The import matplotlib.font_manager could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
353
        from matplotlib import rcParams;
0 ignored issues
show
Configuration introduced by
The import matplotlib could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
354
    
355
        rcParams['font.sans-serif'] = ['Arial'];
356
        rcParams['font.size'] = 12;
357
358
        fig = plt.figure();
359
        axes = None;
360
        if (dimension == 2):
361
            axes = fig.add_subplot(111);
362
        elif (dimension == 3):
363
            axes = fig.gca(projection='3d');
364
        
365
        surface_font = FontProperties();
366
        surface_font.set_name('Arial');
367
        surface_font.set_size('12');
368
        
369
        for i in range(0, self._num_osc, 1):
370
            if (dimension == 2):
371
                axes.plot(self._osc_loc[i][0], self._osc_loc[i][1], 'bo');  
372
                if (self._conn_represent == conn_represent.MATRIX):
373
                    for j in range(i, self._num_osc, 1):    # draw connection between two points only one time
374
                        if (self.has_connection(i, j) == True):
375
                            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);    
376
                            
377
                else:
378
                    for j in self.get_neighbors(i):
379
                        if ( (self.has_connection(i, j) == True) and (i > j) ):     # draw connection between two points only one time
380
                            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);    
381
            
382
            elif (dimension == 3):
383
                axes.scatter(self._osc_loc[i][0], self._osc_loc[i][1], self._osc_loc[i][2], c = 'b', marker = 'o');
384
                
385
                if (self._conn_represent == conn_represent.MATRIX):
386
                    for j in range(i, self._num_osc, 1):    # draw connection between two points only one time
387
                        if (self.has_connection(i, j) == True):
388
                            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);
389
                        
390
                else:
391
                    for j in self.get_neighbors(i):
392
                        if ( (self.has_connection(i, j) == True) and (i > j) ):     # draw connection between two points only one time
393
                            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);
394
                               
395
        plt.grid();
396
        plt.show();
397
    
398