Completed
Push — 0.7.dev ( c5e370...7387bb )
by Andrei
01:08
created

sync_visualizer   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 249
Duplicated Lines 27.31 %

Importance

Changes 0
Metric Value
dl 68
loc 249
rs 10
c 0
b 0
f 0
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A show_output_dynamic() 0 12 1
A frame_generation() 0 5 1
A show_order_parameter() 23 23 3
B animate_correlation_matrix() 0 32 4
B animate() 0 42 5
A show_phase_matrix() 19 21 1
B animate_output_dynamic() 7 31 4
B animate_phase_matrix() 0 36 4
A show_output_dynamics() 0 13 1
A init_frame() 0 2 1
A show_correlation_matrix() 13 16 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""!
2
3
@brief Neural Network: Oscillatory Neural Network based on Kuramoto model
4
@details Based on article description:
5
         - A.Arenas, Y.Moreno, C.Zhou. Synchronization in complex networks. 2008.
6
         - X.B.Lu. Adaptive Cluster Synchronization in Coupled Phase Oscillators. 2009.
7
         - X.Lou. Adaptive Synchronizability of Coupled Oscillators With Switching. 2012.
8
         - A.Novikov, E.Benderskaya. Oscillatory Neural Networks Based on the Kuramoto Model. 2014.
9
10
@authors Andrei Novikov ([email protected])
11
@date 2014-2017
12
@copyright GNU Public License
13
14
@cond GNU_PUBLIC_LICENSE
15
    PyClustering is free software: you can redistribute it and/or modify
16
    it under the terms of the GNU General Public License as published by
17
    the Free Software Foundation, either version 3 of the License, or
18
    (at your option) any later version.
19
    
20
    PyClustering is distributed in the hope that it will be useful,
21
    but WITHOUT ANY WARRANTY; without even the implied warranty of
22
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
    GNU General Public License for more details.
24
    
25
    You should have received a copy of the GNU General Public License
26
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
@endcond
28
29
"""
30
31
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...
32
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...
33
34
import math;
35
import numpy;
0 ignored issues
show
Configuration introduced by
The import numpy 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...
36
import random;
37
38
import pyclustering.core.sync_wrapper as wrapper;
39
40
from scipy.integrate import odeint;
0 ignored issues
show
Configuration introduced by
The import scipy.integrate 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...
41
42
from pyclustering.nnet import network, conn_represent, conn_type, initial_type, solve_type;
43
from pyclustering.utils import pi, draw_dynamics, draw_dynamics_set, set_ax_param;
44
45
46
class sync_dynamic:
47
    """!
48
    @brief Represents output dynamic of Sync.
49
    
50
    """
51
    
52
    @property
53
    def output(self):
54
        """!
55
        @brief (list) Returns output dynamic of the Sync network (phase coordinates of each oscillator in the network) during simulation.
56
        
57
        """
58
        if ( (self._ccore_sync_dynamic_pointer is not None) and ( (self._dynamic is None) or (len(self._dynamic) == 0) ) ):
59
            self._dynamic = wrapper.sync_dynamic_get_output(self._ccore_sync_dynamic_pointer);
60
        
61
        return self._dynamic;
62
    
63
    
64
    @property
65
    def time(self):
66
        """!
67
        @brief (list) Returns sampling times when dynamic is measured during simulation.
68
        
69
        """
70
        if ( (self._ccore_sync_dynamic_pointer is not None) and ( (self._time is None) or (len(self._time) == 0) ) ):
71
            self._time = wrapper.sync_dynamic_get_time(self._ccore_sync_dynamic_pointer);
72
        
73
        return self._time;
74
    
75
    
76
    def __init__(self, phase, time, ccore = None):
77
        """!
78
        @brief Constructor of Sync dynamic.
79
        
80
        @param[in] phase (list): Dynamic of oscillators on each step of simulation. If ccore pointer is specified than it can be ignored.
81
        @param[in] time (list): Simulation time.
82
        @param[in] ccore (ctypes.pointer): Pointer to CCORE sync_dynamic instance in memory.
83
        
84
        """
85
        
86
        self._dynamic = phase;
87
        self._time = time;
88
        self._ccore_sync_dynamic_pointer = ccore;
89
    
90
    
91
    def __del__(self):
92
        """!
93
        @brief Default destructor of Sync dynamic.
94
        
95
        """
96
        if (self._ccore_sync_dynamic_pointer is not None):
97
            wrapper.sync_dynamic_destroy(self._ccore_sync_dynamic_pointer);
98
    
99
    
100
    def __len__(self):
101
        """!
102
        @brief (uint) Returns number of simulation steps that are stored in dynamic.
