Completed
Push — master ( a74470...c8bc69 )
by Andrei
58s
created

syncpr.__init__()   B

Complexity

Conditions 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 20
rs 8.5454
c 0
b 0
f 0
1
"""!
2
3
@brief Phase oscillatory network for patten recognition based on modified Kuramoto model.
4
@details Based on article description:
5
         - R.Follmann, E.E.N.Macau, E.Rosa, Jr., J.R.C.Piqueira. Phase Oscillatory Network and Visual Pattern Recognition. 2014.
6
         
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2018
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
from pyclustering.nnet          import solve_type, initial_type, conn_type,conn_represent;
29
from pyclustering.nnet.sync     import sync_network, sync_dynamic, sync_visualizer;
30
31
import pyclustering.core.syncpr_wrapper as wrapper;
32
33
from pyclustering.core.wrapper import ccore_library;
34
35
from PIL import Image;
0 ignored issues
show
Configuration introduced by
The import PIL 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
37
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...
38
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...
39
40
import math;
41
import cmath;
42
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...
43
44
45
class syncpr_dynamic(sync_dynamic):
46
    """!
47
    @brief Represents output dynamic of syncpr (Sync for Pattern Recognition).
48
    
49
    """
50
    
51
    def __init__(self, phase, time, ccore):
52
        """!
53
        @brief Constructor of syncpr dynamic.
54
        
55
        @param[in] phase (list): Dynamic of oscillators on each step of simulation. If ccore pointer is specified than it can be ignored.
56
        @param[in] time (list): Simulation time.
57
        @param[in] ccore (ctypes.pointer): Pointer to CCORE sync_dynamic instance in memory.
58
        
59
        """
60
        super().__init__(phase, time, ccore);
61
62
63
class syncpr_visualizer(sync_visualizer):
64
    """!
65
    @brief Visualizer of output dynamic of syncpr network (Sync for Pattern Recognition).
66
    
67
    """
68
    
69
    @staticmethod
70
    def show_pattern(syncpr_output_dynamic, image_height, image_width):
71
        """!
72
        @brief Displays evolution of phase oscillators as set of patterns where the last one means final result of recognition.
73
        
74
        @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
75
        @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
76
        @param[in] image_width (uint): Width of the pattern.
77
        
78
        """
79
        number_pictures = len(syncpr_output_dynamic);
80
        iteration_math_step = 1.0;
81
        if (number_pictures > 50):
82
            iteration_math_step = number_pictures / 50.0;
83
            number_pictures = 50;
84
        
85
        number_cols = int(numpy.ceil(number_pictures ** 0.5));
86
        number_rows = int(numpy.ceil(number_pictures / number_cols));
87
        
88
        real_index = 0, 0;
89
        double_indexer = True;
90
        if ( (number_cols == 1) or (number_rows == 1) ):
91
            real_index = 0;
92
            double_indexer = False;
93
        
94
        (_, axarr) = plt.subplots(number_rows, number_cols);
95
        
96
        if (number_pictures > 1):
97
            plt.setp([ax for ax in axarr], visible = False);
98
            
99
        iteration_display = 0.0;
100
        for iteration in range(len(syncpr_output_dynamic)):
101
            if (iteration >= iteration_display):
102
                iteration_display += iteration_math_step;
103
                
104
                ax_handle = axarr;
105
                if (number_pictures > 1):
106
                    ax_handle = axarr[real_index];
107
                    
108
                syncpr_visualizer.__show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration);
109
                
110
                if (double_indexer is True):
111
                    real_index = real_index[0], real_index[1] + 1;
112
                    if (real_index[1] >= number_cols):
113
                        real_index = real_index[0] + 1, 0; 
114
                else:
115
                    real_index += 1;
116
    
117
        plt.show();
118
    
119
    
120
    @staticmethod
121
    def animate_pattern_recognition(syncpr_output_dynamic, image_height, image_width, animation_velocity = 75, title = None, save_movie = None):
122
        """!
123
        @brief Shows animation of pattern recognition process that has been preformed by the oscillatory network.
124
        
