Completed
Push — 0.7.dev ( 0c7dff...8e244d )
by Andrei
59s
created

sync_visualizer   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 286
Duplicated Lines 12.24 %

Importance

Changes 0
Metric Value
dl 35
loc 286
rs 10
c 0
b 0
f 0
wmc 26

13 Methods

Rating   Name   Duplication   Size   Complexity  
A show_local_order_parameter() 0 19 1
A show_output_dynamic() 12 12 1
A frame_generation() 0 5 1
B animate() 0 42 5
A show_order_parameter() 0 19 1
B animate_correlation_matrix() 0 32 4
A __get_start_stop_iterations() 0 19 3
A show_phase_matrix() 0 21 1
B animate_output_dynamic() 0 31 4
B animate_phase_matrix() 0 36 4
A show_output_dynamics() 13 13 1
A init_frame() 0 2 1
A show_correlation_matrix() 0 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
        (start_iteration, stop_iteration) = self.__get_start_stop_iterations(start_iteration, stop_iteration);
302
        
303
        sequence_order = [];
304
        for index in range(start_iteration, stop_iteration):
305
            sequence_order.append(sync_dynamic.calculate_sync_order(self.output[index]));
306
        
307
        return sequence_order;
308
309
310
    @staticmethod
311
    def calculate_sync_order(oscillator_phases):
312
        """!
313
        @brief Calculates level of global synchorization (order parameter) for input phases.
314
        @details This parameter is tend 1.0 when the oscillatory network close to global synchronization and it tend to 0.0 when 
315
                  desynchronization is observed in the network.
316
        
317
        @param[in] oscillator_phases (list): List of oscillator phases that are used for level of global synchronization.
318
        
319
        @return (double) Level of global synchronization (order parameter).
320
        
321
        @see calculate_order_parameter()
322
        
323
        """
324
        
325
        exp_amount = 0.0;
326
        average_phase = 0.0;
327
328
        for phase in oscillator_phases:
329
            exp_amount += math.expm1( abs(1j * phase) );
330
            average_phase += phase;
331
        
332
        exp_amount /= len(oscillator_phases);
333
        average_phase = math.expm1( abs(1j * (average_phase / len(oscillator_phases))) );
334
        
335
        return abs(average_phase) / abs(exp_amount);
336
337
338
    def calculate_local_order_parameter(self, oscillatory_network, start_iteration = None, stop_iteration = None):
339
        """!
0 ignored issues
show
Bug introduced by
A suspicious escape sequence \l 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...
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...
340
        @brief Calculates local order parameter.
341
        @details Local order parameter or so-called level of local or partial synchronization is calculated by following expression:
342
        
343
        \f[
344
        r_{c}=\left | \sum_{i=0}^{N} \frac{1}{N_{i}} \sum_{j=0}e^{ \theta_{j} - \theta_{i} } \right |;
345
        \f]
346
        
347
        where N - total amount of oscillators in the network and \f$N_{i}\f$ - amount of neighbors of oscillator with index \f$i\f$.
348
        
349
        @param[in] oscillatory_network (sync): Sync oscillatory network whose structure of connections is required for calculation.
350
        @param[in] start_iteration (uint): The first iteration that is used for calculation, if 'None' then the last iteration is used.
351
        @param[in] stop_iteration (uint): The last iteration that is used for calculation, if 'None' then 'start_iteration' + 1 is used.
352
        
353
        @return (list) List of levels of local (partial) synchronization (local order parameter evolution).
354
        
355
        """
356
357
        (start_iteration, stop_iteration) = self.__get_start_stop_iterations(start_iteration, stop_iteration);
358
        
359
        sequence_local_order = [];
360
        for index in range(start_iteration, stop_iteration):
361
            sequence_local_order.append(sync_dynamic.calculate_local_sync_order(self.output[index], oscillatory_network));
362
        
363
        return sequence_local_order;
364
365
366
    @staticmethod
367
    def calculate_local_sync_order(oscillator_phases, oscillatory_network):
368
        """!
369
        @brief Calculates level of local synchorization (local order parameter) for input phases for the specified network.
370
        @details This parameter is tend 1.0 when the oscillatory network close to local synchronization and it tend to 0.0 when 
371
                  desynchronization is observed in the network.
372
        
373
        @param[in] oscillator_phases (list): List of oscillator phases that are used for level of local (partial) synchronization.
374
        @param[in] oscillatory_network (sync): Instance of oscillatory network whose connections are required for calculation.
375
        
376
        @return (double) Level of local synchronization (local order parameter).
377
        
378
        """