103
        
104
        """
105
        if (self._ccore_sync_dynamic_pointer is not None):
106
            return wrapper.sync_dynamic_get_size(self._ccore_sync_dynamic_pointer);
107
        
108
        return len(self._dynamic);
109
    
110
    
111
    def __getitem__(self, index):
112
        """!
113
        @brief Indexing of the dynamic.
114
        
115
        """
116
        if (index is 0):
117
            return self.time;
118
        
119
        elif (index is 1):
120
            return self.output;
121
        
122
        else:
123
            raise NameError('Out of range ' + index + ': only indexes 0 and 1 are supported.');
124
125
126
    def allocate_sync_ensembles(self, tolerance = 0.01, indexes = None, iteration = None):
127
        """!
128
        @brief Allocate clusters in line with ensembles of synchronous oscillators where each synchronous ensemble corresponds to only one cluster.
129
               
130
        @param[in] tolerance (double): Maximum error for allocation of synchronous ensemble oscillators.
131
        @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]).
132
        @param[in] iteration (uint): Iteration of simulation that should be used for allocation.
133
        
134
        @return (list) Grours (lists) of indexes of synchronous oscillators.
135
                For example [ [index_osc1, index_osc3], [index_osc2], [index_osc4, index_osc5] ].
136
        
137
        """
138
        
139
        if (self._ccore_sync_dynamic_pointer is not None):
140
            ensembles = wrapper.sync_dynamic_allocate_sync_ensembles(self._ccore_sync_dynamic_pointer, tolerance, iteration);
141
            
142
            if (indexes is not None):
143
                for ensemble in ensembles:
144
                    for index in range(len(ensemble)):
145
                        ensemble[index] = indexes[ ensemble[index] ];
146
                
147
            return ensembles;
148
        
149
        if ( (self._dynamic is None) or (len(self._dynamic) == 0) ):
150
            return [];
151
        
152
        number_oscillators = len(self._dynamic[0]);
153
        last_state = None;
154
        
155
        if (iteration is None):
156
            last_state = self._dynamic[len(self._dynamic) - 1];
157
        else:
158
            last_state = self._dynamic[iteration];
159
        
160
        clusters = [];
161
        if (number_oscillators > 0):
162
            clusters.append([0]);
163
        
164
        for i in range(1, number_oscillators, 1):
165
            cluster_allocated = False;
166
            for cluster in clusters:
167
                for neuron_index in cluster:
168
                    last_state_shifted = abs(last_state[i] - 2 * pi);
169
                    
170
                    if ( ( (last_state[i] < (last_state[neuron_index] + tolerance)) and (last_state[i] > (last_state[neuron_index] - tolerance)) ) or
171
                         ( (last_state_shifted < (last_state[neuron_index] + tolerance)) and (last_state_shifted > (last_state[neuron_index] - tolerance)) ) ):
172
                        cluster_allocated = True;
173
                        
174
                        real_index = i;
175
                        if (indexes is not None):
176
                            real_index = indexes[i];
177
                        
178
                        cluster.append(real_index);
179
                        break;
180
                
181
                if (cluster_allocated == True):
182
                    break;
183
            
184
            if (cluster_allocated == False):
185
                clusters.append([i]);
186
        
187
        return clusters;
188
    
189
    
190
    def allocate_phase_matrix(self, grid_width = None, grid_height = None, iteration = None):
191
        """!
192
        @brief Returns 2D matrix of phase values of oscillators at the specified iteration of simulation.
193
        @details User should ensure correct matrix sizes in line with following expression grid_width x grid_height that should be equal to 
194
                  amount of oscillators otherwise exception is thrown. If grid_width or grid_height are not specified than phase matrix size 
195
                  will by calculated automatically by square root.
196
        
197
        @param[in] grid_width (uint): Width of the allocated matrix.
198
        @param[in] grid_height (uint): Height of the allocated matrix.
199
        @param[in] iteration (uint): Number of iteration of simulation for which correlation matrix should be allocated.
200
                    If iternation number is not specified, the last step of simulation is used for the matrix allocation.
201
        
202
        @return (list) Phase value matrix of oscillators with size [number_oscillators x number_oscillators].
203
        