125
        @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
126
        @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
127
        @param[in] image_width (uint): Width of the pattern.
128
        @param[in] animation_velocity (uint): Interval between frames in milliseconds.
129
        @param[in] title (string): Title of the animation that is displayed on a figure if it is specified.
130
        @param[in] save_movie (string): If it is specified then animation will be stored to file that is specified in this parameter.
131
        
132
        """
133
        figure = plt.figure();
134
        
135
        def init_frame():
136
            return frame_generation(0);
137
        
138
        def frame_generation(index_dynamic):
139
            figure.clf();
140
            
141
            if (title is not None):
142
                figure.suptitle(title, fontsize = 26, fontweight = 'bold')
143
            
144
            ax1 = figure.add_subplot(121, projection='polar');
145
            ax2 = figure.add_subplot(122);
146
            
147
            dynamic = syncpr_output_dynamic.output[index_dynamic];
148
            
149
            artist1, = ax1.plot(dynamic, [1.0] * len(dynamic), marker = 'o', color = 'blue', ls = '');
150
            artist2 = syncpr_visualizer.__show_pattern(ax2, syncpr_output_dynamic, image_height, image_width, index_dynamic);
151
            
152
            return [ artist1, artist2 ];
153
        
154
        cluster_animation = animation.FuncAnimation(figure, frame_generation, len(syncpr_output_dynamic), interval = animation_velocity, init_func = init_frame, repeat_delay = 5000);
155
156
        if (save_movie is not None):
157
#             plt.rcParams['animation.ffmpeg_path'] = 'C:\\Users\\annoviko\\programs\\ffmpeg-win64-static\\bin\\ffmpeg.exe';
158
#             ffmpeg_writer = animation.FFMpegWriter();
159
#             cluster_animation.save(save_movie, writer = ffmpeg_writer, fps = 15);
160
            cluster_animation.save(save_movie, writer = 'ffmpeg', fps = 15, bitrate = 1500);
161
        else:
162
            plt.show();
163
    
164
    
165
    @staticmethod
166
    def __show_pattern(ax_handle, syncpr_output_dynamic, image_height, image_width, iteration):
167
        """!
168
        @brief Draws pattern on specified ax.
169
        
170
        @param[in] ax_handle (Axis): Axis where pattern should be drawn.
171
        @param[in] syncpr_output_dynamic (syncpr_dynamic): Output dynamic of a syncpr network.
172
        @param[in] image_height (uint): Height of the pattern (image_height * image_width should be equal to number of oscillators).
173
        @param[in] image_width (uint): Width of the pattern.
174
        @param[in] iteration (uint): Simulation iteration that should be used for extracting pattern.
175
        
176
        @return (matplotlib.artist) Artist (pattern) that is rendered in the canvas.
177
        
178
        """
179
        
180
        current_dynamic = syncpr_output_dynamic.output[iteration];
181
        stage_picture = [(255, 255, 255)] * (image_height * image_width);
182
        for index_phase in range(len(current_dynamic)):
183
            phase = current_dynamic[index_phase];
184
            
185
            pixel_color = math.floor( phase * (255 / (2 * math.pi)) );
186
            stage_picture[index_phase] = (pixel_color, pixel_color, pixel_color);
187
          
188
        stage = numpy.array(stage_picture, numpy.uint8);
189
        stage = numpy.reshape(stage, (image_height, image_width) + ((3),)); # ((3),) it's size of RGB - third dimension.
190
        
191
        image_cluster = Image.fromarray(stage);
192
        
193
        artist = ax_handle.imshow(image_cluster, interpolation = 'none');
194
        plt.setp(ax_handle, visible = True);
195
        
196
        ax_handle.xaxis.set_ticklabels([]);
197
        ax_handle.yaxis.set_ticklabels([]);
198
        ax_handle.xaxis.set_ticks_position('none');
199
        ax_handle.yaxis.set_ticks_position('none');
200
        
201
        return artist;
202
203
204
class syncpr(sync_network):
205
    """!
206
    @brief Model of phase oscillatory network for pattern recognition that is based on the Kuramoto model.
