Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Uvod

Za uvod si oglejmo spodnji video:

Loading...

V okviru reševanja enačb obravnavamo poljubno enačbo, ki je odvisna od spremenljivke xx in iščemo rešitev:

f(x)=0.f(x)=0.

Rešitvam enačbe rečemo tudi koreni (angl. roots). Koren enačbe f(x)=0f(x)=0 je hkrati tudi ničla funkcije y=f(x)y=f(x).

Funkcija y=f(x)y=f(x) ima lahko ničle stopnje:

  • ničla prve stopnje: funkcija seka abscisno os pod neničelnim kotom,

  • ničle sode stopnje: funkcija se dotika abscisne osi, vendar je ne seka,

  • ničle lihe stopnje: funkcija seka abscisno os, pri ničli stopnje 3 in več imamo prevoj (tangenta je vzporedna z abscisno osjo).

Tukaj je pomembno izpostaviti, da iščemo rešitev poljubne enačbe f(x)=0f(x)=0. Če za linearne, kvadratne ali kubične enačbe, lahko določimo analitične rešitve; za večino nelinearnih enačb analitične rešitve ne moremo določiti. Iz tega razloga so numerični pristopi toliko bolj pomembni.

Omejitve funkcije f(x)f(x)

Za funkcijo y=f(x)y=f(x) zahtevamo, da je na zaprtem intervalu [x0,x1][x_0, x_1] zvezna. Pri računanju ničel, se bomo omejili samo na ničle prve stopnje.

Zgled

Poljubno funkcijo y=f(x)y=f(x) lahko definiramo s Pythonovo funkcijo; za zgled tukaj definirajmo polinom:

Ker pa gre za polinom x3−10x2+5x^3-10x^2+5 s koeficienti [1, -10, 0, 5] pa je bolje, da ga definiramo s pomočjo np.poly1d:

numpy.poly1d(c_or_r, r=False, variable=None)

kjer so parametri:

  • c_or_r koeficienti polinoma s padajočo potenco ali če je r=True ničle polinoma,

  • r je privzeto False, kar pomeni, da se podajo koeficienti polinoma,

  • variable spremenljivka, ki se izpiše pri uporabi funkcije print().

Opomba: np.poly1d spada v starejši vmesnik; sodobnejši je razred numpy.polynomial.Polynomial (dokumentacija), ki koeficiente pričakuje v naraščajočem vrstnem redu potenc: Polynomial([5, 0, -10, 1]). Objekt prav tako kličemo kot funkcijo, ničle pa dobimo z metodo roots().

Uvozimo numpy in definirajmo polinom:

   3      2
1 x - 10 x + 5

Prikažimo funkcijo f(x)f(x):

<Figure size 640x480 with 1 Axes>

Opazimo, da so ničle funkcije f(x)f(x) blizu -0,7 in +0,7. Objekt poly1d ima atribut roots ali tudi r (glejte dokumentacijo), ki vrne te ničle:

array([ 9.94949106, 0.73460351, -0.68409457])

V nadaljevanju bo naš cilj numerično določiti ničlo za poljubno funkcijo f.

Inkrementalna metoda

Inkrementalno reševanje temelji na ideji, da v kolikor ima funkcija f(x)f(x) pri x0x_0 in x1x_1 različna predznaka, potem je vmes vsaj ena ničla. Zaprti interval [x0,x1][x_0, x_1] razdelimo torej na odseke širine Δx\Delta x; na odseku, kjer opazimo spremembo predznaka, je vsaj ena ničla funkcije.

Metoda je prikazana na sliki. Inkrementalna metoda

Za ničlo zahtevamo:

∣xi+1−xi∣<εin∣f(xi+1)∣+∣f(xi)∣<D,\left|x_{i+1}-x_i\right|<\varepsilon\quad\textrm{in}\quad \left|f(x_{i+1})\right|+\left|f(x_{i})\right|<D,

kjer je ε\varepsilon zahtevana natančnost rešitve in DD izbrana majhna vrednost, ki prepreči, da bi kot ničlo razpoznali pol (kar sicer zaradi pogoja zveznosti ni mogoče).