379
        
380 View Code Duplication
        exp_amount = 0.0;
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
381
        num_neigh = 0.0;
382
        
383
        for i in range(0, len(oscillatory_network), 1):
384
            for j in range(0, len(oscillatory_network), 1):
385
                if (oscillatory_network.has_connection(i, j) == True):
386
                    exp_amount += math.exp(-abs(oscillator_phases[j] - oscillator_phases[i]));
387
                    num_neigh += 1.0;
388
        
389
        if (num_neigh == 0):
390
            num_neigh = 1.0;
391
        
392
        return exp_amount / num_neigh;
393
394
395
    def __get_start_stop_iterations(self, start_iteration, stop_iteration):
396
        """!
397
        @brief Aplly rules for start_iteration and stop_iteration parameters.
398
399
        @param[in] start_iteration (uint): The first iteration that is used for calculation.
400
        @param[in] stop_iteration (uint): The last iteration that is used for calculation.
401
        
402
        @return (tuple) New the first iteration and the last.
403
        
404
        """
405
        if (start_iteration is None):
406
            start_iteration = len(self._dynamic) - 1;
407
        
408
        if (stop_iteration is None):
409
            stop_iteration = start_iteration + 1;
410
        
411
        return (start_iteration, stop_iteration);
412
413
414 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
415
class sync_visualizer:
416
    """!
417
    @brief Visualizer of output dynamic of sync network (Sync).
418
    
419
    """
420
421
    @staticmethod
422
    def show_output_dynamic(sync_output_dynamic):
423
        """!
424
        @brief Shows output dynamic (output of each oscillator) during simulation.
425
        
426
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
427
        
428
        @see show_output_dynamics
429
        
430
        """
431
        
432
        draw_dynamics(sync_output_dynamic.time, sync_output_dynamic.output, x_title = "t", y_title = "phase", y_lim = [0, 2 * 3.14]);
433
    
434
    
435
    @staticmethod
436
    def show_output_dynamics(sync_output_dynamics):
437
        """!
438
        @brief Shows several output dynamics (output of each oscillator) during simulation.
439
        @details Each dynamic is presented on separate plot.
440
        
441
        @param[in] sync_output_dynamics (list): list of output dynamics 'sync_dynamic' of the Sync network.
442
        
443
        @see show_output_dynamic
444
        
445
        """
446
        
447
        draw_dynamics_set(sync_output_dynamics, "t", "phase", None, [0, 2 * 3.14], False, False);
448
    
449
    
450
    @staticmethod
451
    def show_correlation_matrix(sync_output_dynamic, iteration = None):
452
        """!
453
        @brief Shows correlation matrix between oscillators at the specified iteration.
454
        
455
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
456
        @param[in] iteration (uint): Number of interation of simulation for which correlation matrix should be allocated.
457
                                      If iternation number is not specified, the last step of simulation is used for the matrix allocation.
458
        
459
        """
460
        
461
        _ = plt.figure();
462
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(iteration);
463
        
464
        plt.imshow(correlation_matrix, cmap = plt.get_cmap('cool'), interpolation='kaiser', vmin = 0.0, vmax = 1.0); 
465
        plt.show();
466
467
468
    @staticmethod
469
    def show_phase_matrix(sync_output_dynamic, grid_width = None, grid_height = None, iteration = None):
470
        """!
471
        @brief Shows 2D matrix of phase values of oscillators at the specified iteration.
472
        @details User should ensure correct matrix sizes in line with following expression grid_width x grid_height that should be equal to 
473
                  amount of oscillators otherwise exception is thrown. If grid_width or grid_height are not specified than phase matrix size 
474
                  will by calculated automatically by square root.
475
        
476
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network whose phase matrix should be shown.
477
        @param[in] grid_width (uint): Width of the phase matrix.
478
        @param[in] grid_height (uint): Height of the phase matrix.
479
        @param[in] iteration (uint): Number of iteration of simulation for which correlation matrix should be allocated.
480
                    If iternation number is not specified, the last step of simulation is used for the matrix allocation.
481
        
482
        """
483
        
484
        _ = plt.figure();
485
        phase_matrix = sync_output_dynamic.allocate_phase_matrix(grid_width, grid_height, iteration);
486
        
