Simulace ODE v Pythonu

Dnes se podíváme na problematiku simulace obyčejné diferenciální rovnice ODE v pythonu. Budeme využívat modul scipy, který obsahuje mnoho algortimů pro numerickou matematiku. Pro řešení dynamického systémů (ten je popsán diferenciálními rovnicemi) použijeme numerickou metodu. Rozdíl mezi numerickým a analytickém řešením je ten, že analytické řešení je zcela přesné, ale lze takto vyřešit jen omezenou sadu úloh. Naopak numerika je velmi používaná v inženýrské praxi.

Jak vypadá taková ODE?

\large\frac{dx}{dt}= ax 

Tak například výše máme ODE prvního řádu. Na levé straně máme derivaci x podle času. Pro tuto jednoduchou ODE je známé analytické řešení. Řešením ODE je funkce:

\large x(t)= e^{at}x(0)

Take se můžeme setkat s termínem „řešení počátečního problému“. Můsíme tedy znát počáteční podmínku x(0).

My budeme řešit rovnice numerickou metodou. K tomu využijeme modul scipy a funkci solve_ipv. V prvé řadě musíme nadefinovat funkci, kterou bude numerická metoda volat a získávat tak řešení v každém výpačetním kroku. Dále interval na jakém budeme řešení chtít např. čas simulace. Bude potřeba zadata počáteční podmínky. Funkce solve_ipv také nabízí několik numerických metod. Pro začátek je nejvhodnější volit RK45. Jedná se o metodu s adaptivním krokem simulace. Velikost výpočetního kroku upravuje dle odhadu chyby řešení. Konkrétně se jedná o motedu z rodiny Runge–Kutta–Fehlberg, což je velmi dobrá explicitní metoda 4-5 řádu. Pro některé typy úloh však vhodná není. jedná se zejména o tzv. stiff problémy.

Vyřešíme jednoduchou ODE1 (prvního řádu)

τ = 0.1
k = 10
u <0-0.1> = 0 ; <0.1-1> = 1

Vstupem tedy bude jednotkový skok v čase 0,1 s.

Příklad řešení vykreslen v matplotlib:

Dále vyřešíme rovnici, která je asi nejpoužívanější ve školním prostředí. Jedná se o hmotu s pružinou a tlumičem. Tento systém má i poměrně dobré praktické využití např. nejednodušší model zavěšení náprav automobilu.

\large \ddot{x}m+\dot{x}b+ kx = F(t) 

Setrvačná, tlumící a potencionální (síla pružiny) je v tomto případě rovna budící síle. Tentokrát jsme použili tzv. Newtonův zápis. Jedna tečka nad x znaméná první derivaci podle času atd.

Při prvním pohledu zjistíme, že se jedná o ODE druhého řádu, což je typické pro mechanické systémy. Numerické řešiče řeší soustavy ODE prvního řádu a je tedy nutné naši rovnici upravit. Tato úprava je možná vždy a tedy rovnice vyšších řádů můžeme převést na soustavu rovnic řádu prvního.

Upravená ovnice vhodná pro simulaci bude vypadat takto:

\large \dot{x}=v \\ \dot{v} = \frac{1}{m}(F(t) - vb - xk)

Takto upravená rovnice je už vhodná k simulaci. Jedná se o soustavu ODE, kde máme první derivace izolované na jední straně rovnic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt


def ode1(t, x):
    tau = 0.1
    k = 10

    if t < 0.1:
        u = 0
    else:
        u = 1

    dx = (-x + k * u) / tau

    return dx

def ode2(t, x):

    F = 100
    k = 10
    b = 3
    m = 1

    dx1 = x[1]
    dx2 = (F - k*x[0] - b*x[1])/m

    dx = [dx1, dx2]

    return dx

def ode3(t, x):

    F = 1
    k = 10
    b = 0
    m = 1

    omg = np.sqrt(k/m)

    dx1 = x[1]
    dx2 = (F*np.sin(omg*t) - k*x[0] - b*x[1])/m

    dx = [dx1, dx2]

    return dx

def ode4(t, x):

    k = 1000
    b = 1001
    m = 1

    dx1 = x[1]
    dx2 = (- k*x[0] - b*x[1])/m

    dx = [dx1, dx2]

    return dx

def ode_sim():

    xt2 = integrate.solve_ivp(fun=ode2, t_span=[0.0, 10.0],
                             y0=[0.0, 0.0], max_step=0.01,
                             method="RK45")

    xt1 = integrate.solve_ivp(fun=ode1, t_span=[0.0, 1.0],
                             y0=[0.0, 0.0], max_step=0.01,
                             method="RK45")

    xt3 = integrate.solve_ivp(fun=ode3, t_span=[0.0, 10.0],
                             y0=[0.0, 0.0], max_step=0.01,
                             method="RK45")

    xt4 = integrate.solve_ivp(fun=ode4, t_span=[0.0, 10.0],
                             y0=[1.0, 0.0], max_step=0.1,
                             method="BDF")

    plt.figure(1)
    plt.plot(xt2.t, xt2.y[0])
    plt.title("Spring-mass-dumper")
    plt.xlabel("time [t]")
    plt.ylabel("x [m]")
    plt.grid()

    plt.figure(2)
    plt.plot(xt1.t, xt1.y[0])
    plt.title("ode 1")
    plt.xlabel("time [t]")
    plt.ylabel("x [m]")
    plt.grid()

    plt.figure(3)
    plt.plot(xt3.t, xt3.y[0])
    plt.title("Spring-mass-resonance")
    plt.xlabel("time [t]")
    plt.ylabel("x [m]")
    plt.grid()

    plt.figure(4)
    plt.plot(xt4.t, xt4.y[0])
    plt.title("Spring-mass-dumper-stiff")
    plt.xlabel("time [t]")
    plt.ylabel("x [m]")
    plt.grid()

    plt.show()
    print(len(xt4.t))


if __name__ == '__main__':
    ode_sim()