Inkrementalna metoda ima nekatere slabosti:

  • je zelo počasna,

  • lahko zgreši dve ničli, ki sta si zelo blizu,

  • večkratne sode ničle (lokalni ekstrem, ki se samo dotika abscise) ne zazna.

Inkrementalna metoda spada med t. i. zaprte (angl. bracketed) metode, saj išče ničle funkcije samo na intervalu [x0,x1][x_0, x_1]. Pozneje bomo spoznali tudi odprte metode, ki lahko konvergirajo k ničli zunaj podanega intervala.

Zaradi vseh zgoraj navedenih slabosti inkrementalno metodo pogosto uporabimo samo za izračun začetnega približka ničle.

Numerična implementacija

Poglejmo si sedaj inkrementalno iskanje ničel funkcije:

Poglejmo sedaj uporabo na zgoraj definiranem polinomu:

array([0.734, 0.735])

Ničla je izolirana z natančnostjo 0,001, preverimo še vsoto absolutnih funkcijskih vrednosti:

np.float64(0.013071529000000304)

Ugotovimo, da je relativno majhna; bomo pa se s sledečimi metodami trudili rezultat bistveno izboljšati.

Pripravimo sliko:

Prikažimo rezultat:

<Figure size 640x480 with 1 Axes>

Da smo torej na intervalu [0,1][0, 1] izračunali rešitev z natančnostjo Δx=0,001\Delta x=0,001, smo morali 1000-krat klicati funkcijo f(x)f(x). Gre za zelo neučinkovito metodo, zato bomo iskali boljše načine; najprej s preprostim iterativnim inkrementalnim pristopom.

Iterativna inkrementalna metoda

Iterativna inkrementalna metoda v prvi iteraciji z inkrementalno metodo omeji interval iskanja ničel pri relativno velikem koraku. Interval, najden v prvi iteraciji, se v drugi iteraciji razdeli na manjše intervale in ponovi se inkrementalno iskanje ničle. Tretja iteracija se nato omeji na interval določen v drugi in tako dalje. Z iteracijami zaključimo, ko smo dosegli predpisano natančnost rešitve ϵ\epsilon.

Metoda je prikazana na sliki: Iterativna inkrementalna metoda

Numerična implementacija

S 30 klici funkcije f(x)f(x) tako dobimo podobno natančnost kot prej v 1000:

array([0.734, 0.735])
array([0.734, 0.735])

Seveda pa lahko natančnost bistveno izboljšamo z večanjem števila iteracij:

array([0.7346035 , 0.73460351])

Preverimo še kriterij vsote absolutnih funkcijskih vrednosti, ki mora biti majhen:

np.float64(1.3073143190212022e-07)

Bisekcijska metoda

Na intervalu [x0,x1][x_0, x_1], kjer vemo, da obstaja ničla funkcije (predznaka f(x0)f(x_0) in f(x1)f(x_1) se razlikujeta), lahko uporabimo bisekcijsko metodo.

Ideja metode je:

  • interval [x0,x1][x_0, x_1] razdelimo na pol (od tukaj ime: bi-sekcija): x2=(x0+x1)/2x_2 = (x_0+x_1)/2,

  • če imata f(x0)f(x_0) in f(x2)f(x_2) različne predznake, je nov interval iskanja ničle [x0,x2][x_0, x_2], sicer pa: [x2,x1][x_2, x_1],

  • glede na predhodni korak definiramo nov zaprt interval [x0,x1][x_0, x_1] in nadaljujemo z iterativnim postopkom, dokler ne dosežemo želene natančnosti ∣x1−x0∣<ε\left|x_1-x_0\right|<\varepsilon.

Slika metode: Bisekcijska metoda

Bisekcijska metoda spada med zaprte metode, ki vrne ničlo funkcije na podanem intervalu [x0,x1][x_0, x_1].

Ocena napake

Če v začetku začnemo z intervalom Δx=∣x1−x0∣\Delta x = \left|x_1-x_0\right|, potem je natančnost bisekcijske metode po prvem koraku bisekcije:

ε1=Δx/2,\varepsilon_1 = \Delta x/2,

po drugem koraku:

ε2=Δx/22\varepsilon_2 = \Delta x/2^2

