Completed
Push — master ( 30917b...e69bed )
by Andrei
01:45
created

cnn_network.__calculate_states()   A

Complexity

Conditions 3

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
"""!
2
3
@brief Chaotic Neural Network
4
@details Based on article description:
5
         - 
6
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2016
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
import math;
29
30
import random;
31
32
from enum import IntEnum;
33
34
from pyclustering.utils import euclidean_distance_sqrt, average_neighbor_distance, heaviside, draw_dynamics;
35
36
37
class type_conn(IntEnum):
38
    """!
39
    @brief Enumeration of connection types for Chaotic Neural Network.
40
    
41
    @see cnn_network
42
    
43
    """
44
    
45
    ## All oscillators have connection with each other.
46
    ALL_TO_ALL  = 0,
47
    
48
    ## Connections between oscillators are created in line with Delaunay triangulation.
49
    TRIANGULATION_DELAUNAY = 1,
50
51
52
class cnn_dynamic:
53
    def __init__(self, output = [], time = []):
0 ignored issues
show
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
54
        self.output = output;
55
        self.time = time;
56
57
58
    def __len__(self):
59
        return len(self.output);
60
61
62
    def allocate_observation_matrix(self):
63
        number_neurons = len(self.output[0]);
64
        observation_matrix = [];
65
        
66
        for iteration in range(len(self.output)):
67
            obervation_column = [];
68
            for index_neuron in range(number_neurons):
69
                obervation_column.append(heaviside(self.output[iteration][index_neuron]));
70
            
71
            observation_matrix.append(obervation_column);
72
        
73
        return observation_matrix;
74
75
76
class cnn_visualizer:
77
    @staticmethod
78
    def show_output_dynamic(cnn_output_dynamic):
79
        draw_dynamics(cnn_output_dynamic.time, cnn_output_dynamic.output, x_title = "t", y_title = "x");
80
    
81
    @staticmethod
82
    def show_observation_matrix(cnn_output_dynamic):
83
        observation_matrix = cnn_output_dynamic.allocate_observation_matrix();
84
        draw_dynamics(cnn_output_dynamic.time, observation_matrix, x_title = "t", y_title = "outputs");
85
86
87
class cnn_network:
88
    def __init__(self, num_osc, conn_type = type_conn.ALL_TO_ALL, amount_neighbors = 3):
89
        self.__num_osc = num_osc;
90
        self.__conn_type = conn_type;
91
        self.__amount_neighbors = amount_neighbors;
92
        
93
        self.__average_distance = 0.0;
94
        self.__weights = None;
95
        self.__weights_summary = None;
96
        
97
        self.__output = [ random.random() for _ in range(num_osc) ];
98
    
99
    
100
    def __len__(self):
101
        return self.__num_osc;
102
    
103
    
104
    def simulate(self, steps, stimulus):
105
        self.__create_weights(stimulus);
106
        
107
        dynamic = cnn_dynamic([], []);
108
        dynamic.output.append(self.__output);
109
        dynamic.time.append(0);
110
        
111
        for step in range(1, steps, 1):
112
            self.__output = self.__calculate_states();
113
            
114
            dynamic.output.append(self.__output);
115
            dynamic.time.append(step);
116
            
117
        return dynamic;
118
    
119
    
120
    def __calculate_states(self):
121
        output = [ 0.0 for _ in range(self.__num_osc) ];
122
        
123
        for i in range(self.__num_osc):
124
            output[i] = self.__neuron_evolution(i);
125
        
126
        return output;
127
    
128
    
129
    def __neuron_evolution(self, index):
130
        value = 0.0;
131
        state = 1.0 - 2.0 * self.__output[index];
132
        
133
        for index in range(self.__num_osc):
134
            value += self.__weights[index][index] * state;
135
        
136
        return value / self.__weights_summary[index];
137
    
138
    
139
    def __create_weights(self, stimulus):
140
        self.__average_distance = average_neighbor_distance(stimulus, self.__amount_neighbors);
141
        
142
        self.__weights = [ [ 0.0 for _ in range(len(stimulus)) ] for _ in range(len(stimulus)) ];
143
        self.__weights_summary = [ 0.0 for _ in range(self.__num_osc) ];
144
        
145
        if (self.__conn_type == type_conn.ALL_TO_ALL):
146
            self.__create_weights_all_to_all(stimulus);
147
        
148
        elif (self.__conn_type == type_conn.TRIANGULATION_DELAUNAY):
149
            self.__create_weights_delaunay_triangulation(stimulus);
150
    
151
    
152
    def __create_weights_all_to_all(self, stimulus):
153
        for i in range(len(stimulus)):
154
            for j in range(i + 1, len(stimulus)):
155
                weight = self.__calculate_weight(stimulus[i], stimulus[j]);
156
                
157
                self.__weights[i][j] = weight;
158
                self.__weights[j][i] = weight;
159
                
160
                self.__weights_summary[i] += weight;
161
                self.__weights_summary[j] += weight;
162
    
163
    
164
    def __create_weights_delaunay_triangulation(self, stimulus):
165
        pass;
166
    
167
    
168
    def __calculate_weight(self, oscillator_location1, oscillator_location2):
169
        distance = euclidean_distance_sqrt(oscillator_location1, oscillator_location2);
170
        return math.exp(-distance / (2.0 * self.__amount_neighbors));
171