487
        plt.imshow(phase_matrix, cmap = plt.get_cmap('jet'), interpolation='kaiser', vmin = 0.0, vmax = 2.0 * math.pi); 
488
        plt.show();
489
490
491
    @staticmethod
492
    def show_order_parameter(sync_output_dynamic, start_iteration = None, stop_iteration = None):
493
        """!
494
        @brief Shows evolution of order parameter (level of global synchronization in the network).
495
        
496
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network whose evolution of global synchronization should be visualized.
497
        @param[in] start_iteration (uint): The first iteration that is used for calculation, if 'None' then the first is used
498
        @param[in] stop_iteration (uint): The last iteration that is used for calculation, if 'None' then the last is used.
499
        
500
        """
501
        
502
        (start_iteration, stop_iteration) = sync_visualizer.__get_start_stop_iterations(sync_output_dynamic, start_iteration, stop_iteration);
503
        
504
        order_parameter = sync_output_dynamic.calculate_order_parameter(start_iteration, stop_iteration);
505
        axis = plt.subplot(111);
506
        plt.plot(sync_output_dynamic.time[start_iteration:stop_iteration], order_parameter, 'b-', linewidth = 2.0);
507
        set_ax_param(axis, "t", "R (order parameter)", None, [0.0, 1.05]);
508
        
509
        plt.show();
510
511
512
    @staticmethod
513
    def show_local_order_parameter(sync_output_dynamic, oscillatory_network, start_iteration = None, stop_iteration = None):
514
        """!
515
        @brief Shows evolution of local order parameter (level of local synchronization in the network).
516
        
517
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network whose evolution of global synchronization should be visualized.
518
        @param[in] oscillatory_network (sync): Sync oscillatory network whose structure of connections is required for calculation.
519
        @param[in] start_iteration (uint): The first iteration that is used for calculation, if 'None' then the first is used
520
        @param[in] stop_iteration (uint): The last iteration that is used for calculation, if 'None' then the last is used.
521
        
522
        """
523
        (start_iteration, stop_iteration) = sync_visualizer.__get_start_stop_iterations(sync_output_dynamic, start_iteration, stop_iteration);
524
        
525
        order_parameter = sync_output_dynamic.calculate_local_order_parameter(oscillatory_network, start_iteration, stop_iteration);
526
        axis = plt.subplot(111);
527
        plt.plot(sync_output_dynamic.time[start_iteration:stop_iteration], order_parameter, 'b-', linewidth = 2.0);
528
        set_ax_param(axis, "t", "R (local order parameter)", None, [0.0, 1.05]);
529
        
530
        plt.show();
531
532
533
    @staticmethod
534
    def animate_output_dynamic(sync_output_dynamic, animation_velocity = 75, save_movie = None):
535
        """!
536
        @brief Shows animation of output dynamic (output of each oscillator) during simulation on a circle from [0; 2pi].
537
        
538
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
539
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
540
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
541
        
542
        """
543
        
544
        figure = plt.figure();
545
        
546
        dynamic = sync_output_dynamic.output[0];
547
        artist, = plt.polar(dynamic, [1.0] * len(dynamic), 'o', color = 'blue');
548
        
549
        def init_frame():
550
            return [ artist ];
551
        
552
        def frame_generation(index_dynamic):
553
            dynamic = sync_output_dynamic.output[index_dynamic];
554
            artist.set_data(dynamic, [1.0] * len(dynamic));
555
            
556
            return [ artist ];
557
        
558
        phase_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
559
560
        if (save_movie is not None):
561
            phase_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
562
        else:
563
            plt.show();
564
565
566
    @staticmethod
567
    def animate_correlation_matrix(sync_output_dynamic, animation_velocity = 75, colormap = 'cool', save_movie = None):
568
        """!
569
        @brief Shows animation of correlation matrix between oscillators during simulation.
570
        
571
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
572
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
573
        @param[in] colormap (string): Name of colormap that is used by matplotlib ('gray', 'pink', 'cool', spring', etc.).
574
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
575
        
576
        """
577
        
578
        figure = plt.figure();
579
        
580
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(0);
581
        artist = plt.imshow(correlation_matrix, cmap = plt.get_cmap(colormap), interpolation='kaiser', hold = True, vmin = 0.0, vmax = 1.0);
582
        
583
        def init_frame(): 
584
            return [ artist ];
585
        
586
        def frame_generation(index_dynamic):