204
        """
205
        
206
        output_dynamic = self.output;
207
        
208
        if ( (output_dynamic is None) or (len(output_dynamic) == 0) ):
209
            return [];
210
        
211
        current_dynamic = output_dynamic[len(output_dynamic) - 1];
212
        if (iteration is not None):
213
            current_dynamic = output_dynamic[iteration];
214
        
215
        width_matrix = grid_width;
216
        height_matrix = grid_height;
217
        number_oscillators = len(current_dynamic);
218
        if ( (width_matrix is None) or (height_matrix is None) ):
219
            width_matrix = int(math.ceil(math.sqrt(number_oscillators)));
220
            height_matrix = width_matrix;
221
222
        if (number_oscillators != width_matrix * height_matrix):
223
            raise NameError("Impossible to allocate phase matrix with specified sizes, amout of neurons should be equal to grid_width * grid_height.");
224
        
225
        
226
        phase_matrix = [ [ 0.0 for i in range(width_matrix) ] for j in range(height_matrix) ];
227
        for i in range(height_matrix):
228
            for j in range(width_matrix):
229
                phase_matrix[i][j] = current_dynamic[j + i * width_matrix];
230
        
231
        return phase_matrix;
232
    
233
    
234
    def allocate_correlation_matrix(self, iteration = None):
235
        """!
236
        @brief Allocate correlation matrix between oscillators at the specified step of simulation.
237
               
238
        @param[in] iteration (uint): Number of iteration of simulation for which correlation matrix should be allocated.
239
                    If iternation number is not specified, the last step of simulation is used for the matrix allocation.
240
        
241
        @return (list) Correlation matrix between oscillators with size [number_oscillators x number_oscillators].
242
        
243
        """
244
        
245
        if (self._ccore_sync_dynamic_pointer is not None):
246
            return wrapper.sync_dynamic_allocate_correlation_matrix(self._ccore_sync_dynamic_pointer, iteration);
247
        
248
        if ( (self._dynamic is None) or (len(self._dynamic) == 0) ):
249
            return [];
250
        
251
        dynamic = self._dynamic;
252
        current_dynamic = dynamic[len(dynamic) - 1];
253
        
254
        if (iteration is not None):
255
            current_dynamic = dynamic[iteration];
256
        
257
        number_oscillators = len(dynamic[0]);
258
        affinity_matrix = [ [ 0.0 for i in range(number_oscillators) ] for j in range(number_oscillators) ];
259
        
260
        for i in range(number_oscillators):
261
            for j in range(number_oscillators):
262
                phase1 = current_dynamic[i];
263
                phase2 = current_dynamic[j];
264
                
265
                affinity_matrix[i][j] = abs(math.sin(phase1 - phase2));
266
                
267
        return affinity_matrix;
268
269
270
    def calculate_order_parameter(self, start_iteration = None, stop_iteration = None):
271
        """!
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
272
        @brief Calculates level of global synchorization (order parameter).
273
        @details This parameter is tend 1.0 when the oscillatory network close to global synchronization and it tend to 0.0 when 
274
                  desynchronization is observed in the network. Order parameter is calculated using following equation:
275
                  
276
                  \f[
277
                  r_{c}=\frac{1}{Ne^{i\varphi }}\sum_{j=0}^{N}e^{i\theta_{j}};
278
                  \f]
279
                  
280
                  where \f$\varphi\f$ is a average phase coordinate in the network, \f$N\f$ is an amount of oscillators in the network.
281
        
282
        @param[in] start_iteration (uint): The first iteration that is used for calculation, if 'None' then the last iteration is used.
283
        @param[in] stop_iteration (uint): The last iteration that is used for calculation, if 'None' then 'start_iteration' + 1 is used.
284
        
285
        Example:
286
        @code
287
            oscillatory_network = sync(16, type_conn = conn_type.ALL_TO_ALL);
288
            output_dynamic = oscillatory_network.simulate_static(100, 10);
289
            
290
            print("Order parameter at the last step: ", output_dynamic.calculate_order_parameter());
291
            print("Order parameter at the first step:", output_dynamic.calculate_order_parameter(0));
292
            print("Order parameter evolution between 40 and 50 steps:", output_dynamic.calculate_order_parameter(40, 50));
293
        @endcode
294
        
295
        @return (list) List of levels of global synchronization (order parameter evolution).
296
        
297
        @see calculate_sync_order()
298
        
299
        """
300
        
301
        if (start_iteration is None):
302
            start_iteration = len(self._dynamic) - 1;
303
        
304
        if (stop_iteration is None):
305
            stop_iteration = start_iteration + 1;
306
        
307
        sequence_order = [];
308
        for index in range(start_iteration, stop_iteration):
309
            sequence_order.append(sync_dynamic.calculate_sync_order(self._dynamic[index]));
310
        
311
        return sequence_order;
312
313
314
    @staticmethod
315
    def calculate_sync_order(oscillator_phases):
316
        """!
317
        @brief Calculates level of global synchorization (order parameter) for input phases.