207
    @details The model uses second-order and third-order modes of the Fourier components.
208
             
209
             CCORE option can be used to use the pyclustering core - C/C++ shared library for processing that significantly increases performance.
210
             
211
    Example:
212
    @code
213
        # Network size should be equal to size of pattern for learning.
214
        net = syncpr(size_network, 0.3, 0.3);
215
        
216
        # Train network using list of patterns (input images).
217
        net.train(image_samples);
218
        
219
        # Recognize image using 10 steps during 10 seconds of simulation.
220
        sync_output_dynamic = net.simulate(10, 10, pattern, solve_type.RK4, True);
221
        
222
        # Display output dynamic.
223
        syncpr_visualizer.show_output_dynamic(sync_output_dynamic);
224
        
225
        # Display evolution of recognition of the pattern.
226
        syncpr_visualizer.show_pattern(sync_output_dynamic, image_height, image_width);
227
    
228
    @endcode
229
    
230
    """
231
232
    def __init__(self, num_osc, increase_strength1, increase_strength2, ccore = True):
233
        """!
234
        @brief Constructor of oscillatory network for pattern recognition based on Kuramoto model.
235
        
236
        @param[in] num_osc (uint): Number of oscillators in the network.
237
        @param[in] increase_strength1 (double): Parameter for increasing strength of the second term of the Fourier component.
238
        @param[in] increase_strength2 (double): Parameter for increasing strength of the third term of the Fourier component.
239
        @param[in] ccore (bool): If True simulation is performed by CCORE library (C++ implementation of pyclustering).
240
        
241
        """
242
        
243
        if ( (ccore is True) and ccore_library.workable() ):
244
            self._ccore_network_pointer = wrapper.syncpr_create(num_osc, increase_strength1, increase_strength2);
245
            
246
        else:
247
            self._increase_strength1 = increase_strength1;
248
            self._increase_strength2 = increase_strength2;
249
            self._coupling = [ [0.0 for i in range(num_osc)] for j in range(num_osc) ];
250
251
            super().__init__(num_osc, 1, 0, conn_type.ALL_TO_ALL, conn_represent.MATRIX, initial_type.RANDOM_GAUSSIAN, ccore)
252
    
253
    
254
    def __del__(self):
255
        """!
256
        @brief Default destructor of syncpr.
257
        
258
        """
259
        
260
        if (self._ccore_network_pointer is not None):
261
            wrapper.syncpr_destroy(self._ccore_network_pointer);
262
            self._ccore_network_pointer = None;
263
264
265
    def __len__(self):
266
        """!
267
        @brief Returns size of the network.
268
        
269
        """        
270
        if (self._ccore_network_pointer is not None):
271
            return wrapper.syncpr_get_size(self._ccore_network_pointer);
272
        
273
        else:
274
            return self._num_osc;
275
    
276
    
277
    def train(self, samples):
278
        """!
279
        @brief Trains syncpr network using Hebbian rule for adjusting strength of connections between oscillators during training.
280
        
281
        @param[in] samples (list): list of patterns where each pattern is represented by list of features that are equal to [-1; 1].
282
        
283
        """
284
        
285
        # Verify pattern for learning
286
        for pattern in samples:
287
            self.__validate_pattern(pattern);
288
        
289
        if (self._ccore_network_pointer is not None):
290
            return wrapper.syncpr_train(self._ccore_network_pointer, samples);
291
        
292
        length = len(self);
293
        number_samples = len(samples);
294
        
295
        for i in range(length):
296
            for j in range(i + 1, len(self), 1):
297
                
298
                # go through via all patterns
299
                for p in range(number_samples):
300
                    value1 = samples[p][i];
301
                    value2 = samples[p][j];
302
                    
303
                    self._coupling[i][j] += value1 * value2;
304
                
305
                self._coupling[i][j] /= length;
306
                self._coupling[j][i] = self._coupling[i][j];
307
    
308
    
309
    def simulate(self, steps, time, pattern, solution = solve_type.RK4, collect_dynamic = True):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'simulate' method
Loading history...
310
        """!
