hysteresis.py
1 """!
2 
3 @brief Neural Network: Hysteresis Oscillatory Network
4 @details Implementation based on paper @cite article::nnet::hysteresis::1.
5 
6 @authors Andrei Novikov (pyclustering@yandex.ru)
7 @date 2014-2020
8 @copyright GNU Public License
9 
10 @cond GNU_PUBLIC_LICENSE
11  PyClustering is free software: you can redistribute it and/or modify
12  it under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  PyClustering is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  GNU General Public License for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program. If not, see <http://www.gnu.org/licenses/>.
23 @endcond
24 
25 """
26 
27 import numpy
28 
29 from scipy.integrate import odeint
30 
31 from pyclustering.nnet import *
32 
33 from pyclustering.utils import draw_dynamics
34 
35 
37  """!
38  @brief Represents output dynamic of hysteresis oscillatory network.
39 
40  """
41 
42  @property
43  def output(self):
44  """!
45  @brief (list) Returns outputs of oscillator during simulation.
46 
47  """
48  return self._dynamic;
49 
50 
51  @property
52  def time(self):
53  """!
54  @brief (list) Returns sampling times when dynamic is measured during simulation.
55 
56  """
57 
58  return self._time;
59 
60 
61  def __init__(self, amplitudes, time):
62  """!
63  @brief Constructor of hysteresis neural network dynamic.
64 
65  @param[in] amplitudes (list): Dynamic (amplitudes) of oscillators on each step of simulation.
66  @param[in] time (list): Simulation time (timestamps of simulation steps) when amplitudes are stored.
67 
68  """
69 
70  if (len(amplitudes) != len(time)):
71  raise NameError("Length of list of dynamics of oscillators should be equal to length of simulation timestamps of steps.");
72 
73  self._dynamic = amplitudes;
74 
75  self._time = time;
76 
77 
78  def __len__(self):
79  """!
80  @brief (uint) Returns number of simulation steps that are stored in dynamic.
81 
82  """
83 
84  return len(self._dynamic);
85 
86 
87  def allocate_sync_ensembles(self, tolerance = 0.1, threshold_steps = 1):
88  """!
89  @brief Allocate clusters in line with ensembles of synchronous oscillators where each
90  synchronous ensemble corresponds to only one cluster.
91 
92  @param[in] tolerance (double): Maximum error for allocation of synchronous ensemble oscillators.
93  @param[in] threshold_steps (uint): Number of steps from the end of simulation that should be analysed for ensemble allocation.
94  If amout of simulation steps has been less than threshold steps than amount of steps will be reduced to amout
95  of simulation steps.
96 
97  @return (list) Grours of indexes of synchronous oscillators, for example, [ [index_osc1, index_osc3], [index_osc2], [index_osc4, index_osc5] ]."
98 
99  """
100 
101  clusters = [ [0] ];
102 
103  number_oscillators = len(self._dynamic[0]);
104 
105  for i in range(1, number_oscillators, 1):
106  captured_neuron = True;
107  for cluster in clusters:
108  neuron_index = cluster[0];
109 
110  analysis_steps = threshold_steps;
111  if (len(self._dynamic) < analysis_steps):
112  analysis_steps = len(self._dynamic);
113 
114  analysis_start_step_index = len(self._dynamic) - 1;
115 
116  for step in range(analysis_start_step_index, analysis_start_step_index - analysis_steps, -1):
117  neuron_amplitude = self._dynamic[step][neuron_index];
118  candidate_amplitude = self._dynamic[step][i];
119 
120  if ( not (candidate_amplitude < (neuron_amplitude + tolerance)) or not (candidate_amplitude > (neuron_amplitude - tolerance)) ):
121  captured_neuron = False;
122  break;
123 
124  if ( captured_neuron is True ):
125  cluster.append(i);
126  break;
127 
128  if (captured_neuron is False):
129  clusters.append([i]);
130 
131  return clusters;
132 
133 
135  """!
136  @brief Visualizer of output dynamic of hysteresis oscillatory network.
137 
138  """
139 
140  @staticmethod
141  def show_output_dynamic(hysteresis_output_dynamic):
142  """!
143  @brief Shows output dynamic (output of each oscillator) during simulation.
144 
145  @param[in] hysteresis_output_dynamic (hysteresis_dynamic): Output dynamic of the hysteresis oscillatory network.
146 
147  """
148 
149  draw_dynamics(hysteresis_output_dynamic.time, hysteresis_output_dynamic.output, x_title = "Time", y_title = "x(t)");
150 
151 
153  """!
154  @brief Hysteresis oscillatory network that uses relaxation oscillators that are represented by objective hysteresis neurons whose output in range [-1, +1].
155 
156  Examples:
157  @code
158  # create hysteresis oscillatory network with 5 oscillators.
159  network = hysteresis_network(5);
160 
161  # set initial states (from range [-1, +1]).
162  network.states = [1 0 1 0 1];
163 
164  # set initial outputs.
165  network.outputs = [1 1 1 1 1];
166 
167  # perform simulation of the network during 1000 steps in 10 time units.
168  output_dynamic = network.simulate(1000, 10);
169 
170  # show output dynamic of the network.
171  hysteresis_visualizer.show_output_dynamic(output_dynamic);
172 
173  # obtain synchronous ensembles.
174  ensembles = output_dynamic.allocate_sync_ensembles(tolerance = 0.5, threshold_steps = 5);
175  print(ensembles);
176  @endcode
177 
178  """
179 
180  @property
181  def outputs(self):
182  """!
183  @brief Returns current outputs of neurons.
184 
185  @return (list) Current outputs of neurons.
186 
187  """
188 
189  return self._outputs;
190 
191  @outputs.setter
192  def outputs(self, values):
193  """!
194  @brief Sets outputs of neurons.
195 
196  """
197 
198  self._outputs = [val for val in values];
199  self._outputs_buffer = [val for val in values];
200 
201  @property
202  def states(self):
203  """!
204  @brief Return current states of neurons.
205 
206  @return (list) States of neurons.
207 
208  """
209 
210  return self._states;
211 
212  @states.setter
213  def states(self, values):
214  """!
215  @brief Set current states of neurons.
216 
217  """
218 
219  self._states = [val for val in values];
220 
221 
222  def __init__(self, num_osc, own_weight = -4, neigh_weight = -1, type_conn = conn_type.ALL_TO_ALL, type_conn_represent = conn_represent.MATRIX):
223  """!
224  @brief Constructor of hysteresis oscillatory network.
225 
226  @param[in] num_osc (uint): Number of oscillators in the network.
227  @param[in] own_weight (double): Weight of connection from oscillator to itself - own weight.
228  @param[in] neigh_weight (double): Weight of connection between oscillators.
229  @param[in] type_conn (conn_type): Type of connection between oscillators in the network.
230  @param[in] type_conn_represent (conn_represent): Internal representation of connection in the network: matrix or list.
231 
232  """
233 
234  super().__init__(num_osc, type_conn, type_conn_represent);
235 
236  # list of states of neurons.
237  self._states = [0] * self._num_osc;
238 
239  # list of current outputs of neurons.
240  self._outputs = [-1] * self._num_osc;
241 
242  # list of previous outputs of neurons
243  self._outputs_buffer = [-1] * self._num_osc;
244 
245  # matrix of connection weights between neurons.
246  self._weight = list();
247  for index in range(0, self._num_osc, 1):
248  self._weight.append( [neigh_weight] * self._num_osc);
249  self._weight[index][index] = own_weight;
250 
251 
252  def _neuron_states(self, inputs, t, argv):
253  """!
254  @brief Returns new value of the neuron (oscillator).
255 
256  @param[in] inputs (list): Initial values (current) of the neuron - excitatory.
257  @param[in] t (double): Current time of simulation.
258  @param[in] argv (tuple): Extra arguments that are not used for integration - index of the neuron.
259 
260  @return (double) New value of the neuron.
261 
262  """
263 
264  xi = inputs[0];
265  index = argv;
266 
267  # own impact
268  impact = self._weight[index][index] * self._outputs[index];
269 
270  for i in range(0, self._num_osc, 1):
271  if (self.has_connection(i, index)):
272  impact += self._weight[index][i] * self._outputs[i];
273 
274  x = -xi + impact;
275 
276  if (xi > 1): self._outputs_buffer[index] = 1;
277  if (xi < -1): self._outputs_buffer[index] = -1;
278 
279  return x;
280 
281 
282  def simulate(self, steps, time, solution = solve_type.RK4, collect_dynamic = True):
283  """!
284  @brief Performs static simulation of hysteresis oscillatory network.
285 
286  @param[in] steps (uint): Number steps of simulations during simulation.
287  @param[in] time (double): Time of simulation.
288  @param[in] solution (solve_type): Type of solution (solving).
289  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
290 
291  @return (hysteresis_dynamic) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
292  otherwise returns only last values (last step of simulation) of dynamic.
293  """
294 
295  return self.simulate_static(steps, time, solution, collect_dynamic);
296 
297 
298  def simulate_static(self, steps, time, solution = solve_type.RK4, collect_dynamic = False):
299  """!
300  @brief Performs static simulation of hysteresis oscillatory network.
301 
302  @param[in] steps (uint): Number steps of simulations during simulation.
303  @param[in] time (double): Time of simulation.
304  @param[in] solution (solve_type): Type of solution (solving).
305  @param[in] collect_dynamic (bool): If True - returns whole dynamic of oscillatory network, otherwise returns only last values of dynamics.
306 
307  @return (hysteresis_dynamic) Dynamic of oscillatory network. If argument 'collect_dynamic' = True, than return dynamic for the whole simulation time,
308  otherwise returns only last values (last step of simulation) of dynamic.
309 
310  """
311 
312  # Check solver before simulation
313  if (solution == solve_type.FAST):
314  raise NameError("Solver FAST is not support due to low accuracy that leads to huge error.");
315  elif (solution == solve_type.RKF45):
316  raise NameError("Solver RKF45 is not support in python version.");
317 
318  dyn_state = None;
319  dyn_time = None;
320 
321  if (collect_dynamic == True):
322  dyn_state = [];
323  dyn_time = [];
324 
325  dyn_state.append(self._states);
326  dyn_time.append(0);
327 
328  step = time / steps;
329  int_step = step / 10.0;
330 
331  for t in numpy.arange(step, time + step, step):
332  # update states of oscillators
333  self._states = self._calculate_states(solution, t, step, int_step);
334 
335  # update states of oscillators
336  if (collect_dynamic is True):
337  dyn_state.append(self._states);
338  dyn_time.append(t);
339 
340  if (collect_dynamic is False):
341  dyn_state.append(self._states);
342  dyn_time.append(time);
343 
344  return hysteresis_dynamic(dyn_state, dyn_time);
345 
346 
347  def _calculate_states(self, solution, t, step, int_step):
348  """!
349  @brief Calculates new states for neurons using differential calculus. Returns new states for neurons.
350 
351  @param[in] solution (solve_type): Type solver of the differential equation.
352  @param[in] t (double): Current time of simulation.
353  @param[in] step (double): Step of solution at the end of which states of oscillators should be calculated.
354  @param[in] int_step (double): Step differentiation that is used for solving differential equation.
355 
356  @return (list) New states for neurons.
357 
358  """
359 
360  next_states = [0] * self._num_osc;
361 
362  for index in range (0, self._num_osc, 1):
363  result = odeint(self._neuron_states, self._states[index], numpy.arange(t - step, t, int_step), (index , ));
364  next_states[index] = result[len(result) - 1][0];
365 
366  self._outputs = [val for val in self._outputs_buffer];
367  return next_states;
def time(self)
(list) Returns sampling times when dynamic is measured during simulation.
Definition: hysteresis.py:52
def _calculate_states(self, solution, t, step, int_step)
Calculates new states for neurons using differential calculus.
Definition: hysteresis.py:347
def outputs(self)
Returns current outputs of neurons.
Definition: hysteresis.py:181
Utils that are used by modules of pyclustering.
Definition: __init__.py:1
Hysteresis oscillatory network that uses relaxation oscillators that are represented by objective hys...
Definition: hysteresis.py:152
def output(self)
(list) Returns outputs of oscillator during simulation.
Definition: hysteresis.py:43
Represents output dynamic of hysteresis oscillatory network.
Definition: hysteresis.py:36
Visualizer of output dynamic of hysteresis oscillatory network.
Definition: hysteresis.py:134
def allocate_sync_ensembles(self, tolerance=0.1, threshold_steps=1)
Allocate clusters in line with ensembles of synchronous oscillators where each synchronous ensemble c...
Definition: hysteresis.py:87
def has_connection(self, i, j)
Returns True if there is connection between i and j oscillators and False - if connection doesn&#39;t exi...
Definition: __init__.py:366
def __init__(self, amplitudes, time)
Constructor of hysteresis neural network dynamic.
Definition: hysteresis.py:61
def simulate(self, steps, time, solution=solve_type.RK4, collect_dynamic=True)
Performs static simulation of hysteresis oscillatory network.
Definition: hysteresis.py:282
def simulate_static(self, steps, time, solution=solve_type.RK4, collect_dynamic=False)
Performs static simulation of hysteresis oscillatory network.
Definition: hysteresis.py:298
def show_output_dynamic(hysteresis_output_dynamic)
Shows output dynamic (output of each oscillator) during simulation.
Definition: hysteresis.py:141
def states(self)
Return current states of neurons.
Definition: hysteresis.py:202
Common network description that consists of information about oscillators and connection between them...
Definition: __init__.py:97
def _neuron_states(self, inputs, t, argv)
Returns new value of the neuron (oscillator).
Definition: hysteresis.py:252
Neural and oscillatory network module.
Definition: __init__.py:1
def __len__(self)
(uint) Returns number of simulation steps that are stored in dynamic.
Definition: hysteresis.py:78
def __init__(self, num_osc, own_weight=-4, neigh_weight=-1, type_conn=conn_type.ALL_TO_ALL, type_conn_represent=conn_represent.MATRIX)
Constructor of hysteresis oscillatory network.
Definition: hysteresis.py:222