import numpy as np
from ipywidgets import interact
import matplotlib.pyplot as plt
%matplotlib inline
import sympy as sym
sym.init_printing()Uvod¶
Zapis (ene) diferencialne enačbe¶
Predpostavimo, da je mogoče diferencialno enačbo prvega reda zapisati v eksplicitni obliki:
kjer je podana funkcija in velja .
Dodatno je podan začetni pogoj:
Cilj reševanja diferencialne enačbe je izračunati funkcijo , ki reši zgoraj definiran začetni problem. Ob določenih pogojih funkcije ima začetni problem enolično rešitev na intervalu, ki vsebuje .
Pri numeričnem reševanju vedno računamo tabelo funkcije , ki reši dan začetni problem. Pri tem so vozlišča običajno ekvidistantna:
in imenujemo (časovni) korak (integracije).
Tukaj si bomo pogledali nekatere numerične metode za reševanje diferencialnih enačb pri začetnem pogoju.
Eulerjeva metoda¶
Eksplicitna Eulerjeva metoda temelji na razvoju funkcije v Taylorjevo vrsto:
Naredimo napako metode , ker zanemarimo odvode drugega in višjih redov; sedaj lahko ob znani vrednosti in odvodu ocenimo vrednosti pri naslednjem časovnem koraku . Ko imamo enkrat znane vrednosti pri , ponovimo postopek!
Koraki Eulerjeve metode:
Postavimo , , .
Izračun vrednosti funkcije pri :
in nadaljevanje v koraku 2.
Diferencialno enačbo rešujemo na intervalu in velja . je število integracijskih korakov (kolikokrat izvedemo korak 2 v zgornjem algoritmu).
Numerična rešitev začetnega problema:
pri vrednostih neodvisne spremenljivke:
from IPython.display import YouTubeVideo
YouTubeVideo('cfZ8v0b-R8o', width=800, height=300)Napaka Eulerjeve metode¶
Napaka Eulerjeve metode na vsakem koraku je reda .
Ker na intervalu od do tako napako naredimo -krat, je kumulativna napaka .
Lokalno je napaka drugega reda, globalno pa je napaka prvega reda in ker je Eulerjeva metoda tako nenatančna jo redko uporabljamo v praksi!
Ocena napake¶
Točna rešitev pri velikosti koraka je:
kjer je numerični približek in napaka metode. Ker je globalna napaka prvega reda, lahko napako zapišemo kot:
Ob predpostavki, da je konstanta pri koraku in koraku enaka, lahko določimo oceno napake pri boljšem približku . Očitno velja:
nato določimo oceno napake:
Komentar na implicitno Eulerjevo metodo¶
Pri eksplicitni Eulerjevi metodi računamo rešitev pri iz izračunane vrednosti pri .
V kolikor bi nastopala neznana vrednost rešitve pri , to je , tudi na desni strani, bi govorili o implicitni Eulerjevi metodi (ali povratni Eulerjevi metodi):
Ker se iskana vrednost nahaja na obeh straneh enačbe, moramo za določitev rešiti (nelinearno) enačbo. Prednost implicitne Eulerjeve metode je, da je bolj stabilna (npr. v primeru togih sistemov, ki jih bomo spoznali pozneje) kakor eksplicitna oblika, vendar pa je numerično bolj zahtevna (zaradi računanja rešitve enačbe).
Numerična implementacija¶
Najprej uvozimo potrebne knjižnice:
import numpy as np
import matplotlib.pylab as plt
%matplotlib inlineNato definirajmo Eulerjevo metodo:
def euler(f, t, y0, *args, **kwargs):
"""
Eulerjeva metoda za reševanje sistema diferencialnih enačb: y' = f(t, y)
:param f: funkcija, ki vrne prvi odvod - f(t, y)
:param t: časovni vektor kjer računamo rešitev
:param y0: začetna vrednosti
:param args: dodatni argumenti funkcije f (brezimenski)
:param kwargs: dodatni argumenti funkcije f (poimenovani)
:return y: vrne np.array ``y`` vrednosti funkcije.
"""
y = np.zeros_like(t)
y[0] = y0
h = t[1]-t[0]
for i in range(len(t)-1):
y[i+1] = y[i] + f(t[i], y[i], *args, **kwargs) * h
return yPripravimo funkcijo za oceno napake (v numeričnem smislu bi bilo bolje oceno napake vključiti v funkcijo euler, vendar jo zaradi jasnosti predstavimo ločeno):
def euler_napaka(f, t, y0, *args, **kwargs):
""" Ocena napake Eulerjeve metode; argumenti so isti kakor za funkcijo `euler`
"""
n = len(t)
if n < 5:
raise Exception('Vozlišč mora biti vsaj 5.')
if n%2==0: # sodo vozlišč; odstrani eno točko in spremeni na liho (da je sodo odsekov)
n = n - 1
y_h = euler(f, t[:n], y0, *args, **kwargs)
y_2h = euler(f, t[:n:2], y0, *args, **kwargs)
E_h = y_h[-1] - y_2h[-1]
return E_hNumerični zgled¶
Kot primer rešimo diferencialno enačbo, ki opisuje padanje telesa, ki je izpostavljeno sili teže in zračnemu uporu:

Glede na II. Newtonov zakon, lahko zapišemo diferencialno enačbo:
kjer je masa, gravitacijski pospešek, koeficient zračnega upora in hitrost. Diferencialno enačbo bi hoteli rešiti glede na začetni pogoj:
Definirajmo funkcijo desnih strani:
def f_zračni_upor(t, y, g=9.81, m=1., c=0.5):
return g-c*y/mDefinirajmo začetni pogoj in časovni vektor, kjer nas zanima rezultat:
v0 = 0
t = np.linspace(0, 10, 11)
tarray([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])Kličemo funkcijo euler za izračun vrednosti (hitrost ):
y = euler(f_zračni_upor, t, y0=v0)
yarray([ 0. , 9.81 , 14.715 , 17.1675 , 18.39375 ,
19.006875 , 19.3134375 , 19.46671875, 19.54335938, 19.58167969,
19.60083984])Prikažemo rezultat:
plt.plot(t, y)
plt.title('Hitrost mase v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.show()
Preverimo sedaj vpliv časovnega koraka:
for n in [11, 101, 1001]:
t = np.linspace(0, 10, n)
y = euler(f_zračni_upor, t, y0=v0, c=0.7)
plt.plot(t, y, label=f'Časovni korak: {t[1]:1.0e}')
plt.title('Hitrost mase v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.legend()
plt.show()
Opazimo, da se numerična napaka pri spremembi koraka iz 1 na 0,1 bistveno zmanjša!
Ocenimo še napako pri 100 in 1000 odsekih:
n=101
t = np.linspace(0, 10, n)
euler_napaka(f_zračni_upor, t, y0=v0)n=1001
t = np.linspace(0, 10, n)
euler_napaka(f_zračni_upor, t, y0=v0)Ko smo korak zmanjšali na desetino, se je proporcionalno zmanjšala tudi napaka (prvi red napake).
Poglejmo še primer, ko je zračni upor c argument funkcije euler in je prek **kwargs posredovan v funkcijo f_zračni_upor():
for c in np.linspace(0, 1, 5):
t = np.linspace(0, 5, 1001)
y = euler(f_zračni_upor, t, y0=v0, c=c)
plt.plot(t, y, label=f'$c={c}$')
plt.title('Hitrost mase v odvisnosti od časa pri različnem koef. zračnega upora')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.legend()
plt.show()
Metoda Runge-Kutta drugega reda¶
Eulerjeva metoda je prvega reda (prvega reda je namreč globalna napaka ). Če bi želeli izpeljati metodo drugega reda napake, bi si morali pomagati z razvojem v Taylorjevo vrsto, kjer bomo zanemarili tretji in višje odvode:
Lokalna napaka metode bo tako tretjega reda, globalna pa drugega reda.
Uporabimo zamenjavi in :
Ker je desna stran odvisna od neodvisne in odvisne spremenljivke , moramo uporabiti implicitno odvajanje:
Vstavimo v izraz za Taylorjevo vrsto:
Kot je razvidno iz zgornjega izraza, potrebujemo dodatne odvode. To predstavlja določeno težavo, ki se ji lahko izognemo na različne načine; v nadaljevanju si bomo pogledali pristop Runge-Kutta. Ker bomo zgornji izraz pozneje še potrebovali, smo ga tukaj poimenovali .
Ideja pristopa Runge-Kutta¶
Zgornjo dilemo metoda Runge-Kutta (razvita leta 1901) rešuje z idejo, ki smo jo sicer že srečali pri Gaussovi integraciji: točnejšo rešitev poskuša najti z uteženo dodatno vrednostjo funkcije :
kjer so , , in neznane konstante (načeloma od 0 do vključno 1). Če bi v zgornjem izrazu uporabili , bi izpeljali metodo prvega reda; z dodatno funkcijsko vrednostjo () pa se bo izkazalo, da bomo izpeljali metodo drugega reda.
Iskanje neznanih konstant , , , nadaljujemo z zapisom v obliki Taylorjeve vrste prvega reda:
Vstavimo sedaj izpeljani nazaj v izraz za :
Nadaljujemo z izpeljevanjem in enačbo preoblikujemo, da bo podobna zgoraj izpeljani s Taylorjevo vrsto :
Primerjajmo sedaj z zgoraj izpeljanim izrazom:
Ugotovimo, da za enakost mora veljati:
Imamo torej tri enačbe in štiri neznanke. Eno od konstant si tako lahko poljubno izberemo, ostale tri pa izračunamo. Če na primer izberemo , bi to imenovali spremenjena Eulerjeva metoda in bi ostali parametri bili: , . Izbira parametrov nima bistvenega vpliva na rešitev. Sicer pa velja omeniti, da tudi metodo Runge-Kutta drugega reda redko uporabljamo, saj obstajajo boljše metode.
Parametre , , in vstavimo v prvo enačbo tega poglavja. Ko je definiran začetni čas in začetni pogoj , uporabimo metodo Runge-Kutta drugega reda:
Metoda Runge-Kutta četrtega reda¶
Podobno kot smo izpeljali metodo Runge-Kutta drugega reda, se izpelje metodo Runge-Kutta četrtega reda. Tudi pri metodi četrtega reda obstaja več različic in kot metoda Runge-Kutta četrtega reda razumemo naslednjo metodo:
Koraki metode Runge-Kutta četrtega reda so:
Določitev in , ,
Izračun koeficientov: , , , ,
Izračun vrednosti rešitve diferencialne enačbe pri :
in nadaljevanje v koraku 2.
Napaka metode Runge-Kutta četrtega reda¶
Metodo Runge-Kutta četrtega reda imenujemo tako zato, ker ima lokalno napako petega reda , vendar pa to napako naredimo -krat, zato je globalna napaka četrtega reda .
Ocena napake¶
Točen rezultat pri velikosti koraka je:
kjer je numerični približek rešitve in napaka metode. Ker je globalna napaka četrtega reda, lahko napako zapišemo tako:
Podobno lahko za velikost koraka zapišemo:
kjer je numerični približek rešitve in napaka metode:
Ob predpostavki, da je konstanta pri koraku in koraku enaka, lahko izračunamo oceno napake pri boljšem približku .
Najprej je res:
Numerična implementacija¶
def runge_kutta_4(f, t, y0, *args, **kwargs):
"""
Metoda Runge-Kutta 4. reda za reševanje diferencialne enačbe: y' = f(t, y)
:param f: funkcija, ki jo kličemo s parametroma t in y in vrne
vrednost prvega odvoda
:param t: ekvidistanten časovni vektor oz. neodvisna spremenljivka
:param y0: začetna vrednost
:param args: dodatni argumenti funkcije f (brezimenski)
:param kwargs: dodatni argumenti funkcije f (poimenovani)
:return y: funkcijske vrednosti.
"""
def RK4(f, t, y, *args, **kwargs):
k0 = h*f(t, y, *args, **kwargs)
k1 = h*f(t + h/2.0, y + k0/2.0, *args, **kwargs)
k2 = h*f(t + h/2.0, y + k1/2.0, *args, **kwargs)
k3 = h*f(t + h, y + k2, *args, **kwargs)
return (k0 + 2.0*k1 + 2.0*k2 + k3)/6.0
y = np.zeros_like(t)
y[0] = y0
h = t[1]-t[0]
for i, ti in enumerate(t[:-1]):
y[i+1] = y[i] + RK4(f, ti, y[i], *args, **kwargs)
return yFunkcija za oceno napake:
def runge_kutta_4_napaka(f, t, y0, *args, **kwargs):
""" Ocena napake metode Runge Kutta 4; argumenti isti kakor za `runge_kutta_4`
"""
n = len(t)
if n < 5:
raise Exception('Vozlišč mora biti vsaj 5.')
if n%2==0: # sodo vozlišč; odstrani eno točko in spremeni na liho (da je sodo odsekov)
n = n - 1
y_h = runge_kutta_4(f, t[:n], y0, *args, **kwargs)
y_2h = runge_kutta_4(f, t[:n:2], y0, *args, **kwargs)
E_h = (y_h[-1] - y_2h[-1])/15
return E_hNumerični zgled¶
Poglejmo sedaj primer izračuna hitrosti padajoče mase:
def f_zračni_upor(t, y, g=9.81, m=1., c=0.5):
return g-c*y/mPodajmo začetni pogoj in časovni vektor, kjer nas zanima rezultat:
v0 = 0
t = np.linspace(0, 10, 11)
tarray([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])Za primerjavo izračunajmo rešitev s funkcijo euler ter runge_kutta_4:
y_euler = euler(f_zračni_upor, t, y0=v0, c=0.4)
y_rk4 = runge_kutta_4(f_zračni_upor, t, y0=v0, c=0.4)Prikažemo rezultat:
plt.plot(t, y_euler, label='Euler')
plt.plot(t, y_rk4, label='RK4')
plt.title('Hitrost mase v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.legend()
plt.show()
Poglejmo še numerično napako:
n=101
t = np.linspace(0, 10, n)
runge_kutta_4_napaka(f_zračni_upor, t, y0=v0, c=0.4)n=1001
t = np.linspace(0, 10, n)
runge_kutta_4_napaka(f_zračni_upor, t, y0=v0, c=0.4)Pri zmanjšanju koraka na desetino, se je napaka zmanjšala za približno 104-krat (kar ustreza pričakovanjem za metodo četrtega reda).
Uporaba scipy za reševanje navadnih diferencialnih enačb¶
Paket scipy ima implementiranih veliko numeričnih metod za reševanje začetnih problemov navadnih diferencialnih enačb. Tukaj si bomo ogledali funkcijo scipy.integrate.solve_ivp, ki je primerna za večino začetnih problemov.
scipy.integrate.solve_ivp¶
Sintaksa za uporabo (IVP - angl. Initial Value Problem):
scipy.integrate.solve_ivp(fun, t_span, y0, method='RK45',
t_eval=None, dense_output=False,
events=None, vectorized=False,
args=None, **options)Pojasnilo vseh argumentov je v dokumentaciji, tukaj bomo izpostavili nekatere:
funje desna stran (func(t, y...)),t_spanterka (t0,tf), ki definira začetnit0in končni častf,y0seznam začetne(ih) vrednosti,methoddefinira numerično metodo (privzeta je eksplicitna ‘RK45’, za toge sisteme pa so boljše: ‘Radau’, ‘BDF’, ‘LSODA’),t_evaluporabimo, če želimo rešitve ob določenih vrednosti neodvisne spremenljivke,dense_outputali se pripravi tudi zvezna rešitev, privzetoFalse,eventsza sledenje dogodkov, ki ustavijo reševanje začetnega problema (npr. ko se masa dotakne tal in je relativna razdalja do tal nič).argsza posredovanje argumentov v funkcijofun(glejte primer spodaj).
Rezultat klicanja solve_ivp je objekt z atributi (izbrani):
tvrednosti neodvisne spremenljivke pri katerih je izračunan rezultat,yrezultat,solfunkcija zvezne rešitve (samo v primerudense_output=True)successjeTrue, če je bila rešitev uspešno izračunana ali prekinjena zaradi dogodka (events).
Numerični zgled¶
Definirajmo začetni pogoj in časovni vektor, kjer nas zanima rezultat (prikažemo samo prvih deset elementov):
v0 = 0
t = np.linspace(0, 10, 101)
t[:10]array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])Argumente (g=9.81, m=1., c=0.4) v funkcijo posredujemo prek parametra args:
from scipy.integrate import solve_ivp
y_ivp = solve_ivp(f_zračni_upor, t_span=(t[0], t[-1]), y0=[v0], args=(9.81, 1, 0.4))Lahko bi pa uporabili tudi lambda izraza (dokumentacija):
y_ivp = solve_ivp(lambda t,y: f_zračni_upor(t, y, c=0.4), t_span=(t[0], t[-1]), y0=[v0])Zgoraj uporabljen lambda izraz lambda t, y: f_zračni_upor(t, y, c=0.4) je ekvivalenten:
def ime_funkcije(t, y):
return f_zračni_upor(t, y, c=0.4)Poglejmo rezultat:
y_ivp message: The solver successfully reached the end of the integration interval.
success: True
status: 0
t: [ 0.000e+00 1.000e-04 1.100e-03 1.110e-02 1.111e-01
1.111e+00 2.982e+00 5.236e+00 7.948e+00 1.000e+01]
y: [[ 0.000e+00 9.810e-04 1.079e-02 1.086e-01 1.066e+00
8.800e+00 1.708e+01 2.150e+01 2.350e+01 2.407e+01]]
sol: None
t_events: None
y_events: None
nfev: 56
njev: 0
nlu: 0Ker je solve_ivp že pripravljen za sistem navadnih diferencialnih enačb prvega reda, je rezultat podan v dveh dimenzijah. Iskan rezultat je torej: y_ivp.y[0].
Za primerjavo še rezultat z lastno implementacijo Runge-Kutta 4. reda:
y_rk4 = runge_kutta_4(f_zračni_upor, t, y0=v0, c=0.4)y_rk4[:10]array([0. , 0.96163898, 1.88557156, 2.77327623, 3.6261735 ,
4.44562819, 5.23295161, 5.98940363, 6.71619474, 7.41448797])Prikažemo rezultat:
plt.plot(y_ivp.t, y_ivp.y[0], '.', label='solve_ivp')
plt.plot(t, y_rk4, label='RK4')
plt.title('Hitrost mase v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.legend()
plt.show()
Sistem navadnih diferencialnih enačb¶
Zgoraj smo si pogledali reševanje začetnega problema ene navadne diferencialne enačbe; sedaj bomo reševanje posplošili na začetni problem za sistem navadnih diferencialnih enačb prvega reda.
Takšen sistem zapišemo:
kjer so podani dodatni (začetni) pogoji:
S smo označili neodvisno spremenljivko (ni nujno, da je to vedno čas) in vektor desnih strani.
Računamo rešitev sistema diferencialnih enačb pri , to je funkcijskih vrednosti na vsakem koraku , in . Lahko se dokaže, da lahko uporabimo vsako metodo, ki smo jo izpeljali za diferencialne enačbe prvega reda tudi za sistem navadnih diferencialnih enačb prvega reda, če zamenjamo skalarne veličine z ustreznimi vektorskimi.
Numerična implementacija¶
Metodi Euler in Runge-Kutta četrtega reda potrebujeta zgolj malenkostne popravke (y0 je numerično polje, f vrne seznam vrednosti odvodov):
def euler_sistem(f, t, y0, *args, **kwargs):
"""
Eulerjeva metoda za reševanje sistema navadnih diferencialnih enačb prvega reda : y' = f(t, y)
:param f: funkcija, ki jo kličemo s parametroma t in y in vrne seznam
funkcij desnih strani
:param t: ekvidistantni (časovni) vektor neodvisne spremenljivke
:param y0: seznam začetnih vrednosti
:param args: dodatni argumenti funkcije f (brezimenski)
:param kwargs: dodatni argumenti funkcije f (poimenovani)
:return y: vrne np.array ``y`` funkcijskih vrednosti.
"""
y = np.zeros((t.shape[0], len(y0)))
y[0] = np.copy(y0)
h = t[1]-t[0]
for i, ti in enumerate(t[:-1]):
# tukaj je bistvo Eulerjeve metode
y[i+1] = y[i]+f(ti, y[i], *args, **kwargs)*h
return ydef runge_kutta_4_sistem(f, t, y0, *args, **kwargs):
"""
Metoda Runge-Kutta 4. reda za reševanje sistema navadnih diferencialnih enačb prvega reda: y' = f(t, y)
:param f: funkcija, ki jo kličemo s parametroma t in y in vrne seznam
funkcij desnih strani
:param t: ekvidistantni (časovni) vektor neodvisne spremenljivke
:param y0: seznam začetnih vrednosti
:param args: dodatni argumenti funkcije f (brezimenski)
:param kwargs: dodatni argumenti funkcije f (poimenovani)
:return y: vrne np.array ``y`` funkcijskih vrednosti.
"""
def RK4(f, t, y, *args, **kwargs):
k0 = h*f(t, y, *args, **kwargs)
k1 = h*f(t + h/2.0, y + k0/2.0, *args, **kwargs)
k2 = h*f(t + h/2.0, y + k1/2.0, *args, **kwargs)
k3 = h*f(t + h, y + k2, *args, **kwargs)
return (k0 + 2.0*k1 + 2.0*k2 + k3)/6.0
y = np.zeros((t.shape[0], len(y0)))
y[0] = np.copy(y0)
h = t[1]-t[0]
for i, ti in enumerate(t[:-1]):
y[i+1] = y[i] + RK4(f, ti, y[i], *args, **kwargs)
return yNumerični zgled¶
Padanje mase nadgradimo v ravninsko gibanje; velikost sile upora zraka naj bo definirana kot:
Sila upora zraka deluje v nasprotno stran kot kaže vektor hitrosti.

Glede na drugi Newtonov zakon zapišemo sistem dveh (vezanih) diferencialnih enačb prvega reda:
je masa, gravitacijski pospešek, koeficient zračnega upora ter in hitrost v oz smeri. Diferencialno enačbo bi želeli rešiti glede na začetni pogoj:
Definirajmo seznam desnih strani:
def f_zračni_upor_sila(t, y, g=9.81, m=1., c=0.5):
vx, vy = y
return np.array([-c*vx*np.sqrt(vx**2+vy**2)/m, g-c*vy*np.sqrt(vx**2+vy**2)/m])Definirajmo začetni pogoj in časovni vektor, kjer nas zanima rezultat:
y0 = np.array([5., 5.])
t = np.linspace(0, 5, 101)
t[:5]array([0. , 0.05, 0.1 , 0.15, 0.2 ])Za primerjavo izračunajmo rešitev s funkcijo runge_kutta_4 ter solve_ivp:
y_RK4 = runge_kutta_4_sistem(f_zračni_upor_sila, t=t, y0=y0)
y_ivp = solve_ivp(f_zračni_upor_sila, t_span=(t[0], t[-1]), y0=y0, t_eval=t)Poglejmo rezultat:
y_ivp.y[:,:5]array([[5. , 4.23209907, 3.63858619, 3.16619484, 2.78087608],
[5. , 4.68459713, 4.48359747, 4.35932897, 4.28742151]])y_RK4[:5].Tarray([[5. , 4.23211316, 3.6399137 , 3.16660748, 2.77780226],
[5. , 4.68462761, 4.48494405, 4.35999931, 4.28466858]])Prikažemo rezultat:
plt.plot(t, y_ivp.y[0], label='$v_x$')
plt.plot(t, y_ivp.y[1], label='$v_y$')
plt.title('Hitrost mase (upor in sila) v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost $v$ [m/s]')
plt.legend()
plt.show()
Preoblikovanje diferencialne enačbe višjega reda v sistem diferencialnih enačb prvega reda¶
Pogledali si bomo, kako navadno diferencialno enačbo poljubnega reda :
pri začetnih pogojih:
preoblikujemo v sistem diferencialnih enačb prvega reda.
Najprej namesto odvodov vpeljemo nove spremenljivke:
Diferencialna enačba -tega reda zapisana z novimi spremenljivkami je:
Numerični zgled¶
Vrnemo se k padajoči masi:

Vendar tokrat drugi Newtonov zakon zapišimo glede na pomik (diferencialna enačba drugega reda):
je masa, gravitacijski pospešek, koeficient zračnega upora ter in pospešek v izbranem koordinatnem sistemu. Začetni pogoji:
Imamo sistem dveh diferencialnih enačb drugega reda. Z uvedbo novih spremenljivk :
Pripravimo sistem diferencialnih enačb prvega reda:
Definirajmo Pythonovo funkcijo desnih strani / prvih odvodov:
def f_zračni_upor_vezana(t, y, g=9.81, m=1., c=0.5):
x, vx, y, vy = y
return np.array([vx, -c*vx*np.sqrt(vx**2+vy**2)/m, vy, g-c*vy*np.sqrt(vx**2+vy**2)/m])Definirajmo začetni pogoj in časovni vektor, kjer nas zanima rezultat:
y0 = np.array([0., 5., 0., 5.])
t = np.linspace(0, 5, 101)
t[:5]array([0. , 0.05, 0.1 , 0.15, 0.2 ])Izračunamo rešitev:
y_ivp = solve_ivp(f_zračni_upor_vezana, t_span=(t[0], t[-1]), y0=y0, t_eval=t)Poglejmo rezultat ():
y_ivp.y[:,:5]array([[0. , 0.22992477, 0.42611996, 0.59587483, 0.74424286],
[5. , 4.23183658, 3.64002431, 3.16703014, 2.77775353],
[0. , 0.24153615, 0.47038377, 0.69124852, 0.9072755 ],
[5. , 4.68435696, 4.48497294, 4.36034218, 4.28444004]])Prikažemo rezultate; najprej hitrost, nato lego ( koordinata je pozitivna navzdol)!
plt.plot(t, y_ivp.y[1], label='$v_x$')
plt.plot(t, y_ivp.y[3], label='$v_y$')
plt.title('Hitrost mase v odvisnosti od časa')
plt.xlabel('Čas $t$ [s]')
plt.ylabel('Hitrost [m/s]')
plt.legend()
plt.show()
plt.scatter(y_ivp.y[0], y_ivp.y[2], marker='.')
plt.xlabel('$x$ [m]')
plt.ylabel('$y$ [m]')
plt.title('Lega mase')
plt.show()
Stabilnost reševanja diferencialnih enačb*¶
Numerično reševanje diferencialnih enačb je izpostavljeno napaki metode in zaokrožitveni napaki. Te napake so pri različnih numeričnih metodah različne.
Reševanje diferencialne enačbe je stabilno, če majhna sprememba začetnega pogoja vodi v majhno spremembo izračunane rešitve; sicer govorimo o nestabilnosti reševanja.
Stabilnost je odvisna od diferencialne enačbe, od uporabljene numerične metode in od koraka integracije .
Primer preprostega nihala¶
Poglejmo si najprej primer reševanja diferencialne enačbe preprostega nihala.

Slika (vir: Slavič, Dinamika, mehanska nihanja in mehanika tekočin, 2017) prikazuje dinamski sistem (masa , togost ), katerega diferencialna enačba je
Tako diferencialno enačbo preoblikujemo v standardno obliko lastnega nihanja:
kjer je lastna krožna frekvenca:
in pričakujemo odziv oblike:
Numerični zgled¶
Najprej definirajmo vektor začetnih pogojev in funkcijo desnih strani / prvih odvodov (diferencialno enačbo drugega reda pretvorimo v sistem diferencialnih enačb prvega reda ), kjer velja :
def f_nihalo(t, y, omega0=2*np.pi):
"""
Funkcija desnih strani za nihalo z eno prostostno stopnjo
:param t: čas
:param y: seznam začetnih vrednosti
:param omega: lastna krožna frekvenca
:return y': seznam vrednosti odvodov
"""
return np.array([y[1], -omega0**2*y[0]])Definirajmo podatke in analitično rešitev:
x0 = 1.
omega0 = 2*np.pi
x_zacetni_pogoji = np.array([x0, 0.])
t1 = 4.
cas = np.linspace(0, t1, 500)
pomik = x0*np.cos(omega0*cas) # analitična rešitev
hitrost = -x0*omega0*np.sin(omega0*cas) # analitična rešitevRešitev s pomočjo metod Euler in Runge-Kutta četrtega reda:
t_Eu = np.linspace(0, t1, 101)
t_RK4 = t_Eu
dt = t_Eu[1]
x_Eu = euler_sistem(f_nihalo, t_Eu, x_zacetni_pogoji)
x_RK4 = solve_ivp(f_nihalo, t_span=(t_RK4[0], t_RK4[-1]), y0=x_zacetni_pogoji, t_eval=t_RK4).yPrikažimo rezultate:
plt.plot(cas, pomik, label='Analitična rešitev')
plt.plot(t_Eu, x_Eu[:,0], '.', label='Euler')
plt.plot(t_RK4, x_RK4[0], '.', label='Runge-Kutta 4')
plt.legend()
plt.title('Stabilnost različnih metod')
plt.ylabel('Pomik [m]')
plt.xlabel('Čas [s]')
plt.show()
Opazimo, da je Eulerjeva metoda nestabilna in če bi povečali korak, bi postala nestabilna tudi metoda Runge-Kutta četrtega reda.
Zakaj je Eulerjeva metoda tako nestabilna?¶
x_Eu[:10,0]array([ 1. , 1. , 0.93683453, 0.8105036 , 0.62499707,
0.3882947 , 0.1121141 , -0.18859332, -0.49638247, -0.79225904])Spomnimo se Eulerjeve metode:
ki nam pove, da pomik določimo glede na lego in hitrost . Začetni pogoji izhajajo iz skrajnega odmika in takrat je hitrost , kar pomeni, da bo . Že v prvem koraku torej naredimo razmeroma veliko napako. Vendar zakaj potem začne vrednost alternirajoče naraščati?
Spomnimo se, da je analitična rešitev in je torej .
Vstavimo pripravljena izraza v Eulerjevo metodo in uredimo:
Predpostavimo, da gledamo stanje ob takem času , ko velja in :
V kolikor bo absolutna vrednost izraza večja kot 1, bo pri času vrednost večja kot v predhodnem koraku in v sledečem verjetno spet. Sledi, da lahko pride do nestabilnosti. Da se je izognemo, mora veljati:
Opomba: v nekaterih knjigah boste videli tudi vrednost ; enolične meje za vse diferencialne enačbe ni mogoče definirati; v splošnem pa velja, da je korak definiran relativno glede na najkrajšo periodo v diferencialni enačbi (npr.: je v bistvu enako oziroma ). Perioda je definirana glede na najvišjo lastno frekvenco sistema , ki jo izračunamo iz lastne vrednosti sistema.
Dodatno: Primer Van der Polovega nihala¶
Namen tega primera je pokazati, kako lahko izbira integratorja vpliva na hitrost reševanja problema! Van der Polovo nihalo je opisano tukaj.
Definirajmo seznam odvodov:
def f_van_der_pol(t, y, mu=1000):
"""
Funkcija desnih strani za Van der Pol nihalo
:param t: čas
:param y: seznam začetnih vrednosti
:param mu: parameter dušenja in nelinearnosti
:return y': seznam vrednosti odvodov
"""
return np.array([y[1], mu*(1-y[0]**2)*y[1]-y[0]])x_zacetni_pogoji = np.array([1.5, 0.])
dt = 0.1
t1 = 3000Rešitev po metodi RK45 (gre za eksplicitno shemo, ki ni primerna za toge sisteme diferencialnih enačb; reševanje je zelo počasno, zato rešitev računamo samo do t1/100):
vp_RK45 = solve_ivp(f_van_der_pol, t_span=(0., t1/100), y0=x_zacetni_pogoji, method='RK45')Implicitna shema BDF (angl. Backward Differentiation Formulas) se tukaj izkaže kot bistveno bolj primerna. Zaradi stabilnosti, so koraki lahko bistveno večji in zato je reševanje bistveno hitrejše:
vp_BDF = solve_ivp(f_van_der_pol, t_span=(0., t1), y0=x_zacetni_pogoji, method='BDF')plt.plot(vp_BDF.t, vp_BDF.y[0], 'C1.', label='Pomik - BDF [m]')
plt.plot(vp_RK45.t, vp_RK45.y[0], 'C0.', label='Pomik - RK45 [m]')
plt.xlabel('Čas [s]')
plt.legend(loc=(1.01, 0));
Dodatno: simbolno reševanje diferencialne enačbe drugega reda¶
Pogledali si bomo primer, prikazan na sliki, kjer je masa na klancu naklona . Koeficient trenja je , težnostni pospešek pa . Začetna hitrost je , pomik .
Gibalna enačba (samo za smer ) je definirana glede na II. Newtonov zakon (glejte diagram sil na prosto telo).
Izpeljava gibalne enačbe¶
y = sym.Function('y')
m, mu, g, alpha, t, v0 = sym.symbols('m, mu, g, alpha, t, v0')
eq = sym.Eq(m*y(t).diff(t,2), m*g*sym.sin(alpha)-m*g*sym.cos(alpha)*mu)
eqRešitev enačbe je:
dsol = sym.dsolve(eq, y(t))
dsolDa določimo in , vstavimo :
dsol.args[1].subs(t, 0)Nato odvajamo po času in ponovno vstavimo :
dsol.args[1].diff(t).subs(t, 0)Glede na začetne pogoje smo torej določili konstante:
zacetni_pogoji = {'C1': 0, 'C2': v0}Sledi rešitev:
resitev = dsol.args[1].subs(zacetni_pogoji)
resitevPripravimo si funkciji za numerični klic:
podatki = {mu: 0.3, alpha: 15*np.pi/180, v0: 1., g: 9.81} #tukaj uporabimo np.pi, da imamo numerično vrednost
pomik = sym.lambdify(t, resitev.subs(podatki), 'numpy')
hitrost = sym.lambdify(t, resitev.diff(t).subs(podatki), 'numpy')
print('Pomik pri 0s: {:g}m'.format(pomik(0)))
print('Hitrost pri 0s: {:g}m/s'.format(hitrost(0)))Pomik pri 0s: 0m
Hitrost pri 0s: 1m/s
Pripravimo prikaz:
cas = np.linspace(0, 4, 100)
cas2 = np.linspace(0, 4, 5)def slika():
plt.plot(cas, pomik(cas), 'C0', label='Pomik [m]')
plt.plot(cas, hitrost(cas), 'C1', label='Hitrost [m/s]')
plt.plot(cas2, pomik(cas2), 'C0o', label='Pomik - velik korak[m]')
plt.plot(cas2, hitrost(cas2), 'C1o', label='Hitrost - velik korak [m/s]')
plt.xlabel('Čas [s]')
plt.ylabel('Pomik [m] / Hitrost [m/s]')
plt.legend(loc=(1.01, 0));
plt.show()slika()
Simbolno preoblikovanje diferencialne enačbe v sistem diferencialnih enačb prvega reda¶
Spomnimo se izvorne diferencialne enačbe:
eqDefinirajmo nove spremenljivke in pripravimo funkcijo :
y0 = sym.Function('y0')
y1 = sym.Function('y1')
f = sym.simplify(eq.args[1]/m)
fPovežimo sedaj nove spremenljivke.
naj bo enako :
eq1 = sym.Eq(y0(t).diff(t), y1(t))
eq1Odvod (v bistvu je to ) naj bo enak funkciji :
eq2 = sym.Eq(y1(t).diff(t), f)
eq2Zgornje izraze zapišemo v vektorski obliki:
y_odvod = [y0(t).diff(t), y1(t).diff(t)]
y_odvodf_vec = [y1(t), f]
f_vecSpomnimo se sedaj f_vec:
f_vecČe rešujemo numerično, potem funkcijo zapišemo:
pospesek = float((eq.args[1]/m).simplify().subs(podatki))
pospesek #raziščite zakaj smo tukaj tako definirali! namig: type(pospesek)def F_klada(t, y):
return np.array([y[1], pospesek],dtype=float)Preverimo funkcijo pri začetnem času s in pri začetnih pogojih :
y_zacetni_pogoji = np.array([0, podatki[v0]])
y_zacetni_pogojiarray([0., 1.])F_klada(0., y_zacetni_pogoji)array([ 1. , -0.30370487])Uporabimo sedaj Eulerjevo metodo:
#%%timeit
x_Eu = np.linspace(0, 4, 5)
y_Eu = euler_sistem(F_klada, x_Eu, np.array([0, 1.]))
y_Euarray([[ 0. , 1. ],
[ 1. , 0.69629513],
[ 1.69629513, 0.39259025],
[ 2.08888538, 0.08888538],
[ 2.17777075, -0.2148195 ]])Prikažemo in primerjamo z analitično rešitvijo:
def narisi_euler(n=5):
x_Eu = np.linspace(0, 4, n)
y_Eu = euler_sistem(F_klada, x_Eu, np.array([0, 1.]))
plt.title('Eulerjeva metoda s korakom $h={:g}$'.format(x_Eu[1]-x_Eu[0]))
plt.plot(cas, pomik(cas), 'C0', label='Pomik - analitično [m]')
plt.plot(cas, hitrost(cas), 'C1', label='Hitrost - analitično [m/s]')
plt.plot(x_Eu, y_Eu[:, 0], 'C0.', label='Pomik - Euler [m]')
plt.plot(x_Eu, y_Eu[:, 1], 'C1.', label='Hitrost - Euler [m/s]')
plt.xlabel('Čas [s]')
plt.ylabel('Pomik [m] / Hitrost [m/s]')
plt.ylim(-0.5, 2.5)
plt.legend(loc=(1.01, 0))
plt.show();interact(narisi_euler, n=(3, 10, 1));Vprašanje 1: Diferencialno enačbo:
rešite simbolno z uporabo modula sympy. Prikažite rešitev na intervalu (namig: ). Vrednosti neznank so pozitivne in realne.
Začetni pogoj:
Eulerjeva metoda¶
Vprašanje 2: Diferencialno enačbo:
rešite še numerično s pripravljeno funkcijo Eulerjeve metode za 30 vrednosti na intervalu .
Začetni pogoj:
Vprašanje 3: Prikažite vrednosti napake numerične rešitve z Eulerjevo metodo, ko število diskretnih točk na intervalu povečujete od 10 do 100 s korakom 30.
Reševanje navadnih D. E. višjega reda¶
Vprašanje 4: Padalec z maso je izpostavljen gravitaciji in sili zračnega upora. Gibanje padalca opisuje diferencialna enačba:
Zapišite sistem diferencialnih enačb prvega reda in pripravite funkcijo za numerično reševanje. Izračunajte vrednosti funkcije prvih odvodov pri začetnem času in začetnih pogojih.
Podatki:
Vprašanje 5: Zgoraj pripravljeno funkcijo uporabite pri numeričnem reševanju diferencialne enačbe padalca s funkcijo scipy.integrate.solve_ivp pri 100 diskretnih točkah za prvih 30 sekund padca.
Izrišite dobljen potek poti () in hitrosti padalca v odvisnosti od časa . Kako bi izračunali tudi pospešek?
Vprašanje 6: Izstrelek mase izstrelimo s hitrostjo pod kotom . Gibalni enačbi za pomike v smereh in sta:
Sila upora je enaka , pri čemer je hitrost in

vir: Numerical Methods in Engineering With Python 3, 3rd Ed, Jaan Kiusalaas
Sistem dveh diferencialnih enačb drugega reda zapišite v obliki sistema štirih diferencialnih enačb prvega reda in določite vektor začetnih pogojev.
Podatki:
Vprašanje 7: Pripravite funkcijo odvodov za numerično reševanje zgornjega sistema dveh diferencialnih enačb 2. reda in z uporabo scipy.integrate.solve_ivp in določite krivuljo leta izstrelka pri 100 diskretnih točkah v času s.
Sila upora je enaka , pri čemer je hitrost in
Vprašanje 8: Podan sistem dveh diferencialnih enačb drugega reda rešite še s pomočjo funkcije scipy.integrate.solve_ivp z metodo 'LSODA'.
Sistem enačb integrirajte do časa, ko izstrelek pade na tla (). Uporabite enake začetne pogoje kot pri prejšnji nalogi in maksimalni razmik na časovni osi s.
Grafično prikažite dobljeno trajektorijo leta izstrelka.