311
        @brief Performs static simulation of syncpr oscillatory network.
312
        @details In other words network performs pattern recognition during simulation.
313
        
314
        @param[in] steps (uint): Number steps of simulations during simulation.
315
        @param[in] time (double): Time of simulation.
316
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
317
        @param[in] solution (solve_type): Type of solver that should be used for simulation.
318
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
319
        
320
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
321
                otherwise returns only last values (last step of simulation) of dynamic.
322
        
323
        @see simulate_dynamic()
324
        @see simulate_static()
325
        
326
        """
327
                    
328
        return self.simulate_static(steps, time, pattern, solution, collect_dynamic);
329
    
330
    
331
    def simulate_dynamic(self, pattern, order = 0.998, solution = solve_type.RK4, collect_dynamic = False, step = 0.1, int_step = 0.01, threshold_changes = 0.0000001):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'simulate_dynamic' method
Loading history...
332
        """!
333
        @brief Performs dynamic simulation of the network until stop condition is not reached.
334
        @details In other words network performs pattern recognition during simulation. 
335
                 Stop condition is defined by input argument 'order' that represents memory order, but
336
                 process of simulation can be stopped if convergance rate is low whose threshold is defined
337
                 by the argument 'threshold_changes'.
338
        
339
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
340
        @param[in] order (double): Order of process synchronization, distributed 0..1.
341
        @param[in] solution (solve_type): Type of solution.
342
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
343
        @param[in] step (double): Time step of one iteration of simulation.
344
        @param[in] int_step (double): Integration step, should be less than step.
345
        @param[in] threshold_changes (double): Additional stop condition that helps prevent infinite simulation, defines limit of changes of oscillators between current and previous steps.
346
        
347
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
348
                otherwise returns only last values (last step of simulation) of dynamic.
349
        
350
        @see simulate()
351
        @see simulate_static()
352
        
353
        """
354
        
355
        self.__validate_pattern(pattern);
356
        
357
        if (self._ccore_network_pointer is not None):
358
            ccore_instance_dynamic = wrapper.syncpr_simulate_dynamic(self._ccore_network_pointer, pattern, order, solution, collect_dynamic, step);
359
            return syncpr_dynamic(None, None, ccore_instance_dynamic);
360
        
361
        for i in range(0, len(pattern), 1):
362
            if (pattern[i] > 0.0):
363
                self._phases[i] = 0.0;
364
            else:
365
                self._phases[i] = math.pi / 2.0;
366
        
367
        # For statistics and integration
368
        time_counter = 0;
369
        
370
        # Prevent infinite loop. It's possible when required state cannot be reached.
371
        previous_order = 0;
372
        current_order = self.__calculate_memory_order(pattern);
373
        
374
        # If requested input dynamics
375
        dyn_phase = [];
376
        dyn_time = [];
377
        if (collect_dynamic == True):
378
            dyn_phase.append(self._phases);
379
            dyn_time.append(0);
380
        
381
        # Execute until sync state will be reached
382
        while (current_order < order):
383
            # update states of oscillators
384
            self._phases = self._calculate_phases(solution, time_counter, step, int_step);
385
            
386
            # update time
387
            time_counter += step;
388
            
389
            # if requested input dynamic
390
            if (collect_dynamic == True):
391
                dyn_phase.append(self._phases);
392
                dyn_time.append(time_counter);
393
                
394
            # update orders
395
            previous_order = current_order;
396
            current_order = self.__calculate_memory_order(pattern);
397
            
398
            # hang prevention
399
            if (abs(current_order - previous_order) < threshold_changes):
400
                break;
401
        
402
        if (collect_dynamic != True):
403
            dyn_phase.append(self._phases);
404
            dyn_time.append(time_counter);
405
        
406
        output_sync_dynamic = syncpr_dynamic(dyn_phase, dyn_time, None);
407
        return output_sync_dynamic;
408
409
410
    def simulate_static(self, steps, time, pattern, solution = solve_type.FAST, collect_dynamic = False):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'simulate_static' method
Loading history...
411
        """!