318
        @details This parameter is tend 1.0 when the oscillatory network close to global synchronization and it tend to 0.0 when 
319
                  desynchronization is observed in the network.
320
        
321
        @param[in] oscillator_phases (list): List of oscillator phases that are used for level of global synchronization.
322
        
323
        @return (double) Level of global synchronization (order parameter).
324
        
325
        @see calculate_order_parameter()
326
        
327
        """
328
        
329
        exp_amount = 0.0;
330
        average_phase = 0.0;
331
332
        for phase in oscillator_phases:
333
            exp_amount += math.expm1( abs(1j * phase) );
334
            average_phase += phase;
335
        
336
        exp_amount /= len(oscillator_phases);
337
        average_phase = math.expm1( abs(1j * (average_phase / len(oscillator_phases))) );
338
        
339
        return abs(average_phase) / abs(exp_amount);
340
341
342
class sync_visualizer:
343
    """!
344
    @brief Visualizer of output dynamic of sync network (Sync).
345
    
346
    """
347
348
    @staticmethod
349
    def show_output_dynamic(sync_output_dynamic):
350
        """!
351
        @brief Shows output dynamic (output of each oscillator) during simulation.
352
        
353
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
354
        
355
        @see show_output_dynamics
356
        
357
        """
358
        
359
        draw_dynamics(sync_output_dynamic.time, sync_output_dynamic.output, x_title = "t", y_title = "phase", y_lim = [0, 2 * 3.14]);
360
    
361
    
362
    @staticmethod
363
    def show_output_dynamics(sync_output_dynamics):
364
        """!
365
        @brief Shows several output dynamics (output of each oscillator) during simulation.
366
        @details Each dynamic is presented on separate plot.
367
        
368
        @param[in] sync_output_dynamics (list): list of output dynamics 'sync_dynamic' of the Sync network.
369
        
370
        @see show_output_dynamic
371
        
372
        """
373
        
374
        draw_dynamics_set(sync_output_dynamics, "t", "phase", None, [0, 2 * 3.14], False, False);
375
    
376
    
377
    @staticmethod
378
    def show_correlation_matrix(sync_output_dynamic, iteration = None):
379
        """!
380 View Code Duplication
        @brief Shows correlation matrix between oscillators at the specified iteration.
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
381
        
382
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
383
        @param[in] iteration (uint): Number of interation of simulation for which correlation matrix should be allocated.
384
                                      If iternation number is not specified, the last step of simulation is used for the matrix allocation.
385
        
386
        """
387
        
388
        _ = plt.figure();
389
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(iteration);
390
        
391
        plt.imshow(correlation_matrix, cmap = plt.get_cmap('cool'), interpolation='kaiser', vmin = 0.0, vmax = 1.0); 
392
        plt.show();
393
394
395
    @staticmethod
396
    def show_phase_matrix(sync_output_dynamic, grid_width = None, grid_height = None, iteration = None):
397
        """!
398
        @brief Shows 2D matrix of phase values of oscillators at the specified iteration.
399
        @details User should ensure correct matrix sizes in line with following expression grid_width x grid_height that should be equal to 
400
                  amount of oscillators otherwise exception is thrown. If grid_width or grid_height are not specified than phase matrix size 
401
                  will by calculated automatically by square root.
402
        
403
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network whose phase matrix should be shown.
404
        @param[in] grid_width (uint): Width of the phase matrix.
405
        @param[in] grid_height (uint): Height of the phase matrix.
406
        @param[in] iteration (uint): Number of iteration of simulation for which correlation matrix should be allocated.
407
                    If iternation number is not specified, the last step of simulation is used for the matrix allocation.
408
        
409
        """
410
        
411
        _ = plt.figure();
412
        phase_matrix = sync_output_dynamic.allocate_phase_matrix(grid_width, grid_height, iteration);
413
        
414 View Code Duplication
        plt.imshow(phase_matrix, cmap = plt.get_cmap('jet'), interpolation='kaiser', vmin = 0.0, vmax = 2.0 * math.pi); 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
415
        plt.show();
416
417
418
    @staticmethod
419
    def show_order_parameter(sync_output_dynamic, start_iteration = None, stop_iteration = None):
420
        """!
421
        @brief Shows evolution of order parameter (level of global synchronization in the network).
422
        
423
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network whose evolution of global synchronization should be visualized.
424
        @param[in] start_iteration (uint): The first iteration that is used for calculation, if 'None' then the first is used
425
        @param[in] stop_iteration (uint): The last iteration that is used for calculation, if 'None' then the last is used.
426
        