587
            correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(index_dynamic);
588
            artist.set_data(correlation_matrix);
589
            
590
            return [ artist ];
591
592
        correlation_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), init_func = init_frame, interval = animation_velocity , repeat_delay = 1000, blit = True);
593
        
594
        if (save_movie is not None):
595
            correlation_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
596
        else:
597
            plt.show();
598
599
600
    @staticmethod
601
    def animate_phase_matrix(sync_output_dynamic, grid_width = None, grid_height = None, animation_velocity = 75, colormap = 'jet', save_movie = None):
602
        """!
603
        @brief Shows animation of phase matrix between oscillators during simulation on 2D stage.
604
        @details If grid_width or grid_height are not specified than phase matrix size will by calculated automatically by square root.
605
        
606
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
607
        @param[in] grid_width (uint): Width of the phase matrix.
608
        @param[in] grid_height (uint): Height of the phase matrix.
609
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
610
        @param[in] colormap (string): Name of colormap that is used by matplotlib ('gray', 'pink', 'cool', spring', etc.).
611
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
612
        
613
        """
614
        
615
        figure = plt.figure();
616
        
617
        def init_frame(): 
618
            return frame_generation(0);
619
        
620
        def frame_generation(index_dynamic):
621
            figure.clf();
622
            axis = figure.add_subplot(111);
623
            
624
            phase_matrix = sync_output_dynamic.allocate_phase_matrix(grid_width, grid_height, index_dynamic);
625
            axis.imshow(phase_matrix, cmap = plt.get_cmap(colormap), interpolation='kaiser', vmin = 0.0, vmax = 2.0 * math.pi);
626
            artist = figure.gca();
627
            
628
            return [ artist ];
629
630
        phase_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), init_func = init_frame, interval = animation_velocity , repeat_delay = 1000);
631
        
632
        if (save_movie is not None):
633
            phase_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
634
        else:
635
            plt.show();
636
637
638
    @staticmethod
639
    def __get_start_stop_iterations(sync_output_dynamic, start_iteration, stop_iteration):
640
        """!
641
        @brief Apply rule of preparation for start iteration and stop iteration values.
642
        
643
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
644
        @param[in] start_iteration (uint): The first iteration that is used for calculation.
645
        @param[in] stop_iteration (uint): The last iteration that is used for calculation.
646
        
647
        @return (tuple) New values of start and stop iterations.
648
        
649
        """
650
        if (start_iteration is None):
651
            start_iteration = 0;
652
        
653
        if (stop_iteration is None):
654
            stop_iteration = len(sync_output_dynamic);
655
        
656
        return (start_iteration, stop_iteration);
657
658
659
    @staticmethod
660
    def animate(sync_output_dynamic, title = None, save_movie = None):
661
        """!
662
        @brief Shows animation of phase coordinates and animation of correlation matrix together for the Sync dynamic output on the same figure.
663
        
664
        @param[in] sync_output_dynamic (sync_dynamic): Output dynamic of the Sync network.
665
        @param[in] title (string): Title of the animation that is displayed on a figure if it is specified.
666
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
667
        
668
        """
669
        
670
        dynamic = sync_output_dynamic.output[0];
671
        correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(0);
672
        
673
        figure = plt.figure(1);
674
        if (title is not None):
675
            figure.suptitle(title, fontsize = 26, fontweight = 'bold')
676
        
677
        ax1 = figure.add_subplot(121, projection='polar');
678
        ax2 = figure.add_subplot(122);
679
        
680
        artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
681
        artist2 = ax2.imshow(correlation_matrix, cmap = plt.get_cmap('Accent'), interpolation='kaiser');
682
        
683
        def init_frame():
684
            return [ artist1, artist2 ];
685
686
        def frame_generation(index_dynamic):
687
            dynamic = sync_output_dynamic.output[index_dynamic];
688
            artist1.set_data(dynamic, [1.0] * len(dynamic));
689
            
690
            correlation_matrix = sync_output_dynamic.allocate_correlation_matrix(index_dynamic);
691
            artist2.set_data(correlation_matrix);
692
            
693
            return [ artist1, artist2 ];
694
        
695
        dynamic_animation = animation.FuncAnimation(figure, frame_generation, len(sync_output_dynamic), interval = 75, init_func = init_frame, repeat_delay = 5000);
696
        
697
        if (save_movie is not None):
698
            dynamic_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
