Completed
Push — master ( 5979b8...94fb35 )
by Andrei
01:45
created

syncnet_visualizer   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 50
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 10
c 0
b 0
f 0
wmc 4

3 Methods

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