427
        """
428
        
429
        if (start_iteration is None):
430
            start_iteration = 0;
431
        
432
        if (stop_iteration is None):
433
            stop_iteration = len(sync_output_dynamic);
434
        
435
        order_parameter = sync_output_dynamic.calculate_order_parameter(start_iteration, stop_iteration);
436
        axis = plt.subplot(111);
437
        plt.plot(sync_output_dynamic.time[start_iteration:stop_iteration], order_parameter, 'b-', linewidth = 2.0);
438
        set_ax_param(axis, "t", "R (order parameter)", None, [0.0, 1.05]);
439
        
440
        plt.show();
441
442
443
    @staticmethod
444
    def animate_output_dynamic(sync_output_dynamic, animation_velocity = 75, save_movie = None):
445
        """!
446
        @brief Shows animation of output dynamic (output of each oscillator) during simulation on a circle from [0; 2pi].
447
        
448
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
449
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
450
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
451
        
452
        """
453
        
454
        figure = plt.figure();
455
        
456
        dynamic = sync_output_dynamic.output[0];
457
        artist, = plt.polar(dynamic, [1.0] * len(dynamic), 'o', color = 'blue');
458
        
459
        def init_frame():
460
            return [ artist ];
461
        
462
        def frame_generation(index_dynamic):
463
            dynamic = sync_output_dynamic.output[index_dynamic];
464
            artist.set_data(dynamic, [1.0] * len(dynamic));
465
            
466
            return [ artist ];
467
        
468
        phase_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
469
470
        if (save_movie is not None):
471
            phase_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
472
        else:
473
            plt.show();
474
475
476
    @staticmethod
477
    def animate_correlation_matrix(sync_output_dynamic, animation_velocity = 75, colormap = 'cool', save_movie = None):
478
        """!
479
        @brief Shows animation of correlation matrix between oscillators during simulation.
480
        