699
        else:
700
            plt.show();
701
702
703
704
class sync_network(network):
705
    """!
706
    @brief Model of oscillatory network that is based on the Kuramoto model of synchronization.
707
    
708
    """
709
710
    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):
711
        """!
712
        @brief Constructor of oscillatory network is based on Kuramoto model.
713
        
714
        @param[in] num_osc (uint): Number of oscillators in the network.
715
        @param[in] weight (double): Coupling strength of the links between oscillators.
716
        @param[in] frequency (double): Multiplier of internal frequency of the oscillators.
717
        @param[in] type_conn (conn_type): Type of connection between oscillators in the network (all-to-all, grid, bidirectional list, etc.).
718
        @param[in] representation (conn_represent): Internal representation of connection in the network: matrix or list.
719
        @param[in] initial_phases (initial_type): Type of initialization of initial phases of oscillators (random, uniformly distributed, etc.).
720
        @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).
721
        
722
        """
723
        
724
        self._ccore_network_pointer = None;      # Pointer to CCORE Sync implementation of the network.
725
        
726
        if (ccore is True):
727
            self._ccore_network_pointer = wrapper.sync_create_network(num_osc, weight, frequency, type_conn, initial_phases);
728
            self._num_osc = num_osc;
729
            self._conn_represent = conn_represent.MATRIX;
730
        
731
        else:   
732
            super().__init__(num_osc, type_conn, representation);
733
            
734
            self._weight = weight;
735
            
736
            self._phases = list();
737
            self._freq = list();
738
            
739
            random.seed();
740
            for index in range(0, num_osc, 1):
741
                if (initial_phases == initial_type.RANDOM_GAUSSIAN):
742
                    self._phases.append(random.random() * 2 * pi);
743
                
744
                elif (initial_phases == initial_type.EQUIPARTITION):
745
                    self._phases.append( pi / num_osc * index);
746
                
747
                self._freq.append(random.random() * frequency);
748
749
750
    def __del__(self):
751
        """!
752
        @brief Destructor of oscillatory network is based on Kuramoto model.
753
        
754
        """
755
        
756
        if (self._ccore_network_pointer is not None):
757
            wrapper.sync_destroy_network(self._ccore_network_pointer);
758
            self._ccore_network_pointer = None;
759
760
761
    def sync_order(self):
762
        """!
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...
763
        @brief Calculates current level of global synchorization (order parameter) in the network.
764
        @details This parameter is tend 1.0 when the oscillatory network close to global synchronization and it tend to 0.0 when 
765
                  desynchronization is observed in the network. Order parameter is calculated using following equation:
766
                  
767
                  \f[
768
                  r_{c}=\frac{1}{Ne^{i\varphi }}\sum_{j=0}^{N}e^{i\theta_{j}};
769
                  \f]
770
                  
771
                  where \f$\varphi\f$ is a average phase coordinate in the network, \f$N\f$ is an amount of oscillators in the network.
772
        
773
        Example:
774
        @code
775
            oscillatory_network = sync(16, type_conn = conn_type.ALL_TO_ALL);
776
            output_dynamic = oscillatory_network.simulate_static(100, 10);
777
            
778
            if (oscillatory_network.sync_order() < 0.9): print("Global synchronization is not reached yet.");
779
            else: print("Global synchronization is reached.");
780
        @endcode
781
        
782
        @return (double) Level of global synchronization (order parameter).
783
        
784
        @see sync_local_order()
785
        
786
        """
787
        
788
        if (self._ccore_network_pointer is not None):
789
            return wrapper.sync_order(self._ccore_network_pointer);
790
        
791
        return sync_dynamic.calculate_sync_order(self._phases);
792
793
794
    def sync_local_order(self):
795
        """!
796
        @brief Calculates current level of local (partial) synchronization in the network.
797
        
798
        @return (double) Level of local (partial) synchronization.
799
        
800
        @see sync_order()
801
        
802
        """
803
        
804
        if (self._ccore_network_pointer is not None):
805
            return wrapper.sync_local_order(self._ccore_network_pointer);
806
        
807
        return sync_dynamic.calculate_local_sync_order(self._phases, self);
808
809
810
    def _phase_kuramoto(self, teta, t, argv):