412
        @brief Performs static simulation of syncpr oscillatory network.
413
        @details In other words network performs pattern recognition during simulation.
414
        
415
        @param[in] steps (uint): Number steps of simulations during simulation.
416
        @param[in] time (double): Time of simulation.
417
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
418
        @param[in] solution (solve_type): Type of solution.
419
        @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
420
        
421
        @return (list) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
422
                otherwise returns only last values (last step of simulation) of dynamic.
423
        
424
        @see simulate()
425
        @see simulate_dynamic()
426
        
427
        """
428
        
429
        self.__validate_pattern(pattern);
430
        
431
        if (self._ccore_network_pointer is not None):
432
            ccore_instance_dynamic = wrapper.syncpr_simulate_static(self._ccore_network_pointer, steps, time, pattern, solution, collect_dynamic);
433
            return syncpr_dynamic(None, None, ccore_instance_dynamic);
434
        
435
        for i in range(0, len(pattern), 1):
436
            if (pattern[i] > 0.0):
437
                self._phases[i] = 0.0;
438
            else:
439
                self._phases[i] = math.pi / 2.0;
440
                
441
        return super().simulate_static(steps, time, solution, collect_dynamic);
442
    
443
    
444
    def memory_order(self, pattern):
445
        """!
446
        @brief Calculates function of the memorized pattern.
447
        @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
448
        
449
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
450
        
451
        @return (double) Order of memory for the specified pattern.
452
        
453
        """
454
        
455
        self.__validate_pattern(pattern);
456
        
457
        if (self._ccore_network_pointer is not None):
458
            return wrapper.syncpr_memory_order(self._ccore_network_pointer, pattern);
459
        
460
        else:
461
            return self.__calculate_memory_order(pattern);
462
463
    
464
    def __calculate_memory_order(self, pattern):
465
        """!
466
        @brief Calculates function of the memorized pattern without any pattern validation.
467
        
468
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
469
        
470
        @return (double) Order of memory for the specified pattern.
471
                
472
        """
473
        
474
        memory_order = 0.0;
475
        for index in range(len(self)):
476
            memory_order += pattern[index] * cmath.exp( 1j * self._phases[index] );
477
        
478
        memory_order /= len(self);
479
        return abs(memory_order);
480
        
481
    
482
    def _phase_kuramoto(self, teta, t, argv):
483
        """!
484
        @brief Returns result of phase calculation for specified oscillator in the network.
485
        
486
        @param[in] teta (double): Phase of the oscillator that is differentiated.
487
        @param[in] t (double): Current time of simulation.
488
        @param[in] argv (tuple): Index of the oscillator in the list.
489
        
490
        @return (double) New phase for specified oscillator (don't assign it here).
491
        
492
        """
493
        
494
        index = argv;
495
        
496
        phase = 0.0;
497
        term = 0.0;
498
        
499
        for k in range(0, self._num_osc):
500
            if (k != index):
501
                phase_delta = self._phases[k] - teta;
502
                
503
                phase += self._coupling[index][k] * math.sin(phase_delta);
504
                
505
                term1 = self._increase_strength1 * math.sin(2.0 * phase_delta);
506
                term2 = self._increase_strength2 * math.sin(3.0 * phase_delta);
507
                
508
                term += (term1 - term2);
509
                
510
        return ( phase + term / len(self) );
511
    
512
    
513
    def __validate_pattern(self, pattern):
514
        """!
515
        @brief Validates pattern.
516
        @details Throws exception if length of pattern is not equal to size of the network or if it consists feature with value that are not equal to [-1; 1].
517
        
518
        @param[in] pattern (list): Pattern for recognition represented by list of features that are equal to [-1; 1].
519
        
520
        """
521
        if (len(pattern) != len(self)):
522
            raise NameError('syncpr: length of the pattern (' + len(pattern) + ') should be equal to size of the network');
523
        
524
        for feature in pattern:
525
            if ( (feature != -1.0) and (feature != 1.0) ):
526
                raise NameError('syncpr: patten feature (' + feature + ') should be distributed in [-1; 1]');