pyCLINE 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyCLINE/model.py ADDED
@@ -0,0 +1,1045 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import os
4
+ import matplotlib.pyplot as plt
5
+ import jitcdde as jitcdde
6
+
7
+ # ------------------- FitzHugh-Nagumo oscillator -------------------
8
+ class FHN:
9
+ """
10
+ The Fitzhuge-Nagumo model is a simplified model of the electrical activity of a neuron. In this form taken from Prokop et al., iScience (2024).
11
+ """
12
+ def __init__(self, p=[1, 1, 0.3, 0.5, 0.0]):
13
+ self.p = p # parameters of the model
14
+
15
+ def model(self, U):
16
+ """
17
+ Model formulation of the FHN model.
18
+ With:
19
+ p = [c, d, eps, b, a]
20
+ p = [1, 1, 0.3, 0.5, 0.0]
21
+ Prokop et al., iScience (2024)
22
+
23
+ Args:
24
+ U (array): Array of initial conditions for the model.
25
+
26
+ Returns:
27
+ U (array): Array of the next state of the model.
28
+ """
29
+ u= U[0]
30
+ v= U[1]
31
+ # p = [c, d, eps, b, a]; Prokop et al., iScience (2024)
32
+ # p = [1, 1, 0.3, 0.5, 0.0]
33
+ return np.array([-u**3 + self.p[0]*u**2 + self.p[1]*u - v,
34
+ self.p[2]*(u - self.p[3]*v + self.p[4])])
35
+
36
+ def vnull(self, u):
37
+ """
38
+ Calculate the v-nullcline of the FHN model
39
+
40
+ Args:
41
+ u (array): Array of values of the v-variable.
42
+
43
+ Returns:
44
+ v: Array of values of the v-nullcline.
45
+ """
46
+ return -u**3 + self.p[0]*u**2 + self.p[1]*u
47
+
48
+ def unull(self, v):
49
+ """
50
+ Calculate the u-nullcline of the FHN model
51
+
52
+ Args:
53
+ v (array): Array of values of the v-variable.
54
+
55
+ Returns:
56
+ u: Array of values of the u-nullcline.
57
+ """
58
+ return self.p[3]*v - self.p[4]
59
+
60
+ def fixed_points(self):
61
+ """
62
+ Calculate the fixed points of the FHN model.
63
+
64
+ Returns:
65
+ fp (Array): Array of fixed points of the model.
66
+ """
67
+ sol = np.roots([-self.p[3], self.p[3]*self.p[0], (self.p[1]*self.p[3]-1), -self.p[4]])
68
+
69
+ rids = np.where(np.imag(sol)==0)
70
+ rsol = sol[rids]
71
+
72
+ fp = np.zeros([2,rsol.shape[0]])
73
+
74
+ fp[0,:] = np.real(rsol)
75
+ fp[1,:] = (fp[0,:] + self.p[4])/self.p[3]
76
+
77
+ return fp
78
+
79
+ def simulate(self, U, dt):
80
+ """
81
+ Generates the next state of the FHN model using the Runge-Kutta 4th order solver.
82
+
83
+ Args:
84
+ U (array): Array of initial conditions for the model.
85
+ dt (float): Time step.
86
+
87
+ Returns:
88
+ U (array): Array of the next state of the model.
89
+ """
90
+ return rk4_solver(self.model, U, dt)
91
+
92
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=2, check_period=False):
93
+ """
94
+ Generates synthetic data for the FHN model.
95
+
96
+ Args:
97
+ x0 (array): Array of initial conditions for the model.
98
+ dt (float): Time step.
99
+ N (int, optional): Number of time steps. Defaults to 10000.
100
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
101
+ plot (bool, optional): Plot the generated data. Defaults to False.
102
+ max_time (int, optional): Maximum time for the plot. Defaults to 2.
103
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
104
+ """
105
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
106
+ if save:
107
+ save_data(df, f'{self.__class__.__name__}_eps={self.p[2]}_a={self.p[4]}.csv')
108
+ if plot:
109
+ plot_data(df, max_time=max_time)
110
+
111
+
112
+ # ------------------- bicubic oscillator -------------------
113
+ class Bicubic:
114
+ """
115
+ The bicubic model if a model with two nonlinear nullclines (one bistable and one s-shaped) taken from Prokop et al., Chaos (2024).
116
+ """
117
+ def __init__(self,p=[-0.5, 0.5, -0.3]):
118
+ self.p = p
119
+
120
+ def model(self, U):
121
+ """
122
+ Model formulation of the bicubic model.
123
+ With:
124
+ p = [-0.5, 0.5, -0.3];
125
+ Prokop et al., Chaos (2024)
126
+
127
+ Args:
128
+ U (array): Array of initial conditions for the model.
129
+
130
+ Returns:
131
+ U (array): Array of the next state of the model.
132
+ """
133
+
134
+ u= U[0]
135
+ v= U[1]
136
+ return np.array([-u**3 + u**2 + u - v,
137
+ self.p[0]*v**3 + self.p[1]*v**2 + self.p[2]*v + u])
138
+
139
+ def vnull(self, u):
140
+ """
141
+ Calculate the v-nullcline of the bicubic model
142
+
143
+ Args:
144
+ u (array): Array of values of the v-variable.
145
+
146
+ Returns:
147
+ v: Array of values of the v-nullcline.
148
+ """
149
+ return -u**3 + u**2 + u
150
+
151
+ def unull(self, v):
152
+ """
153
+ Calculate the u-nullcline of the bicubic model
154
+
155
+ Args:
156
+ v (array): Array of values of the v-variable.
157
+
158
+ Returns:
159
+ u: Array of values of the u-nullcline.
160
+ """
161
+ return -(self.p[0]*v**3 + self.p[1]*v**2 + self.p[2]*v)
162
+
163
+ def simulate(self, U, dt):
164
+ """
165
+ Generates the next state of the bicubic model using the Runge-Kutta 4th order solver.
166
+
167
+ Args:
168
+ U (array): Array of initial conditions for the model.
169
+ dt (float): Time step.
170
+
171
+ Returns:
172
+ U (array): Array of the next state of the model.
173
+ """
174
+ return rk4_solver(self.model, U, dt)
175
+
176
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=2, check_period=False):
177
+ """
178
+ Generates synthetic data for the biubic model.
179
+
180
+ Args:
181
+ x0 (array): Array of initial conditions for the model.
182
+ dt (float): Time step.
183
+ N (int, optional): Number of time steps. Defaults to 10000.
184
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
185
+ plot (bool, optional): Plot the generated data. Defaults to False.
186
+ max_time (int, optional): Maximum time for the plot. Defaults to 2.
187
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
188
+ """
189
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
190
+ if save:
191
+ save_data(df, f'{self.__class__.__name__}.csv')
192
+ if plot:
193
+ plot_data(df, max_time=max_time)
194
+
195
+ # ------------------- gene expression oscillator -------------------
196
+ class GeneExpression:
197
+ """
198
+ The gene expression model is a model of the regulation of gene expression taken from Novak & Tyson, Nature Rev Mol Cell Biol (2008).
199
+ """
200
+ def __init__(self, p=[1, 0.05, 1, 0.05, 1, 0.05, 1, 1, 0.1, 2]):
201
+ self.p = p
202
+
203
+ def model(self, U):
204
+ """
205
+ Model formulation of the gene expression model.
206
+ With:
207
+ p = [S, k1, Kd, kdx, ksy, kdy, k2, ET, Km, KI]
208
+ p = [1, 0.05, 1, 0.05, 1, 0.05, 1, 1, 0.1, 2];
209
+ Novak & Tyson, Nature Rev Mol Cell Biol (2008)
210
+
211
+ Args:
212
+ U (array): Array of initial conditions for the model.
213
+
214
+ Returns:
215
+ U (array): Array of the next state of the model.
216
+ """
217
+ u= U[0]
218
+ v= U[1]
219
+ # p = [S, k1, Kd, kdx, ksy, kdy, k2, ET, Km, KI]
220
+ # p = [1, 0.05, 1, 0.05, 1, 0.05, 1, 1, 0.1, 2]; Novak & Tyson, Nature Rev Mol Cell Biol (2008)
221
+ return np.array([self.p[4]*v - self.p[5]*u - self.p[6]*self.p[7]*u/(self.p[8] + u + self.p[9]*u**2),
222
+ self.p[1]*self.p[0]*self.p[2]**4/(self.p[2]**4 + u**4) - self.p[3]*v])
223
+
224
+ def vnull(self, v):
225
+ """
226
+ Calculate the v-nullcline of the gene expression model
227
+
228
+ Args:
229
+ v (array): Array of values of the v-variable.
230
+
231
+ Returns:
232
+ u: Array of values of the v-nullcline.
233
+ """
234
+ return self.p[1]*self.p[0]/self.p[3]*self.p[2]**4/(self.p[2]**4 + v**4)
235
+
236
+ def unull(self, v):
237
+ """
238
+ Calculate the u-nullcline of the gene expression model
239
+
240
+ Args:
241
+ v (array): Array of values of the v-variable.
242
+
243
+ Returns:
244
+ u: Array of values of the u-nullcline.
245
+ """
246
+ return self.p[5]*v/self.p[4] + self.p[6]*self.p[7]*v/(self.p[8] + v + self.p[9]*v**2)
247
+
248
+ def simulate(self, U, dt):
249
+ """
250
+ Generates the next state of the gene expression model using the Runge-Kutta 4th order solver.
251
+
252
+ Args:
253
+ U (array): Array of initial conditions for the model.
254
+ dt (float): Time step.
255
+
256
+ Returns:
257
+ U (array): Array of the next state of the model.
258
+ """
259
+ return rk4_solver(self.model, U, dt)
260
+
261
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=10, check_period=False):
262
+ """
263
+ Generates synthetic data for the gene expression model.
264
+
265
+ Args:
266
+ x0 (array): Array of initial conditions for the model.
267
+ dt (float): Time step.
268
+ N (int, optional): Number of time steps. Defaults to 10000.
269
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
270
+ plot (bool, optional): Plot the generated data. Defaults to False.
271
+ max_time (int, optional): Maximum time for the plot. Defaults to 10.
272
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
273
+ """
274
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
275
+ if save:
276
+ save_data(df, f'{self.__class__.__name__}.csv')
277
+ if plot:
278
+ plot_data(df, max_time=max_time)
279
+
280
+ # ------------------- Gylcolytic oscillator -------------------
281
+ class GlycolyticOscillations:
282
+ """
283
+ The glycolytic oscillator is a model of glycolytic oscillations introduced in Prokop et al., iScience (2024) and in
284
+ this form taken from Prokop et al., Chaos (2024).
285
+ """
286
+ def __init__(self, p=[-0.3, -2.2, 0.25, -0.5, 0.5, 1.8, 0.7, -0.3]):
287
+ self.p = p
288
+
289
+ def model(self, U):
290
+ """
291
+ Model formulation of the glycolytic oscillations model.
292
+ With:
293
+ p = [a, b, c, d, e, f, g, h]
294
+ p = [-0.3, -2.2, 0.25, -0.5, 0.5, 1.8, 0.7, -0.3]
295
+ Prokop et al., Chaos (2024)
296
+
297
+ Args:
298
+ U (array): Array of initial conditions for the model.
299
+
300
+ Returns:
301
+ U (array): Array of the next state of the model.
302
+ """
303
+ u= U[0]
304
+ v= U[1]
305
+
306
+ return np.array([self.p[0]*u + self.p[1]*v + self.p[2]*u**2 + self.p[3]*u**3 + self.p[4]*v**3,
307
+ self.p[5]*u+self.p[6]*v+self.p[7]*u**3])
308
+
309
+ def simulate(self, U, dt):
310
+ """
311
+ Generates the next state of the glycolytic oscillations model using the Runge-Kutta 4th order solver.
312
+
313
+ Args:
314
+ U (array): Array of initial conditions for the model.
315
+ dt (float): Time step.
316
+
317
+ Returns:
318
+ U (array): Array of the next state of the model.
319
+ """
320
+ return rk4_solver(self.model, U, dt)
321
+
322
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=10, check_period=False):
323
+ """
324
+ Generates synthetic data for the glycolytic oscillations model.
325
+
326
+ Args:
327
+ x0 (array): Array of initial conditions for the model.
328
+ dt (float): Time step.
329
+ N (int, optional): Number of time steps. Defaults to 10000.
330
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
331
+ plot (bool, optional): Plot the generated data. Defaults to False.
332
+ max_time (int, optional): Maximum time for the plot. Defaults to 10.
333
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
334
+ """
335
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
336
+ if save:
337
+ save_data(df, f'{self.__class__.__name__}.csv')
338
+ if plot:
339
+ plot_data(df, max_time=max_time)
340
+
341
+ # ------------------- Goodwin oscillator -------------------
342
+ class Goodwin:
343
+ """
344
+ The Goodwin model is a three dimensional model for phosphorylation/dephosphorylation processes of a transcription factor,
345
+ taken in this form from Gonze et al., Acta Biotheoretica (2021), parameters from Prokop et al. (2024).
346
+ """
347
+ def __init__(self, p=[1,1,1,0.1,0.1,0.1,10,1]):
348
+ self.p = p
349
+
350
+ def model(self, U):
351
+ """
352
+ Model formulation of the Goodwin model.
353
+ With:
354
+ p = [a,b,c,d,e,f,n,K]
355
+ p = [1,1,1,0.1,0.1,0.1,10,1]
356
+ Prokop et al., iScience (2024)
357
+
358
+ Args:
359
+ U (array): Array of initial conditions for the model.
360
+
361
+ Returns:
362
+ U (array): Array of the next state of the model.
363
+ """
364
+ u= U[0]
365
+ v= U[1]
366
+ w= U[2]
367
+ # p = [a,b,c,d,e,f,n,K]
368
+ # p = [1,1,1,0.1,0.1,0.1,10,1]; from Prokop et al. (2024)
369
+ return np.array([self.p[0]*(self.p[7]**self.p[6])/(self.p[7]**self.p[6] + w**self.p[6]) - self.p[3]*u,
370
+ self.p[1]*u - self.p[4]*v,
371
+ self.p[2]*v - self.p[5]*w])
372
+ def unull(self, v, w):
373
+ """
374
+ Calculate the u-nullcline of the Goodwin model
375
+
376
+ Args:
377
+ v (array): Array of values of the v-variable.
378
+ w (array): Array of values of the w-variable.
379
+
380
+ Returns:
381
+ u: Array of values of the u-nullcline.
382
+ """
383
+ return self.p[0]/self.p[3]*((self.p[7]**self.p[6])/(self.p[7]**self.p[6] + w**self.p[6])) + 0 * v
384
+
385
+ def vnull(self, u, w):
386
+ """
387
+ Calculate the v-nullcline of the Goodwin model
388
+
389
+ Args:
390
+ u (array): Array of values of the v-variable.
391
+ w (array): Array of values of the w-variable.
392
+
393
+ Returns:
394
+ v: Array of values of the v-nullcline.
395
+ """
396
+ return self.p[1]*u/self.p[4] + 0 * w
397
+
398
+ def wnull(self, u, v):
399
+ """
400
+ Calculate the w-nullcline of the Goodwin model
401
+
402
+ Args:
403
+ u (array): Array of values of the u-variable.
404
+ v (array): Array of values of the v-variable.
405
+
406
+ Returns:
407
+ w: Array of values of the w-nullcline.
408
+ """
409
+ return self.p[2]*v/self.p[5] + 0 * u
410
+
411
+ def simulate(self, U, dt):
412
+ """
413
+ Generates the next state of the Goodwin model using the Runge-Kutta 4th order solver.
414
+
415
+ Args:
416
+ U (array): Array of initial conditions for the model.
417
+ dt (float): Time step.
418
+
419
+ Returns:
420
+ U (array): Array of the next state of the model.
421
+ """
422
+ return rk4_solver(self.model, U, dt)
423
+
424
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=10, check_period=False):
425
+ """
426
+ Generates synthetic data for the gene expression model.
427
+
428
+ Args:
429
+ x0 (array): Array of initial conditions for the model.
430
+ dt (float): Time step.
431
+ N (int, optional): Number of time steps. Defaults to 10000.
432
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
433
+ plot (bool, optional): Plot the generated data. Defaults to False.
434
+ max_time (int, optional): Maximum time for the plot. Defaults to 10.
435
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
436
+ """
437
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
438
+ if save:
439
+ save_data(df, f'{self.__class__.__name__}.csv')
440
+ if plot:
441
+ plot_data(df, max_time=max_time)
442
+
443
+ # ------------------- Oregonator oscillator -------------------
444
+
445
+ class Oregonator:
446
+ """
447
+ The Oregonator model is a model of the Belousov-Zhabotinsky reaction, taken from Tyson, Journal of Chemical Physics (1975).
448
+ """
449
+ def __init__(self, p = [0.005,3,0.60,1e-2]):
450
+ self.p = p
451
+
452
+ def model(self, U):
453
+ """
454
+ Model formulation of the Oregonator model.
455
+ With:
456
+ p = [q,p,f,e]
457
+ p = [0.005,3,0.60,1e-2]
458
+ Tyson, Journal of Chemical Physics (1975)
459
+
460
+ Args:
461
+ U (array): Array of initial conditions for the model.
462
+
463
+ Returns:
464
+ U (array): Array of the next state of the model.
465
+ """
466
+ x, y, z = U
467
+ #1977 Tyson
468
+ q=self.p[0]
469
+ p=self.p[1]
470
+ f=self.p[2] #0.1-2.0
471
+ e=self.p[3]
472
+ a=1-f+(3*f)*q/(1-f)
473
+ b=(1-f)/(q)-(1-3*f)/(1-f)
474
+ g=f-(f*q)/(1-f)
475
+ d=(1-f)/(q)+(1+f)/(1-f)
476
+
477
+ dx=(1/e)*(-a*x-b*y-q*x**2-x*y)
478
+ dy=-g*x-d*y+f*z-x*y
479
+ dz=(1/p)*(x-z)
480
+ return np.array([dx, dy, dz])
481
+
482
+ def xnull(self, x, z):
483
+ """
484
+ Calculate the x-nullcline of the Oregonator model
485
+
486
+ Args:
487
+ x (array): Array of values of the x-variable.
488
+ z (array): Array of values of the z-variable.
489
+
490
+ Returns:
491
+ y: Array of values of the x-nullcline.
492
+ """
493
+ q=self.p[0]
494
+ p=self.p[1]
495
+ f=self.p[2] #0.1-2.0
496
+ e=self.p[3]
497
+ a=1-f+(3*f)*q/(1-f)
498
+ b=(1-f)/(q)-(1-3*f)/(1-f)
499
+ g=f-(f*q)/(1-f)
500
+ d=(1-f)/(q)+(1+f)/(1-f)
501
+ return (-a*x-q*x**2)/(b+x) + 0*z
502
+
503
+ def ynull(self, x, y):
504
+ """
505
+ Calculate the y-nullcline of the Oregonator model
506
+
507
+ Args:
508
+ x (array): Array of values of the x-variable.
509
+ y (array): Array of values of the y-variable.
510
+
511
+ Returns:
512
+ z: Array of values of the y-nullcline.
513
+ """
514
+ q=self.p[0]
515
+ p=self.p[1]
516
+ f=self.p[2] #0.1-2.0
517
+ e=self.p[3]
518
+ a=1-f+(3*f)*q/(1-f)
519
+ b=(1-f)/(q)-(1-3*f)/(1-f)
520
+ g=f-(f*q)/(1-f)
521
+ d=(1-f)/(q)+(1+f)/(1-f)
522
+ return (1/f)*(g*x+d*y+x*y)
523
+
524
+ def znull(self, x, y):
525
+ """
526
+ Calculate the x-nullcline of the Oregonator model
527
+
528
+ Args:
529
+ x (array): Array of values of the x-variable.
530
+ y (array): Array of values of the y-variable.
531
+
532
+ Returns:
533
+ z: Array of values of the z-nullcline.
534
+ """
535
+ return x + 0*y
536
+
537
+ def simulate(self, U, dt):
538
+ """
539
+ Generates the next state of the Oregonator model using the Runge-Kutta 4th order solver.
540
+
541
+ Args:
542
+ U (array): Array of initial conditions for the model.
543
+ dt (float): Time step.
544
+
545
+ Returns:
546
+ U (array): Array of the next state of the model.
547
+ """
548
+ return rk4_solver(self.model, U, dt)
549
+
550
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=10, check_period=False):
551
+ """
552
+ Generates synthetic data for the Oregonator model.
553
+
554
+ Args:
555
+ x0 (array): Array of initial conditions for the model.
556
+ dt (float): Time step.
557
+ N (int, optional): Number of time steps. Defaults to 10000.
558
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
559
+ plot (bool, optional): Plot the generated data. Defaults to False.
560
+ max_time (int, optional): Maximum time for the plot. Defaults to 10.
561
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
562
+ """
563
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
564
+ if save:
565
+ save_data(df, f'{self.__class__.__name__}.csv')
566
+ if plot:
567
+ plot_data(df, max_time=max_time)
568
+
569
+ class Lorenz:
570
+ """
571
+ The Lorenz model is a simple model of atmospheric convection, taken from Lorenz, Journal of the Atmospheric Sciences (1963).
572
+ """
573
+ def __init__(self, p = [-0.5,65]):
574
+ self.p = p
575
+
576
+ def model(self, U):
577
+ """
578
+ Model formulation of the Lorenz model.
579
+ With:
580
+ p = [a, rho]
581
+ p = [-0.5,65]
582
+
583
+ Args:
584
+ U (array): Array of initial conditions for the model.
585
+
586
+ Returns:
587
+ U (array): Array of the next state of the model.
588
+ """
589
+ x,y,z = U
590
+ a, rho = self.p
591
+ return np.array([a*rho*(x-y)-a*y*z, rho*x - y -x*z, -z +x*y])
592
+
593
+ def xnull(self, y, z):
594
+ """
595
+ Calculate the x-nullcline of the Lorenz model
596
+
597
+ Args:
598
+ y (array): Array of values of the y-variable.
599
+ z (array): Array of values of the z-variable.
600
+
601
+ Returns:
602
+ x: Array of values of the x-nullcline.
603
+ """
604
+ a, rho = self.p
605
+ return (1/rho)*y*z +y
606
+
607
+ def ynull(self, x, z):
608
+ """
609
+ Calculate the y-nullcline of the Lorenz model
610
+
611
+ Args:
612
+ x (array): Array of values of the x-variable.
613
+ z (array): Array of values of the z-variable.
614
+
615
+ Returns:
616
+ y: Array of values of the y-nullcline.
617
+ """
618
+ a, rho = self.p
619
+ return x*z - rho*x
620
+
621
+ def znull(self, x, y):
622
+ """
623
+ Calculate the z-nullcline of the Lorenz model
624
+
625
+ Args:
626
+ x (array): Array of values of the x-variable.
627
+ y (array): Array of values of the y-variable.
628
+
629
+ Returns:
630
+ z: Array of values of the z-nullcline.
631
+ """
632
+ return x*y
633
+
634
+ def simulate(self, U, dt):
635
+ """
636
+ Generates the next state of the Lorenz model using the Runge-Kutta 4th order solver.
637
+
638
+ Args:
639
+ U (array): Array of initial conditions for the model.
640
+ dt (float): Time step.
641
+
642
+ Returns:
643
+ U (array): Array of the next state of the model.
644
+ """
645
+ return rk4_solver(self.model, U, dt)
646
+
647
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=25, check_period=False):
648
+ """
649
+ Generates synthetic data for the Oregonator model.
650
+
651
+ Args:
652
+ x0 (array): Array of initial conditions for the model.
653
+ dt (float): Time step.
654
+ N (int, optional): Number of time steps. Defaults to 10000.
655
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
656
+ plot (bool, optional): Plot the generated data. Defaults to False.
657
+ max_time (int, optional): Maximum time for the plot. Defaults to 25.
658
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
659
+ """
660
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
661
+ if save:
662
+ save_data(df, f'{self.__class__.__name__}.csv')
663
+ if plot:
664
+ plot_data(df, max_time=max_time)
665
+
666
+ class Roessler:
667
+ """
668
+ The Roessler model is a simple model of a chaotic oscillator, taken from Roessler, Zeitschrift für Naturforschung A (1976).
669
+ """
670
+ def __init__(self, p = [0.2,0.2,1]):
671
+ self.p = p
672
+
673
+ def model(self, U):
674
+ """
675
+ Model formulation of the Roessler model.
676
+ With:
677
+ p = [abc]
678
+ p = [0.2,0.2,1]
679
+ Roessler, Zeitschrift für Naturforschung A (1976)
680
+
681
+ Args:
682
+ U (array): Array of initial conditions for the model.
683
+
684
+ Returns:
685
+ U (array): Array of the next state of the model.
686
+ """
687
+ x,y,z = U
688
+ a,b,c=self.p
689
+ dx=-y-z
690
+ dy = x+a*y
691
+ dz = b+(x-c)*z
692
+ return np.array([dx, dy, dz])
693
+
694
+ def xnull(self, x, z):
695
+ """
696
+ Calculate the x-nullcline of the Roessler model
697
+
698
+ Args:
699
+ x (array): Array of values of the x-variable.
700
+ z (array): Array of values of the z-variable.
701
+
702
+ Returns:
703
+ y: Array of values of the x-nullcline.
704
+ """
705
+ a,b,c=self.p
706
+ return -z
707
+
708
+ def ynull(self, x, z):
709
+ """
710
+ Calculate the y-nullcline of the Roessler model
711
+
712
+ Args:
713
+ x (array): Array of values of the x-variable.
714
+ z (array): Array of values of the z-variable.
715
+
716
+ Returns:
717
+ y: Array of values of the y-nullcline.
718
+ """
719
+ a,b,c=self.p
720
+ return -(1/a)*x
721
+
722
+ def znull(self, x, y):
723
+ """
724
+ Calculate the z-nullcline of the Roessler model
725
+
726
+ Args:
727
+ x (array): Array of values of the x-variable.
728
+ y (array): Array of values of the y-variable.
729
+
730
+ Returns:
731
+ z: Array of values of the z-nullcline.
732
+ """
733
+ a,b,c=self.p
734
+ return b/(c-x)
735
+
736
+ def simulate(self, U, dt):
737
+ """
738
+ Generates the next state of the Roessler model using the Runge-Kutta 4th order solver.
739
+
740
+ Args:
741
+ U (array): Array of initial conditions for the model.
742
+ dt (float): Time step.
743
+
744
+ Returns:
745
+ U (array): Array of the next state of the model.
746
+ """
747
+ return rk4_solver(self.model, U, dt)
748
+
749
+ def generate_data(self, x0, dt, N=10000, save=True, plot=False, max_time=25, check_period=False):
750
+ """
751
+ Generates synthetic data for the Roessler model.
752
+
753
+ Args:
754
+ x0 (array): Array of initial conditions for the model.
755
+ dt (float): Time step.
756
+ N (int, optional): Number of time steps. Defaults to 10000.
757
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
758
+ plot (bool, optional): Plot the generated data. Defaults to False.
759
+ max_time (int, optional): Maximum time for the plot. Defaults to 25.
760
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
761
+ """
762
+ df=simulate_data(self.simulate, x0, dt, N, check_period)
763
+ if save:
764
+ save_data(df, f'{self.__class__.__name__}.csv')
765
+ if plot:
766
+ plot_data(df, max_time=max_time)
767
+
768
+ # ------------------- Delay oscillator -------------------
769
+ class DelayOscillator:
770
+ """
771
+ Simple model of a Delay Oscillator with a single delay, inspired by Lewis, Current Biology (2003)
772
+ """
773
+ def __init__(self, p=[4,10,2]):
774
+ self.p = p
775
+
776
+ def model(self):
777
+ """
778
+ Model formulation of the delay oscillator model.
779
+ With:
780
+ p = [beta, tau, n]
781
+ p = [4,10,2]
782
+
783
+ Args:
784
+ U (array): Array of initial conditions for the model.
785
+
786
+ Returns:
787
+ U (array): Array of the next state of the model.
788
+ """
789
+ self.DDE = [self.p[0]/(1+jitcdde.y(0,jitcdde.t-self.p[1])**self.p[2])-jitcdde.y(0)]
790
+ return self.DDE
791
+
792
+ def unull(self, y):
793
+ """
794
+ Calculate the u-nullcline of the delay oscillator model
795
+
796
+ Args:
797
+ y (array): Array of values of the v-variable.
798
+
799
+ Returns:
800
+ x: Array of values of the u-nullcline.
801
+ """
802
+ return self.p[0]/(1+y**self.p[2])
803
+
804
+ def vnull(self, x):
805
+ """
806
+ Calculate the u-nullcline of the delay oscillator model
807
+
808
+ Args:
809
+ x (array): Array of values of the u-variable.
810
+
811
+ Returns:
812
+ y: Array of values of the v-nullcline.
813
+ """
814
+ return (self.p[0]/x-1)**(1/self.p[2])
815
+
816
+ def simulate(self, dt, t_max, y_0=0):
817
+ """
818
+ Simulates the delay oscillatory model using the jitcdde solver.
819
+
820
+ Args:
821
+ dt (float): Time step.
822
+ t_max (float): Maximum time of the simulation.
823
+ y_0 (int, optional): Initial condition. Defaults to 0.
824
+
825
+ Returns:
826
+ array : Simulated data of the model.
827
+ """
828
+ self.model()
829
+ DDE = jitcdde.jitcdde(self.DDE)
830
+ DDE.constant_past(y_0)
831
+ DDE.step_on_discontinuities()
832
+
833
+ data = []
834
+ for time in np.arange(DDE.t, DDE.t +t_max, dt):
835
+ data.append(DDE.integrate(time))
836
+ data = np.array(data)
837
+
838
+ return data
839
+
840
+ def generate_data(self, y_0, dt, t_max, save=True, plot=False, check_period=False):
841
+ """
842
+ Generates synthetic data for the delay oscillator model.
843
+
844
+ Args:
845
+ y_0 (float): Initial condition for the model.
846
+ dt (float): Time step.
847
+ t_max (float): Maximum time of the simulation.
848
+ save (bool, optional): Save the generated data to a csv file. Defaults to True.
849
+ plot (bool, optional): Plot the generated data. Defaults to False.
850
+ check_period (bool, optional): Check the period of the oscillation. Defaults to False.
851
+ """
852
+ data = self.simulate( dt, t_max,y_0)
853
+ df = pd.DataFrame(data, columns=['u'])
854
+ df['time'] = np.arange(data.shape[0])*dt
855
+ if save:
856
+ save_data(df, f'{self.__class__.__name__}.csv')
857
+ if plot:
858
+ plot_data(df, max_time=t_max)
859
+
860
+
861
+
862
+ # ------------------- 4th-order Runge-Kutta solver -------------------
863
+
864
+ def rk4_solver(f, u, dt):
865
+ """
866
+ Rung-Kutta 4th order solver for a system of ODEs.
867
+
868
+ Args:
869
+ f (function): Function that describes the system of ODEs. For model classes it is the model method.
870
+ u (float): Value of a state variable at time t.
871
+ dt (float): Time step.
872
+
873
+ Returns:
874
+ u[i+1] (float): Value of a state variable at time t+dt.
875
+ """
876
+ k1 = f(u)
877
+ k2 = f(u + k1*dt/2)
878
+ k3 = f(u + k2*dt/2)
879
+ k4 = f(u + k3*dt)
880
+
881
+ return u + dt/6*(k1 + 2*k2 + 2*k3 + k4)
882
+
883
+ # ------------------- Generate and save data of multiple IC to csv -------------------
884
+
885
+ def simulate_data (simulate, x0, dt, N=10000, check_period=False):
886
+ """
887
+ Generate synthetic data for a given model and initial conditions.
888
+
889
+ Args:
890
+ simulate (method): Method that simulates the model. For model classes it is the simulate method.
891
+ x0 (array of floats): Initial conditions for the model.
892
+ dt (float): Time step.
893
+ N (int, optional): Number of timesteps. Defaults to 10000.
894
+ check_period (bool, optional): Limits the amount of periods calculated. Defaults to True.
895
+
896
+ Returns:
897
+ df (pandas DataFrame): DataFrame with the synthetic data.
898
+ """
899
+ df = pd.DataFrame()
900
+ sim_count = 1
901
+ time = np.arange(N)*dt
902
+ # print(x0,len(x0), N)
903
+ u = np.zeros((len(x0),N))
904
+ if len(x0)==2:
905
+ for i in range(x0[0,:,:].shape[0]):
906
+ for j in range(x0[0,:,:].shape[1]):
907
+
908
+ u[:,0] = [x0[0,i,j], x0[1,i,j]]
909
+
910
+
911
+ for n in range(1,N):
912
+ u[:,n] = simulate(u[:,n-1], dt)
913
+
914
+ df_sim = pd.DataFrame()
915
+
916
+ df_sim['sim'] = np.ones(u.shape[1])*sim_count
917
+ df_sim['time'] = time
918
+ var_name=['u', 'v', 'w', 'x', 'y']
919
+ for i_var in range(len(x0)):
920
+ df_sim[f'{var_name[i_var]}'] = u[i_var,:]
921
+
922
+
923
+
924
+ if check_period:
925
+ _,_,period,_=calculate_period(df_sim['u'].to_numpy(), df_sim['time'].to_numpy())
926
+ if period is not None:
927
+ max_time = 6 * period
928
+ df_sim = df_sim[df_sim['time'] <= max_time]
929
+
930
+ sim_count += 1
931
+
932
+ df = pd.concat((df, df_sim), ignore_index=True)
933
+ if len(x0)==3:
934
+ for i in range(x0[0,:,:,:].shape[0]):
935
+ for j in range(x0[0,:,:,:].shape[1]):
936
+ for k in range(x0[0,:,:,:].shape[2]):
937
+ u[:,0] = [x0[0,i,j,k], x0[1,i,j,k], x0[2,i,j,k]]
938
+
939
+ for n in range(1,N):
940
+ u[:,n] = simulate(u[:,n-1], dt)
941
+
942
+ df_sim = pd.DataFrame()
943
+
944
+ df_sim['sim'] = np.ones(u.shape[1])*sim_count
945
+ df_sim['time'] = time
946
+ var_name=['u', 'v', 'w']
947
+ for i_var in range(len(x0)):
948
+ df_sim[f'{var_name[i_var]}'] = u[i_var,:]
949
+
950
+ if check_period:
951
+ _,_,period,_=calculate_period(df_sim['u'].to_numpy(), df_sim['time'].to_numpy())
952
+ if period is not None:
953
+ max_time = 6 * period
954
+ df_sim = df_sim[df_sim['time'] <= max_time]
955
+
956
+ sim_count += 1
957
+
958
+ df = pd.concat((df, df_sim), ignore_index=True)
959
+
960
+ return df
961
+
962
+ def save_data(df, filename):
963
+ """
964
+ Save the generated data to a csv file.
965
+
966
+ Args:
967
+ df (pandas DataFrame): DataFrame with the synthetic data.
968
+ filename (str): Name of the file, usually taking the name of the model.
969
+ """
970
+ # Create 'data' directory if it does not exist
971
+ if not os.path.exists('data/synthetic_data'):
972
+ os.makedirs('data/synthetic_data')
973
+
974
+ # Save the DataFrame to the 'data' directory
975
+ filepath = os.path.join('data/synthetic_data', filename)
976
+
977
+ # Save the DataFrame to the 'data/synthetic_data' directory
978
+ df.to_csv(filepath, index=False)
979
+ print('Generated data saved to', filepath)
980
+
981
+
982
+ def plot_data(df, max_time):
983
+ """
984
+ Plot the generated data.
985
+
986
+ Args:
987
+ df (pandas DataFrame): DataFrame with the synthetic data.
988
+ max_time (float): Maximum time for the plot.
989
+ """
990
+ fig, ax = plt.subplots(1,1)
991
+
992
+ for i in df['sim'].unique():
993
+ df_sim = df[(df['sim']==i) & (df['time']<=max_time)].copy()
994
+ df_sim.plot.line(x='u', y='v', ax=ax, legend=False)
995
+
996
+ ax.set_ylabel('v')
997
+
998
+
999
+ from scipy.signal import find_peaks
1000
+
1001
+ #### General functions
1002
+ def calculate_period(x_train, t_train):
1003
+ """
1004
+ Calculate the period of an oscillation.
1005
+
1006
+ Parameters
1007
+ ----------
1008
+ x_train : np.array
1009
+ Time series of data.
1010
+ t_train : np.array
1011
+ Time of the time series.
1012
+
1013
+ Returns
1014
+ -------
1015
+ peaks_u : np.array
1016
+ Size of peaks detected in the time series.
1017
+ peaks_t : np.array
1018
+ Time points where peaks occur in the data set.
1019
+ period : float
1020
+ Period of Time Series.
1021
+ peaks[0]: list
1022
+ Array of all indicies of local maxima
1023
+
1024
+ """
1025
+ if len(x_train.shape)<2:
1026
+ peaks=find_peaks(x_train)
1027
+ peaks_t=t_train[peaks[0]]
1028
+ peaks_u=x_train[peaks[0]]
1029
+ elif x_train.shape[1]==2 or x_train.shape[1]==3:
1030
+ peaks=find_peaks(x_train[:,0])
1031
+ peaks_t=t_train[peaks[0]]
1032
+ peaks_u=x_train[peaks[0],0]
1033
+ elif x_train.shape[1]==5:
1034
+ peaks=find_peaks(x_train[:,1])
1035
+ peaks_t=t_train[peaks[0]]
1036
+ peaks_u=x_train[peaks[0],1]
1037
+
1038
+ period_temp=0
1039
+ for i in range(len(peaks_t)-1):
1040
+ period_temp=period_temp+(peaks_t[i+1]-peaks_t[i])
1041
+ if len(peaks_t)>1:
1042
+ subtract=1
1043
+ else: subtract=0
1044
+ period=period_temp/(len(peaks_t)-subtract)
1045
+ return peaks_u, peaks_t, period, peaks[0]