0 ignored issues
show
Unused Code introduced by
The argument t seems to be unused.
Loading history...
811
        """!
812
        @brief Returns result of phase calculation for specified oscillator in the network.
813
        
814
        @param[in] teta (double): Phase of the oscillator that is differentiated.
815
        @param[in] t (double): Current time of simulation.
816
        @param[in] argv (tuple): Index of the oscillator in the list.
817
        
818
        @return (double) New phase for specified oscillator (don't assign here).
819
        
820
        """
821
        
822
        index = argv;
823
        phase = 0;
824
        for k in range(0, self._num_osc):
825
            if (self.has_connection(index, k) == True):
826
                phase += math.sin(self._phases[k] - teta);
827
            
828
        return ( self._freq[index] + (phase * self._weight / self._num_osc) );
829
830
831
    def simulate(self, steps, time, solution = solve_type.FAST, collect_dynamic = True):
832
        """!
833
        @brief Performs static simulation of Sync oscillatory network.
834
        
835
        @param[in] steps (uint): Number steps of simulations during simulation.
836
        @param[in] time (double): Time of simulation.
837
        @param[in] solution (solve_type): Type of solution (solving).
838
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
839
        
840
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
841
                otherwise returns only last values (last step of simulation) of dynamic.
842
        
843
        @see simulate_dynamic()
844
        @see simulate_static()
845
        
846
        """
847
        
848
        return self.simulate_static(steps, time, solution, collect_dynamic);
849
850
851
    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):
852
        """!
853
        @brief Performs dynamic simulation of the network until stop condition is not reached. Stop condition is defined by input argument 'order'.
854
        
855
        @param[in] order (double): Order of process synchronization, distributed 0..1.
856
        @param[in] solution (solve_type): Type of solution.
857
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
858
        @param[in] step (double): Time step of one iteration of simulation.
859
        @param[in] int_step (double): Integration step, should be less than step.
860
        @param[in] threshold_changes (double): Additional stop condition that helps prevent infinite simulation, defines limit of changes of oscillators between current and previous steps.
861
        
862
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
863
                otherwise returns only last values (last step of simulation) of dynamic.
864
        
865
        @see simulate()
866
        @see simulate_static()
867
        
868
        """
869
        
870
        if (self._ccore_network_pointer is not None):
871
            ccore_instance_dynamic = wrapper.sync_simulate_dynamic(self._ccore_network_pointer, order, solution, collect_dynamic, step, int_step, threshold_changes);
872
            return sync_dynamic(None, None, ccore_instance_dynamic);
873
        
874
        # For statistics and integration
875
        time_counter = 0;
876
        
877
        # Prevent infinite loop. It's possible when required state cannot be reached.
878
        previous_order = 0;
879
        current_order = self.sync_local_order();
880
        
881
        # If requested input dynamics
882
        dyn_phase = [];
883
        dyn_time = [];
884
        if (collect_dynamic == True):
885
            dyn_phase.append(self._phases);
886
            dyn_time.append(0);
887
        
888
        # Execute until sync state will be reached
889
        while (current_order < order):
890
            # update states of oscillators
891
            self._phases = self._calculate_phases(solution, time_counter, step, int_step);
892
            
893
            # update time
894
            time_counter += step;
895
            
896
            # if requested input dynamic
897
            if (collect_dynamic == True):
898
                dyn_phase.append(self._phases);
899
                dyn_time.append(time_counter);
900
                
901
            # update orders
902
            previous_order = current_order;
903
            current_order = self.sync_local_order();
904
            
905
            # hang prevention
906
            if (abs(current_order - previous_order) < threshold_changes):
907
                # print("Warning: sync_network::simulate_dynamic - simulation is aborted due to low level of convergence rate (order = " + str(current_order) + ").");
908
                break;
909
            
910
        if (collect_dynamic != True):
911
            dyn_phase.append(self._phases);
912
            dyn_time.append(time_counter);
913
914
        output_sync_dynamic = sync_dynamic(dyn_phase, dyn_time, None);
915
        return output_sync_dynamic;
916
917
918
    def simulate_static(self, steps, time, solution = solve_type.FAST, collect_dynamic = False):
919
        """!
920
        @brief Performs static simulation of oscillatory network.
921
        
922
        @param[in] steps (uint): Number steps of simulations during simulation.
923
        @param[in] time (double): Time of simulation.
924
        @param[in] solution (solve_type): Type of solution.
925
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
926
        
927
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
928
                otherwise returns only last values (last step of simulation) of dynamic.
929
        
930
        @see simulate()
931
        @see simulate_dynamic()
932
        
933
        """