in po nn korakih:

εn=Δx/2n.\varepsilon_n = \Delta x/2^n.

Ponavadi zahtevamo, da je rešitev podana z natančnostjo ε\varepsilon in iz zgornje enačbe lahko izpeljemo število potrebnih korakov bisekcijske metode:

n=log⁡(Δxε)log⁡(2).n = \frac{\log\left(\frac{\Delta x}{\varepsilon}\right)}{\log(2)}.

Seveda je število korakov celo število.

Numerična implementacija

Sedaj poskusimo najti ničlo z natančnostjo 1e-3:

Rešitev: 0.735, število iteracij: 10, D: 0.01277

V desetih iteracijah smo dobili isto natančen rezultat kakor zgoraj pri iterativni inkrementalni metodi rez30. Poglejmo še izračun ničle s še večjo natančnostjo:

Rešitev: 0.734603, število iteracij: 20, D: 0.00001

Hitrost izvajanja lahko preverimo s t. i. magic funkcijo timeit (dokumentacija), ki večkrat požene funkcijo in analizira čas izvajanja. Če je pred magic funkcijo dvojni znak %%, se izvede in meri čas celotne celice, če pa le enojni %, pa samo ene vrstice.

376 μs ± 8.9 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Iskanje ničle v okolici pola

Poglejmo sedaj iskanje ničle funkcije tan v okolici pola (ničla dejansko ne obstaja):

<Figure size 640x480 with 1 Axes>

V primeru iskanja na intervalu [−1,1][-1, 1] najdemo pravo ničlo:

Rešitev: -0.000, število iteracij: 11, D: 0.00098

V primeru iskanja v okolici pola, pa nas program na to opozori (klic funkcije je tukaj zakomentiran, sicer se avtomatsko generiranje tukaj prekine):

V scipy vgrajena bisekcijska metoda takega preverjanja nima (zaradi hitrosti) in bo vrnila rezultat, ki bo pa napačen. Pri uporabi moramo torej biti previdni.

Uporaba scipy.optimize.root_scalar

Bisekcijska metoda je počasna, vendar zanesljiva metoda iskanja ničel in je implementirana znotraj scipy. Najprej jo uvozimo:

root_scalar(f, args=(), method='bisect', bracket=[a,b], fprime=None, fprime2=None, x0=None, x1=None,xtol=None, rtol=None, maxiter=None, options=None)

Da s funkcijo root_scalar prikličemo bisekcijsko metodo moramo funkciji podati tri parametre: funkijo f ter zaprti interval [a, b] (prek parametra bracket). Predznaka f(a) in f(b) morata biti različna. Ostali parametri, npr. absolutna xtol in relativna rtol napaka ter največje število iteracij maxiter so opcijski - imajo privzete vrednosti. Za več glejte dokumentacijo.

Poglejmo uporabo:

converged: True flag: converged function_calls: 41 iterations: 39 root: 0.7346035077880515 method: bisect

in hitrost:

332 μs ± 22.2 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Preverimo še lahko funkcijo tan(x), najprej na intervalu, kjer je funkcija zvezna:

converged: True flag: converged function_calls: 3 iterations: 1 root: 0.0 method: bisect

Potem še v okolici pola, kjer ni zvezna:

converged: True flag: converged function_calls: 42 iterations: 40 root: -1.5707963267941523 method: bisect

kar je napačna rešitev!

Sekantna metoda

Sekantna metoda zahteva dva začetna približka x0x_0 in x1x_1 in funkcijo f(x)f(x). Ob predpostavki linearne interpolacije med točkama x0,f(x0)x_0, f(x_0) in x1,f(x1)x_1, f(x_1) (skozi točki potegnemo sekanto, od tukaj tudi ime), se določi x2x_2, kjer ima linearna interpolacijska funkcija ničlo. x2x_2 predstavlja nov približek ničle.

Glede na sliko: Sekantna metoda

lahko zapišemo (podobna trikotnika sta na sliki označena z rumeno):

f(x1)x2−x1=f(x0)−f(x1)x1−x0.\frac{f(x_1)}{x_2 − x_1}= \frac{f(x_0) − f(x_1)}{x_1 − x_0}.

