Completed
Push — 0.7.dev ( 60b0dd...a4d05c )
by Andrei
57s
created

syncnet_analyser.allocate_noise()   A

Complexity

Conditions 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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