934
        
935
        if (self._ccore_network_pointer is not None):
936
            ccore_instance_dynamic = wrapper.sync_simulate_static(self._ccore_network_pointer, steps, time, solution, collect_dynamic);
937
            return sync_dynamic(None, None, ccore_instance_dynamic);
938
        
939
        dyn_phase = [];
940
        dyn_time = [];
941
        
942
        if (collect_dynamic == True):
943
            dyn_phase.append(self._phases);
944
            dyn_time.append(0);
945
        
946
        step = time / steps;
947
        int_step = step / 10.0;
948
        
949
        for t in numpy.arange(step, time + step, step):
950
            # update states of oscillators
951
            self._phases = self._calculate_phases(solution, t, step, int_step);
952
            
953
            # update states of oscillators
954
            if (collect_dynamic == True):
955
                dyn_phase.append(self._phases);
956
                dyn_time.append(t);
957
        
958
        if (collect_dynamic != True):
959
            dyn_phase.append(self._phases);
960
            dyn_time.append(time);
961
                        
962
        output_sync_dynamic = sync_dynamic(dyn_phase, dyn_time);
963
        return output_sync_dynamic;
964
965
966
    def _calculate_phases(self, solution, t, step, int_step):
967
        """!
968
        @brief Calculates new phases for oscillators in the network in line with current step.
969
        
970
        @param[in] solution (solve_type): Type solver of the differential equation.
971
        @param[in] t (double): Time of simulation.
972
        @param[in] step (double): Step of solution at the end of which states of oscillators should be calculated.
973
        @param[in] int_step (double): Step differentiation that is used for solving differential equation.
974
        
975
        @return (list) New states (phases) for oscillators.
976
        
977
        """
978
        
979
        next_phases = [0.0] * self._num_osc;    # new oscillator _phases
980
        
981
        for index in range (0, self._num_osc, 1):
982
            if (solution == solve_type.FAST):
983
                result = self._phases[index] + self._phase_kuramoto(self._phases[index], 0, index);
984
                next_phases[index] = self._phase_normalization(result);
985
                
986
            elif (solution == solve_type.RK4):
987
                result = odeint(self._phase_kuramoto, self._phases[index], numpy.arange(t - step, t, int_step), (index , ));
988
                next_phases[index] = self._phase_normalization(result[len(result) - 1][0]);
989
            
990
            else:
991
                raise NameError("Solver '" + solution + "' is not supported");
992
        
993
        return next_phases;
994
995
996
    def _phase_normalization(self, teta):
997
        """!
998
        @brief Normalization of phase of oscillator that should be placed between [0; 2 * pi].
999
        
1000
        @param[in] teta (double): phase of oscillator.
1001
        
1002
        @return (double) Normalized phase.
1003
        
1004
        """
1005
1006
        norm_teta = teta;
1007
        while (norm_teta > (2.0 * pi)) or (norm_teta < 0):
1008
            if (norm_teta > (2.0 * pi)):
1009
                norm_teta -= 2.0 * pi;
1010
            else:
1011
                norm_teta += 2.0 * pi;
1012
        
1013
        return norm_teta;
1014
1015
1016
    def get_neighbors(self, index):
1017
        """!
1018
        @brief Finds neighbors of the oscillator with specified index.
1019
        
1020
        @param[in] index (uint): index of oscillator for which neighbors should be found in the network.
1021
        
1022
        @return (list) Indexes of neighbors of the specified oscillator.
1023
        
1024
        """
1025
        
1026
        if ( (self._ccore_network_pointer is not None) and (self._osc_conn is None) ):
1027
            self._osc_conn = wrapper.sync_connectivity_matrix(self._ccore_network_pointer);
1028
            
1029
        return super().get_neighbors(index);
1030
1031
1032
    def has_connection(self, i, j):
1033
        """!
1034
        @brief Returns True if there is connection between i and j oscillators and False - if connection doesn't exist.
1035
        
1036
        @param[in] i (uint): index of an oscillator in the network.
1037
        @param[in] j (uint): index of an oscillator in the network.
1038
        
1039
        """
1040
        
1041
        if ( (self._ccore_network_pointer is not None) and (self._osc_conn is None) ):
1042
            self._osc_conn = wrapper.sync_connectivity_matrix(self._ccore_network_pointer);
1043
        
1044
        return super().has_connection(i, j);