Sledi, da je nov približek ničle:

x2=x1−f(x1) x1−x0f(x1)−f(x0).x_2= x_1-f(x_1)\,\frac{x_1 − x_0}{f(x_1) - f(x_0)}.

V naslednjem koraku pri sekantni metodi izvedemo sledeče zamenjave: x0=x1x_0=x_1 in x1=x2x_1=x_2.

Sekantna metoda spada med odprte metode, saj lahko najde ničlo funkcije, ki se nahaja zunaj območja [x0,x1][x_0, x_1].

Ocena napake

Konzervativno lahko napako ocenimo iz razlike med dvema zaporednima približkoma:

ε=∣xn−1−xn∣\varepsilon = \left|x_{n-1} -x_{n}\right|

Konvergenca in red konvergence

Konvergenca pomeni, da zaporedje približkov konvergira k rešitvi enačbe α\alpha (α\alpha je rešitev enačbe).

Red konvergence označuje hitrost konvergiranja.

Če ε\varepsilon označimo napako približka in napako z vsakim korakom iteracije linearno zmanjšamo (CC je konstanta):

εn=C εn−11,\varepsilon_n = C\,\varepsilon_{n-1}^1,

govorimo o redu konvergence 1 (ε\varepsilon ima potenco 1)!

Pri predhodno obravnavani bisekcijski metodi napako na vsakem koraku razpolovimo (εn/εn−1=C=1/2\varepsilon_n/\varepsilon_{n-1} = C = 1/2). Bisekcijska metoda ima red konvergence 1.

Red konvergence sekantne metode je višji in jo je mogoče oceniti z:

εn=C εn−11.618.\varepsilon_n = C\,\varepsilon_{n-1}^{1.618}.

Iz zgornje ocene sledi, da se na vsakem koraku iteracije število točnih cifer poveča za približno 60%. Ker je red konvergence višji od 1 in manjši od kvadratične, tako konvergenco imenujemo superlinearna konvergenca.

Numerična implementacija

Poglejmo si uporabo:

1. korak: x0=1, x1=0.555556.
2. korak: x0=0.555556, x1=0.707845.
3. korak: x0=0.707845, x1=0.737957.
4. korak: x0=0.737957, x1=0.734549.
5. korak: x0=0.734549, x1=0.734603.
6. korak: x0=0.734603, x1=0.734604.
7. korak: x0=0.734604, x1=0.734604.
Rešitev: 0.73460351, D: 0.00000

in hitrost

130 μs ± 6.19 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Kakor smo zapisali zgoraj, je sekantna metoda odprtega tipa. Rešitev enačbe je lahko zunaj podanega intervala. Poglejmo si primer:

1. korak: x0=2, x1=1.41615.
2. korak: x0=1.41615, x1=1.85165.
3. korak: x0=1.85165, x1=1.69887.
4. korak: x0=1.69887, x1=1.97485.
5. korak: x0=1.97485, x1=2.09379.
6. korak: x0=2.09379, x1=2.43519.
7. korak: x0=2.43519, x1=2.76579.
8. korak: x0=2.76579, x1=3.05013.
9. korak: x0=3.05013, x1=3.13625.
10. korak: x0=3.13625, x1=3.14158.
11. korak: x0=3.14158, x1=3.14159.
Rešitev: 3.142, D: 0.00002

Uporaba scipy.optimize.root_scalar

Znotraj scipy je sekantna metoda definirana v okviru scipy.optimize.root_scalar funkcije:

converged: True flag: converged function_calls: 7 iterations: 6 root: 0.7346035077893033 method: secant

V primeru sekantne metode, se druga meja intervala izračuna glede na kodo:

if x0 >= 0:
    x1 = x0*(1 + 1e-4) + 1e-4
else:
    x1 = x0*(1 + 1e-4) - 1e-4

Poglejmo še hitrost

156 μs ± 32.7 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Sorodna sekantni metodi je Ridderjeva metoda; v podrobnosti metode tukaj ne gremo, je pa zaprtega tipa, ima kvadratičen red konvergence ter jo kličemo s pomočjo funkcije root_scalar:

converged: True flag: converged function_calls: 14 iterations: 6 root: 0.7346035077883111 method: ridder