481
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
482
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
483
        @param[in] colormap (string): Name of colormap that is used by matplotlib ('gray', 'pink', 'cool', spring', etc.).
484
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
485
        
486
        """
487
        
488
        figure = plt.figure();
489
        
490
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(0);
491
        artist = plt.imshow(correlation_matrix, cmap = plt.get_cmap(colormap), interpolation='kaiser', hold = True, vmin = 0.0, vmax = 1.0);
492
        
493
        def init_frame(): 
494
            return [ artist ];
495
        
496
        def frame_generation(index_dynamic):
497
            correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(index_dynamic);
498
            artist.set_data(correlation_matrix);
499
            
500
            return [ artist ];
501
502
        correlation_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), init_func = init_frame, interval = animation_velocity , repeat_delay = 1000, blit = True);
503
        
504
        if (save_movie is not None):
505
            correlation_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
506
        else:
507
            plt.show();
508
509
510
    @staticmethod
511
    def animate_phase_matrix(sync_output_dynamic, grid_width = None, grid_height = None, animation_velocity = 75, colormap = 'jet', save_movie = None):
512
        """!
513
        @brief Shows animation of phase matrix between oscillators during simulation on 2D stage.
514
        @details If grid_width or grid_height are not specified than phase matrix size will by calculated automatically by square root.
515
        
516
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
517
        @param[in] grid_width (uint): Width of the phase matrix.
518
        @param[in] grid_height (uint): Height of the phase matrix.
519
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
520
        @param[in] colormap (string): Name of colormap that is used by matplotlib ('gray', 'pink', 'cool', spring', etc.).
521
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
522
        
523
        """
524
        
525
        figure = plt.figure();
526
        
527
        def init_frame(): 
528
            return frame_generation(0);
529
        
530
        def frame_generation(index_dynamic):
531
            figure.clf();
532
            axis = figure.add_subplot(111);
533
            
534
            phase_matrix = sync_output_dynamic.allocate_phase_matrix(grid_width, grid_height, index_dynamic);
535
            axis.imshow(phase_matrix, cmap = plt.get_cmap(colormap), interpolation='kaiser', vmin = 0.0, vmax = 2.0 * math.pi);
536
            artist = figure.gca();
537
            
538
            return [ artist ];
539
540
        phase_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), init_func = init_frame, interval = animation_velocity , repeat_delay = 1000);
541
        
542
        if (save_movie is not None):
543
            phase_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
544
        else:
545
            plt.show();
546
547
548
549
    @staticmethod
550
    def animate(sync_output_dynamic, title = None, save_movie = None):
551
        """!
552
        @brief Shows animation of phase coordinates and animation of correlation matrix together for the Sync dynamic output on the same figure.
553
        
554
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
555
        @param[in] title (string): Title of the animation that is displayed on a figure if it is specified.
556
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
557
        
558
        """
559
        
560
        dynamic = sync_output_dynamic.output[0];
561
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(0);
562
        
563
        figure = plt.figure(1);
564
        if (title is not None):
565
            figure.suptitle(title, fontsize = 26, fontweight = 'bold')
566
        
567
        ax1 = figure.add_subplot(121, projection='polar');
568
        ax2 = figure.add_subplot(122);
569
        
570
        artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
571
        artist2 = ax2.imshow(correlation_matrix, cmap = plt.get_cmap('Accent'), interpolation='kaiser');
572
        
573
        def init_frame():
574
            return [ artist1, artist2 ];
575
576
        def frame_generation(index_dynamic):
577
            dynamic = sync_output_dynamic.output[index_dynamic];
578
            artist1.set_data(dynamic, [1.0] * len(dynamic));
579
            
580
            correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(index_dynamic);
581
            artist2.set_data(correlation_matrix);
582
            
583
            return [ artist1, artist2 ];
584
        
585
        dynamic_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), interval = 75, init_func = init_frame, repeat_delay = 5000);
586
        
587
        if (save_movie is not None):
588
            dynamic_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
589
        else:
590
            plt.show();
591
592
593
594
class sync_network(network):
595
    """!
596
    @brief Model of oscillatory network that is based on the Kuramoto model of synchronization.
597
    
598
    """
599
600
    def __init__(self, num_osc, weight = 1, frequency = 0, type_conn = conn_type.ALL_TO_ALL, representation = conn_represent.MATRIX, initial_phases = initial_type.RANDOM_GAUSSIAN, ccore = False):
601
        """!
602
        @brief Constructor of oscillatory network is based on Kuramoto model.
603
        
604
        @param[in] num_osc (uint): Number of oscillators in the network.
605
        @param[in] weight (double): Coupling strength of the links between oscillators.
606
        @param[in] frequency (double): Multiplier of internal frequency of the oscillators.
607
        @param[in] type_conn (conn_type): Type of connection between oscillators in the network (all-to-all, grid, bidirectional list, etc.).
608
        @param[in] representation (conn_represent): Internal representation of connection in the network: matrix or list.
609
        @param[in] initial_phases (initial_type): Type of initialization of initial phases of oscillators (random, uniformly distributed, etc.).
610
        @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).
611
        
612
        """
613
        
614
        self._ccore_network_pointer = None;      # Pointer to CCORE Sync implementation of the network.
615
        
616
        if (ccore is True):
617
            self._ccore_network_pointer = wrapper.sync_create_network(num_osc, weight, frequency, type_conn, initial_phases);
618
        else:   
619
            super().__init__(num_osc, type_conn, representation);
620
            
621
            self._weight = weight;
622
            
623
            self._phases = list();
624
            self._freq = list();
625
            
626
            random.seed();
627
            for index in range(0, num_osc, 1):
628
                if (initial_phases == initial_type.RANDOM_GAUSSIAN):
629
                    self._phases.append(random.random() * 2 * pi);
630
                
631
                elif (initial_phases == initial_type.EQUIPARTITION):
632
                    self._phases.append( pi / num_osc * index);
633
                
634
                self._freq.append(random.random() * frequency);
635
636
637
    def __del__(self):
638
        """!
639
        @brief Destructor of oscillatory network is based on Kuramoto model.
640
        
641
        """
642
        
643
        if (self._ccore_network_pointer is not None):
644
            wrapper.sync_destroy_network(self._ccore_network_pointer);
645
            self._ccore_network_pointer = None;
646
    
647
    
648
    def sync_order(self):
649
        """!
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \s was found. Did you maybe forget to add an r prefix?

Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with r or R are they interpreted as regular expressions.

The escape sequence that was used indicates that you might have intended to write a regular expression.

Learn more about the available escape sequences. in the Python documentation.

Loading history...
650
        @brief Calculates current level of global synchorization (order parameter) in the network.
651
        @details This parameter is tend 1.0 when the oscillatory network close to global synchronization and it tend to 0.0 when 
652
                  desynchronization is observed in the network. Order parameter is calculated using following equation:
653
                  
654
                  \f[
655
                  r_{c}=\frac{1}{Ne^{i\varphi }}\sum_{j=0}^{N}e^{i\theta_{j}};
656
                  \f]
657
                  
658
                  where \f$\varphi\f$ is a average phase coordinate in the network, \f$N\f$ is an amount of oscillators in the network.
659
        
660
        Example:
661
        @code
662
            oscillatory_network = sync(16, type_conn = conn_type.ALL_TO_ALL);
663
            output_dynamic = oscillatory_network.simulate_static(100, 10);
664
            
665
            if (oscillatory_network.sync_order() < 0.9): print("Global synchronization is not reached yet.");
666
            else: print("Global synchronization is reached.");
667
        @endcode
668
        
669
        @return (double) Level of global synchronization (order parameter).
670
        
671
        @see sync_local_order()
672
        
673
        """
674
        
675
        if (self._ccore_network_pointer is not None):
676
            return wrapper.sync_order(self._ccore_network_pointer);
677
        
678
        return sync_dynamic.calculate_sync_order(self._phases);
679
680
681
    def sync_local_order(self):
682
        """!
683
        @brief Calculates current level of local (partial) synchronization in the network.
684
        
685
        @return (double) Level of local (partial) synchronization.
686
        
687
        @see sync_order()
688
        
689
        """
690
        
691
        if (self._ccore_network_pointer is not None):
692
            return wrapper.sync_local_order(self._ccore_network_pointer);
693
        
694
        exp_amount = 0.0;
695
        num_neigh = 0;
696
        
697
        for i in range(0, self._num_osc, 1):
698
            for j in range(0, self._num_osc, 1):
699
                if (self.has_connection(i, j) == True):
700
                    exp_amount += math.exp(-abs(self._phases[j] - self._phases[i]));
701
                    num_neigh += 1;
702
        
703
        if (num_neigh == 0):
704
            num_neigh = 1;
705
        
706
        return exp_amount / num_neigh;
707
708
709
    def _phase_kuramoto(self, teta, t, argv):
0 ignored issues
show
Unused Code introduced by
The argument t seems to be unused.
Loading history...
710
        """!
711
        @brief Returns result of phase calculation for specified oscillator in the network.
712
        
713
        @param[in] teta (double): Phase of the oscillator that is differentiated.
714
        @param[in] t (double): Current time of simulation.
715
        @param[in] argv (tuple): Index of the oscillator in the list.
716
        
717
        @return (double) New phase for specified oscillator (don't assign here).
718
        
719
        """
720
        
721
        index = argv;
722
        phase = 0;
723
        for k in range(0, self._num_osc):
724
            if (self.has_connection(index, k) == True):
725
                phase += math.sin(self._phases[k] - teta);
726
            
727
        return ( self._freq[index] + (phase * self._weight / self._num_osc) );
728
729
730
    def simulate(self, steps, time, solution = solve_type.FAST, collect_dynamic = True):
731
        """!
732
        @brief Performs static simulation of Sync oscillatory network.
733
        
734
        @param[in] steps (uint): Number steps of simulations during simulation.
735
        @param[in] time (double): Time of simulation.
736
        @param[in] solution (solve_type): Type of solution (solving).
737
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
738
        
739
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
740
                otherwise returns only last values (last step of simulation) of dynamic.
741
        
742
        @see simulate_dynamic()
743
        @see simulate_static()
744
        
745
        """
746
        
747
        return self.simulate_static(steps, time, solution, collect_dynamic);
748
749
750
    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):
751
        """!
752
        @brief Performs dynamic simulation of the network until stop condition is not reached. Stop condition is defined by input argument 'order'.
753
        
754
        @param[in] order (double): Order of process synchronization, distributed 0..1.
755
        @param[in] solution (solve_type): Type of solution.
756
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
757
        @param[in] step (double): Time step of one iteration of simulation.
758
        @param[in] int_step (double): Integration step, should be less than step.
759
        @param[in] threshold_changes (double): Additional stop condition that helps prevent infinite simulation, defines limit of changes of oscillators between current and previous steps.
760
        
761
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
762
                otherwise returns only last values (last step of simulation) of dynamic.
763
        
764
        @see simulate()
765
        @see simulate_static()
766
        
767
        """
768
        
769
        if (self._ccore_network_pointer is not None):
770
            ccore_instance_dynamic = wrapper.sync_simulate_dynamic(self._ccore_network_pointer, order, solution, collect_dynamic, step, int_step, threshold_changes);
771
            return sync_dynamic(None, None, ccore_instance_dynamic);
772
        
773
        # For statistics and integration
774
        time_counter = 0;
775
        
776
        # Prevent infinite loop. It's possible when required state cannot be reached.
777
        previous_order = 0;
778
        current_order = self.sync_local_order();
779
        
780
        # If requested input dynamics
781
        dyn_phase = [];
782
        dyn_time = [];
783
        if (collect_dynamic == True):
784
            dyn_phase.append(self._phases);
785
            dyn_time.append(0);
786
        
787
        # Execute until sync state will be reached
788
        while (current_order < order):
789
            # update states of oscillators
790
            self._phases = self._calculate_phases(solution, time_counter, step, int_step);
791
            
792
            # update time
793
            time_counter += step;
794
            
795
            # if requested input dynamic
796
            if (collect_dynamic == True):
797
                dyn_phase.append(self._phases);
798
                dyn_time.append(time_counter);
799
                
800
            # update orders
801
            previous_order = current_order;
802
            current_order = self.sync_local_order();
803
            
804
            # hang prevention
805
            if (abs(current_order - previous_order) < threshold_changes):
806
                # print("Warning: sync_network::simulate_dynamic - simulation is aborted due to low level of convergence rate (order = " + str(current_order) + ").");
807
                break;
808
            
809
        if (collect_dynamic != True):
810
            dyn_phase.append(self._phases);
811
            dyn_time.append(time_counter);
812
813
        output_sync_dynamic = sync_dynamic(dyn_phase, dyn_time, None);
814
        return output_sync_dynamic;
815
816
817
    def simulate_static(self, steps, time, solution = solve_type.FAST, collect_dynamic = False):
818
        """!
819
        @brief Performs static simulation of oscillatory network.
820
        
821
        @param[in] steps (uint): Number steps of simulations during simulation.
822
        @param[in] time (double): Time of simulation.
823
        @param[in] solution (solve_type): Type of solution.
824
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
825
        
826
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
827
                otherwise returns only last values (last step of simulation) of dynamic.
828
        
829
        @see simulate()
830
        @see simulate_dynamic()
831
        
832
        """
833
        
834
        if (self._ccore_network_pointer is not None):
835
            ccore_instance_dynamic = wrapper.sync_simulate_static(self._ccore_network_pointer, steps, time, solution, collect_dynamic);
836
            return sync_dynamic(None, None, ccore_instance_dynamic);
837
        
838
        dyn_phase = [];
839
        dyn_time = [];
840
        
841
        if (collect_dynamic == True):
842
            dyn_phase.append(self._phases);
843
            dyn_time.append(0);
844
        
845
        step = time / steps;
846
        int_step = step / 10.0;
847
        
848
        for t in numpy.arange(step, time + step, step):
849
            # update states of oscillators
850
            self._phases = self._calculate_phases(solution, t, step, int_step);
851
            
852
            # update states of oscillators
853
            if (collect_dynamic == True):
854
                dyn_phase.append(self._phases);
855
                dyn_time.append(t);
856
        
857
        if (collect_dynamic != True):
858
            dyn_phase.append(self._phases);
859
            dyn_time.append(time);
860
                        
861
        output_sync_dynamic = sync_dynamic(dyn_phase, dyn_time);
862
        return output_sync_dynamic;
863
864
865
    def _calculate_phases(self, solution, t, step, int_step):
866
        """!
867
        @brief Calculates new phases for oscillators in the network in line with current step.
868
        
869
        @param[in] solution (solve_type): Type solver of the differential equation.
870
        @param[in] t (double): Time of simulation.
871
        @param[in] step (double): Step of solution at the end of which states of oscillators should be calculated.
872
        @param[in] int_step (double): Step differentiation that is used for solving differential equation.
873
        
874
        @return (list) New states (phases) for oscillators.
875
        
876
        """
877
        
878
        next_phases = [0.0] * self._num_osc;    # new oscillator _phases
879
        
880
        for index in range (0, self._num_osc, 1):
881
            if (solution == solve_type.FAST):
882
                result = self._phases[index] + self._phase_kuramoto(self._phases[index], 0, index);
883
                next_phases[index] = self._phase_normalization(result);
884
                
885
            elif (solution == solve_type.RK4):
886
                result = odeint(self._phase_kuramoto, self._phases[index], numpy.arange(t - step, t, int_step), (index , ));
887
                next_phases[index] = self._phase_normalization(result[len(result) - 1][0]);
888
            
889
            else:
890
                raise NameError("Solver '" + solution + "' is not supported");
891
        
892
        return next_phases;
893
        
894
895
    def _phase_normalization(self, teta):
896
        """!
897
        @brief Normalization of phase of oscillator that should be placed between [0; 2 * pi].
898
        
899
        @param[in] teta (double): phase of oscillator.
900
        
901
        @return (double) Normalized phase.
902
        
903
        """
904
905
        norm_teta = teta;
906
        while (norm_teta > (2.0 * pi)) or (norm_teta < 0):
907
            if (norm_teta > (2.0 * pi)):
908
                norm_teta -= 2.0 * pi;
909
            else:
910
                norm_teta += 2.0 * pi;
911
        
912
        return norm_teta;
913