Brentova metoda (brentq) je v Scipy privzeta in priporočena izbira za iskanje ničel na intervalu. Združuje zanesljivost bisekcije in hitrost sekantne metode/inverzne kvadratne interpolacije. Uporaba in hitrost:

converged: True flag: converged function_calls: 9 iterations: 8 root: 0.7346035077893034 method: brentq

Primerjava hitrosti kaže, da je običajno hitrejša od bisekcije:

74.5 μs ± 14.2 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Sicer pa Scipy podpira tudi algoritem TOMS 748, ki velja za asimptotično najučinkovitejšo metodo za iskanje ničel na intervalu:

converged: True flag: converged function_calls: 9 iterations: 4 root: 0.7346035077893033 method: toms748

Primerjava hitrosti (v večini primerov bi naj bila najhitrejša):

316 μs ± 8.94 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

Newtonova metoda

Doslej predstavljene metode ne zahtevajo dodatnih odvodov funkcije; Newtonova metoda, ki si jo bomo pogledali v nadaljevanju, zahteva en začetni približek x0x_0, poleg definicije funkcije f(x)f(x) pa tudi njen odvod f′(x)f'(x). V literaturi za Newtonovo metodo tudi najdemo izraza tangentna in Newton-Raphsonova metoda.

Princip delovanja metode je prikazan na sliki: Newtonova metoda

Metodo bi lahko izpeljali grafično (s slike), tukaj pa si poglejmo izpeljavo s pomočjo Taylorjeve vrste:

f(xi+1)=f(xi)+f′(xi) (xi+1−xi)+O((xi+1−xi)2),f(x_{i+1})=f(x_i)+f'(x_i)\,\left(x_{i+1}-x_i\right)+\mathcal{O}\left((x_{i+1}-x_i)^2\right),

če naj bo pri xi+1x_{i+1} vrednost funkcije nič, potem velja:

0=f(xi)+f′(xi) (xi+1−xi)+O((xi+1−xi)2).0=f(x_i)+f'(x_i)\,\left(x_{i+1}-x_i\right)+\mathcal{O}\left((x_{i+1}-x_i)^2\right).

Naredimo napako metode in zanemarimo člene višjega reda v Taylorjevi vrsti. Lahko izpeljemo:

xi+1=xi−f(xi)f′(xi).x_{i+1}=x_i-\frac{f(x_i)}{f'(x_i)}.

xi+1x_{i+1} je tako nov približek iskane ničle.

Algoritem Newtonove metode je:

  1. izračunamo nov približek xi+1x_{i+1},

  2. računanje prekinemo, če je največje število iteracij doseženo (rešitve enačbe nismo našli),

  3. če velja ∣xi+1−xi∣<ε\left|x_{i+1}-x_i\right|<\varepsilon računanje prekinemo (izračunali smo približek ničle), sicer povečamo indeks ii in gremo v prvi korak.

Opombi:

  • ε\varepsilon je zahtevana absolutna natančnost,

  • Newtonova metoda lahko divergira, zato v algoritmu predpišemo največje število iteracij.

Zgoraj smo omenili, da je Newtonova metoda ena izmed boljših metod za iskanje ničel funkcij. Ima pa tudi nekaj slabosti/omejitev:

  • spada med odprte metode,

  • kvadratična konvergenca je zagotovljena le v dovolj majhni okolici rešitve enačbe,

  • poznati moramo odvod funkcije.

Red konvergence

Red konvergence Newtonove metode je kvadraten:

εn=C εn−12,\varepsilon_n = C\,\varepsilon_{n-1}^{2},

kjer je CC:

C=−f′′(x)2 f′(x).C=-\frac{f''(x)}{2\,f'(x)}.

Konvergenca je torej hitra, v vsaki novi iteraciji se število točnih števk v približku podvoji.

Numerična implementacija

Definirajmo polinom f in njegov prvi odvod df:

Izračunajmo sedaj ničlo:

Rešitev: 0.73460351, število iteracij: 5, D: 0.00000000

Preverimo hitrost izvajanja:

6.05 μs ± 374 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

Uporaba scipy.optimize.root_scalar

Znotraj scipy je Newtonova metoda definirana v okviru scipy.optimize.root_scalar funkcije:

converged: True flag: converged function_calls: 10 iterations: 5 root: 0.7346035077893033 method: newton

In izmerimo hitrost:

43 μs ± 1.07 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Če pri root_scalar podamo tudi drugi odvod fprime2, se uporabi Halleyeva metoda, ki temelji na izrazu:

xn+1=xn−2f(xn)f′(xn)2 f′2(xn)−f(xn)f′′(xn)x_{n+1}=x_{n}-\frac{2f(x_{n})f'(x_{n})}{2\,f'^{2}(x_{n})-f(x_{n})f''(x_{n})}
converged: True flag: converged function_calls: 10 iterations: 3 root: 0.7346035077893033 method: halley

In hitrost:

33.5 μs ± 1.72 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Reševanje sistemov nelinearnih enačb

Rešujemo sistem enačb, ki ga v vektorski obliki zapišemo takole:

f(x)=0.\mathbf{f}(\mathbf{x})=\mathbf{0}.

V skalarni obliki zgornji vektorski izraz zapišemo:

f0(x0,x1,…,xn−1)=0f1(x0,x1,…,xn−1)=0⋮fn−1(x0,x1,…,xn−1)=0.\begin{array}{rcl} f_0(x_0,x_1,\dots, x_{n-1})&=&0\\ f_1(x_0,x_1,\dots, x_{n-1})&=&0\\ &\vdots&\\ f_{n-1}(x_0,x_1,\dots, x_{n-1})&=&0.\\ \end{array}

Reševanje sistema nn nelinearnih enačb je bistveno bolj zahtevno kot reševanje ene same nelinearne enačbe. Tak sistem enačb ima lahko več rešitev in katero izračunamo, je odvisno od začetnih pogojev. Ponavadi nam pri dobri izbiri začetnih pogojev pomaga fizikalni problem, ki ga rešujemo.

Za računanje rešitve sistema enačb se Newtonova metoda izkaže kot najenostavnejša in pogosto tudi najboljša (obstajajo tudi druge metode, ki pa so velikokrat variacije Newtonove metode).

Podobno kot pri izpeljavi Newtonove metode za reševanje ene enačbe, tudi tukaj začnemo z razvojem funkcije fif_i v Taylorjevo vrsto:

fi(x+Δx)=fi(x)+∑j=0n−1∂fi∂xj Δxj+O(∥Δx∥2).f_i(\mathbf{x}+\Delta \mathbf{x})=f_i(\mathbf{x})+\sum_{j=0}^{n-1} \frac{\partial f_i}{\partial x_j}\,\Delta x_j+\mathcal{O}\left(\|\Delta\mathbf{x}\|^2\right).

Naredimo napako metode, ko zanemarimo člene drugega in višjih redov ter zapišemo izraz v matrični obliki:

f(x+Δx)=f(x)+J(x) Δx,\mathbf{f}(\mathbf{x}+\Delta \mathbf{x})=\mathbf{f}(\mathbf{x})+\mathbf{J}(\mathbf{x})\,\Delta \mathbf{x},

kjer je J(x)\mathbf{J}(\mathbf{x}) Jakobijeva matrika pri vrednostih x\mathbf{x}. Elementi Jakobijeve matrike so:

Jij=∂fi∂xj.J_{ij}=\frac{\partial f_i}{\partial x_j}.

Če naj bo x+Δx\mathbf{x}+\Delta \mathbf{x} rešitev sistema enačb, mora veljati:

0=f(x)+J(x) Δx\mathbf{0}=\mathbf{f}(\mathbf{x})+\mathbf{J}(\mathbf{x})\,\Delta \mathbf{x}

in torej sledi:

J(x) Δx=−f(x).\mathbf{J}(\mathbf{x})\,\Delta \mathbf{x}=-\mathbf{f}(\mathbf{x}).

Izpeljali smo sistem linearnih enačb, matrika koeficientov je označena z J(x)\mathbf{J}(\mathbf{x}), vektor neznank je Δx\Delta\mathbf{x} in vektor konstant −f(x).-\mathbf{f}(\mathbf{x}).

Opomba: analitično računanje Jakobijeve matrike je lahko zamudno in zato jo pogosto približno izračunamo pri x\mathbf{x} numerično:

∂fi∂xj≈fi(x+ej h)−fi(x)h,\frac{\partial f_i}{\partial x_j}\approx \frac{f_i(\mathbf{x}+\mathbf{e}_j\,h)-f_i(\mathbf{x})}{h},

kjer je hh majhen premik in je ej\mathbf{e}_j enotski pomik v smeri xjx_j. Če se Jakobijeva matrika izračuna numerično, govorimo o sekantni metodi in ne Newtonovi.

Pri numeričnem izračunu si lahko pomagamo s funkcijo scipy.optimize.approx_fprime (za podrobnosti glejte dokumentacijo).

Numerična implementacija

Algoritem torej je:

  1. Izberemo začetni približek x0\mathbf{x}_0, največje število iteracij in postavimo indeks na nič: i=0i=0.

  2. Izračunamo Jakobijevo matriko J(xi)\mathbf{J}(\mathbf{x_i}) in rešimo linearni sistem: J(xi) Δxi=−f(xi)\mathbf{J}(\mathbf{x}_i)\,\Delta \mathbf{x}_i=-\mathbf{f}(\mathbf{x}_i).

  3. Izračunamo nov približek: xi+1=xi+Δxi\mathbf{x}_{i+1}=\mathbf{x}_{i}+\Delta\mathbf{x}_i.

  4. Če je napaka manjša od zahtevane, se postopek prekine*. Postopek prekinemo tudi, če je število iteracij večje od dovoljenega, sicer povečamo indeks i=i+1i=i+1 in se vrnemo v korak 2.

* Opomba:

Napako lahko ocenimo z normo razlike dveh zaporednih približkov:

∑j=0n−1∣xi,j−xi−1,j∣<ε,\sum_{j=0}^{n-1}\left|x_{i,j}-x_{i-1,j}\right|<\varepsilon,

kjer je ii indeks iteracije in jj indeks elementa.

Uporaba scipy.optimize.root

Funkcija scipy.optimize.root ima obsežno dokumentacijo in omogoča večje število različnih pristopov:

root(fun, x0, args=(), method='hybr', jac=None, tol=None, callback=None, options=None)

Če uporabimo privzete parametre, moramo definirati zgolj vektorsko funkcijo fun in začetno vrednost x0.

Uvozimo funkcijo:

Poglejmo si uporabo na zgledu (gre za zgled na str. 76, Jože Petrišič, Reševanje enačb, 1996, FS, UNI-LJ):

f0(x)=x02+x0 x1−10=0,f_0(\mathbf{x})=x_0^2+x_0\,x_1-10=0,
f1(x)=x1+3 x0 x12−57=0,f_1(\mathbf{x})=x_1+3\,x_0\,x_1^2-57=0,

kjer je vektor x=[x0,x1]\mathbf{x}=[x_0, x_1].

Najprej definirajmo Python funkcijo, ki vrne seznam rezultatov funkcij [f0(x),f1(x)][f_0(\mathbf{x}),f_1(\mathbf{x})]:

Definirajmo še Jakobijevo matriko:

Uporabimo začetne vrednosti x0=1,5x_0=1,5 in x1=3,5x_1=3,5 ter rešimo problem:

message: The solution converged. success: True status: 1 fun: [-6.972e-12 3.148e-11] x: [ 2.000e+00 3.000e+00] method: hybr nfev: 12 njev: 1 fjac: [[-2.235e-01 -9.747e-01] [ 9.747e-01 -2.235e-01]] r: [-2.954e+01 -3.782e+01 -6.940e+00] qtf: [-2.836e-08 -1.324e-08]

Funkcija root vrne obsežen rezultat. Najbolj pomembna sta atribut x, ki predstavlja iskano rešitev, in atribut success, ki pove, ali je rešitev konvergirala:

array([2., 3.])
True
<Figure size 800x600 with 1 Axes>

Dodatno

Tisti, ki ste navdušeni nad Raspberry Pi in uporabljate njihovo kamero (npr. tole brez infrardečega filtra), vas bo morebiti zanimala knjižnica picamera.

Uporaba sympy.solve za reševanje enačb

Za manjše sisteme lahko rešitev najdemo tudi simbolno. Poglejmo si zgornji primer:

Loading...
Število rešitev: 4
Prva rešitev: (2, 3)

Vprašanja za vaje


Primer 1: Porazdelitev tlaka vzdolž krila modela letala aproksimiramo s premico:

Notranji upogibni moment za prikazan primer je definiran kot:

M(x)=−20x3+24x2−4.2x+0.2M(x) = -20x^3 + 24x^2 - 4.2x + 0.2

Zanima nas, pri kateri oddaljenosti od trupa letala xx je krilo maksimalno in minimalno upogibno obremenjeno.

Poiščimo prave rešitve najprej s pomočjo Sympy (Pozor: tukaj ne gre za numerično reševanje enačb!).


Numerično reševanje enačb (iskanje ničel)

Bisekcijska metoda

Vprašanje 2: Z uporabo bisekcijske metode določite ničle upogibnega momenta M(x)M(x) na intervalu [0,l][0, l]. Primerjajte rezultate s simbolno dobljenimi in komentirajte rezultat. Dolžina krila l=1ml=1 m.

M(x)=−20x3+24x2−4.2x+0.2M(x) = -20x^3 + 24x^2 - 4.2x + 0.2

Vprašanje 3: Poiščite ekstreme funkcije M(x)M(x) na intervalu [0,l][0 ,l].

Komentirajte ustreznost bisekcijske metode za iskanje ničel f(x)f(x) v našem primeru. Rezultate lahko preverite s pomočjo knjižnjice Sympy.

Vprašanje 4: Lastno nihanje sistema mase in vzmeti opišemo z enačbo:

x(t)=cos⁡(πt)x(t) = \cos(\pi t)

Z uporabo bisekcijske metode poiščite vse vrednosti časa tt na intervalu t∈[0,20] st \in [0, 20] \, s, pri katerih je nihalo v ravnovesni legi.

Vprašanje 5: Nalogo 3 rešite tudi z uporabo Ridderjeve metode iz paketa scipy. Primerjajte hitrost Ridderjeve metode z metodo bisekcije iz scipy.


Sekantna metoda

Vprašanje 6: Z uporabo sekantne metode določite vse ničle M(x)M(x) na intervalu [0,l][0, l] (pomagate si lahko z grafičnim prikazom).

M(x)=−20x3+24x2−4.2x+0.2M(x) = -20x^3 + 24x^2 - 4.2x + 0.2

Vprašanje 7: Izračunati želite lastne frekvence nosilca na sliki v upogibni smeri.

V literaturi ste prebrali, da velja:

c2=EI / ρA,β4=ω02 / c2c^2 = EI\, /\, \rho A, \qquad \beta^4 = \omega_0^2 \, / \, c^2

za določitev lastnih frekvenc pa je potrebno poiskati presečišča krivulj:

tanh⁡(βl)=tan⁡(βl)\tanh(\beta l) = \tan(\beta l).

Z uporabo bisekcijske in sekantne metode iz modula scipy določite vrednosti βl\beta l na območju βl∈[0,11]\beta l \in [0, 11], ki rešijo zgornjo enačbo. Uporabite podane vrednosti začetnih približkov in območij.


Newton-Raphsonova metoda

Vprašanje 8: Poiščite vse tri ničle funkcije iz prejšnje naloge z uporabo metode Newton-Raphson.

Funkcijo prvega odvoda določite s pomočjo orodij Sympy, pomagajte si s funkcijo sym.lambdify(x, f, 'numpy'). Uporabite podane vrednosti začetnih približkov.

tanh⁡(βl)=tan⁡(βl)\tanh(\beta l) = \tan(\beta l)

Sistemi nelinearnih enačb

Vprašanje 9: S pomočjo scipy.optimize.root poiščite rešitev sistema nelinearnih enačb:

sin⁡(x)+y+2=0\sin(x) + y + 2 = 0
2x+3y=02^x + 3y = 0

Za vrednosti začetnih približkov izberite: x0=2,y0=0x_0 = 2, \quad y_0 = 0