Demo 6: Ekstremumundersøgelse og optimering#

Demo af Christian Mikkelstrup, Hans Henrik Hermansen, Jakob Lemvig, Karl Johan Måstrup Kristensen og Magnus Troen. Revideret marts 2026 af shsp.

from sympy import *
from dtumathtools import *
init_printing()

Til undersøgelsen af extrema for en funktion \(f:\Omega \to \mathbb{R}\) fortæller lærebogen, at de kan findes tre steder:

  1. I stationære punkter, altså punkter \(\boldsymbol{x}_0\) hvor \(\nabla f(\boldsymbol{x}_0) = \mathbf 0\)

  2. På randen af definitionsmængden, så et punkt \(\boldsymbol{x}_0 \in \partial \Omega \cap \Omega\)

  3. I undtagelsespunkter, hvilket vil sige punkter \(\boldsymbol{x}_0\) hvori \(f\) ikke er differentiabel

Karakterisering af lokale ekstrema#

Når de stationære punkter for en funktion er fundet, kan de jf. lærebogen karakteriseres ud fra Hessematricens egenværdier i punktet:

  • Hvis alle egenværdier er positive, er det stationære punkt et egentligt (lokalt) minimum.

  • Hvis de alle er negative, er det et egentligt (lokalt) maksimum.

  • Hvis nogle egenværdier har forskelligt fortegn, er det stationære punkt et saddelpunkt, og dermed ikke et extremum.

Bemærk, at hvis en egenværdi er nul i et stationært punkt (mens alle andre egenværdier har samme fortegn), så fortæller denne metode os intet om punktet, og vi vil skulle undersøge punktet på anden vis.

Når karakterisering er nemt (en pæn Hessematrix)#

Lad os finde ekstrema for en funktion \(f: \mathbb{R}^2 \to \mathbb{R}\) givet ved:

\[\begin{equation*} f(x,y) = x^3 - 3x^2 + y^3 - 3y^2. \end{equation*}\]
x,y = symbols("x y", real=True)
def f(x,y):
    return x**3 - 3 * x**2 + y**3 - 3 * y**2

f(x,y)
../_images/d0d44b54790e8bebb5599b19f00f51446c596eabbca2edcda9e3d851b4e086d8.png

Vi bemærker, at \(f\) er defineret og differentiabel overalt på \(\mathbb{R}^2\). Der er derfor ingen randpunkter eller undtagelsespunkter at undersøge, og ethvert (lokalt) ekstrema vil være at finde i et stationært punkt.

Lad os finde alle stationære punkter:

lign1 = Eq(f(x,y).diff(x),0)
lign2 = Eq(f(x,y).diff(y),0)
lign1,lign2
../_images/b81cdcc7cfb63bf4b0532d5141d5fb7fc4be26cba3199767025edf1dd86d4dc2.png
losn = nonlinsolve([lign1, lign2], [x,y])
losn
../_images/86a0660ce75ee9f241d71deeb991473d3bb292cfd78ad8c13a1b9d45aca53fb6.png

og herefter Hessematricen:

H = dtutools.hessian(f(x,y))
H
\[\begin{split}\displaystyle \left[\begin{matrix}6 x - 6 & 0\\0 & 6 y - 6\end{matrix}\right]\end{split}\]

som evalueres i hvert stationært punkt:

[{(x0, y0): H.subs([(x,x0),(y,y0)])} for (x0,y0) in losn]
\[\begin{split}\displaystyle \left[ \left\{ \left( 0, \ 0\right) : \left[\begin{matrix}-6 & 0\\0 & -6\end{matrix}\right]\right\}, \ \left\{ \left( 0, \ 2\right) : \left[\begin{matrix}-6 & 0\\0 & 6\end{matrix}\right]\right\}, \ \left\{ \left( 2, \ 0\right) : \left[\begin{matrix}6 & 0\\0 & -6\end{matrix}\right]\right\}, \ \left\{ \left( 2, \ 2\right) : \left[\begin{matrix}6 & 0\\0 & 6\end{matrix}\right]\right\}\right]\end{split}\]

Da alle disse er diagonalmatricer, læses egenværdierne direkte af deres diagonaler. Vi konkluderer:

  • \((0,0)\) er et egentligt (lokalt) maksimum (da vi ser to negative egenværdier).

  • \((2,2)\) er et egentligt (lokalt) minimum (da vi ser to positive egenværdier).

  • \((0,2)\) og \((2,0)\) er ikke ekstrema men saddelpunkter (da vi ser egenværdier med forskellige fortegn).

Et plot med alle fire stationære punkter:

# Tegn punkterne
liste_med_stat_pt = [Matrix([x0,y0,f(x0,y0)]) for (x0,y0) in losn]
punkter = dtuplot.scatter(liste_med_stat_pt, show=False, rendering_kw={"s" : 100,"color":"red"})

# Tegn fladen f
pf = dtuplot.plot3d(f(x,y),(x,-0.8,2.8),(y,-0.8,2.8),use_cm=True, colorbar=False,show=False,wireframe=True,rendering_kw={"alpha":0.6})
pf.camera = {"azim" : -50, "elev" : 30}

# Samlet plot
pf.extend(punkter)
pf.show()
../_images/8f225afc553a72561e0625aa7fefa4d6572488105321749f88523649061f2413.png

Ingen globale ekstrema#

Bemærk, at vi kun undersøgte lokale extrema i ovenstående. Kunne vi være så heldige, at én af dem også et globalt maksimum eller globalt minimum?

Det ville kræve, at ingen andre punkter giver en større, hhv. lavere, funktionsværdi. Men fordi \(f(x,y)\to\infty\) for fx \(x\to \infty\), da \(x^3\)-leddet gror hurtigere end det negative \(x^2\)-led, og da \(f(x,y)\to-\infty\) for fx \(x\to -\infty\), så er billedmængden af funktionen \(\operatorname{im}(f)=]-\infty,\infty[\,\), og funktionen har dermed ingen globale ekstrema.

Når karakterisering ikke er så nemt#

Lad os nu undersøge ekstrema for en anden funktion \(f:\mathbb{R}^2 \to \mathbb{R}\) givet ved

\[\begin{equation*} f(x,y) = x^4 + 4x^2y^2 + y^4 -4x^3 - 4y^3 + 2. \end{equation*}\]
x,y = symbols('x y', real=True)
def f(x, y):
    return x**4 + 4*x**2*y**2 + y**4 - 4*x**3 - 4*y**3 + 2

f(x,y)
../_images/ec8db319f1fe28386f3875f94e99650dd28f8dc36257d03fef92f71706201344.png

Igen er definitionsmængden hele \(\mathbb{R}^2\), hvorpå \(f\) er differentiabel overalt, så igen er der ingen rand ej heller undtagelsespunkter at undersøge, og vi fokuserer kun på stationære punkter.

Alle stationære punkter er:

lign1 = Eq(f(x,y).diff(x),0)
lign2 = Eq(f(x,y).diff(y),0)
lign1,lign2
../_images/afa3cda94f85bd0339d16ce8f41f1e9d1b64072b88a0a126c157cac5529251a9.png
losn = nonlinsolve([lign1,lign2], [x, y])
losn
../_images/9dd6bdf06e912975084d5552fdbadecb669a2d088ca103d379f9ebcb38618de0.png

Lad os få disse udskrevet som decimaltal:

[(N(xlosn),N(ylosn)) for (xlosn,ylosn) in losn]
../_images/0f3701372369212721b51b2d06d419847f5c998ac9e95b4e3881f3e58c13a80d.png

Vi ser ikke-reelle løsninger, selvom vi tidligere afgrænsede vores variabler til de reelle tal med real=True. Den ærgerlige årsag er, at nonlinsolve() ignorerer, hvordan variablerne er defineret. Vi er derfor nødt til manuelt at udtrække de løsninger fra løsningsmængden, der ligger inden for definitionsmængden. Der er kun fire reelle løsninger iblandt dem:

stat_pt = list(losn)[:-2] # Udelad de sidste to elementer (de ikke-reelle løsninger) med "list slicing"
stat_pt
../_images/3c1b534ba257b0414250cd25b3c59b538d479aed985148e4ad1071e4c8cbbe65.png
liste_med_stat_pt = [Matrix([x0,y0,f(x0,y0)]) for (x0,y0) in stat_pt]
punkter = dtuplot.scatter(liste_med_stat_pt, show=False, rendering_kw={"color":"red", "s" : 30})

pf = dtuplot.plot3d(f(x,y),(x,-2, 5),(y,-2, 4),xlim=(-2,5),ylim=(-2,5),zlim=(-30,15), show=False, rendering_kw={"alpha":0.5})
pf.camera = {"azim" : -161, "elev" : 5}
(pf + punkter).show()
../_images/d069b4201e310e10bf3b3222df1db86dff4c4432b7c61ca35c4ec167b12c68c0.png

Hessematricen

H = dtutools.hessian(f(x,y))
H
\[\begin{split}\displaystyle \left[\begin{matrix}12 x^{2} - 24 x + 8 y^{2} & 16 x y\\16 x y & 8 x^{2} + 12 y^{2} - 24 y\end{matrix}\right]\end{split}\]

De stationære punkter indsættes én ad gangen:

Hesse_matricer = [H.subs([(x,x0),(y,y0)]) for x0,y0 in stat_pt]
Hesse_matricer
\[\begin{split}\displaystyle \left[ \left[\begin{matrix}0 & 0\\0 & 0\end{matrix}\right], \ \left[\begin{matrix}72 & 0\\0 & 36\end{matrix}\right], \ \left[\begin{matrix}-4 & 16\\16 & -4\end{matrix}\right], \ \left[\begin{matrix}36 & 0\\0 & 72\end{matrix}\right]\right]\end{split}\]

Ikke alle Hessematricer er diagonale, så vi beregner deres egenværdier med eigenvals:

Egenv_Hesse_matricer = [h.eigenvals() for h in Hesse_matricer]

display(*zip(stat_pt,Egenv_Hesse_matricer))
../_images/8464fcd64d3ba5d490cf189b88def8bd358651630ad688b280fd98a2dd4e5183.png ../_images/a0598d77b8598725b4fcd969705a72db3a425b0efc0897c4ad4c62f5fcdbe2b7.png ../_images/3ee5185c597080f9b5623f300cd9e03e04b144b4d3132caff2733b1256b41315.png ../_images/9e31eaee51c0541d2d4bda733fdb522510943147898c65b6211f70f0ec39b007.png

Heraf aflæser vi:

  • \((0,3)\) og \((3,0)\) er begge egentlige (lokale) minima (positive egenværdier, \(\lambda=36,72\))

  • \((1,1)\) er et saddelpunkt (egenværdier med modsatte fortegn, \(\lambda=-20,12\))

Det sidste stationære punkt \((0,0)\) kan desværre ikke karakteriseres med denne metode, da vi her har en egenværdi på \(0\). Dette punkt skal undersøges særskildt på anden vis.

Særundersøgelse af problematisk stationært punkt#

En metode til særundersøgelse af punktet, er at nærme sig det fra forskellige retninger på funktionen. Dette kan gøres ved, at der laves en restriktion af \(f\) langs forskellige kurver i definitionsmængden. Hvis \((0,0)\) vitterligt er et ekstremumpunkt for \(f\), så vil det være et ekstremumpunkt for enhver kurve på \(f\), der går igennem punktet (er det fx et maksimum for \(f\), så er det et maksimum for alle kurver igennem det). Skulle vi være heldige at finde blot ét modeksempel, så kan vi straks konkludere, at \(f\) ikke har ekstremum i punktet.

Lad os fx undersøge, hvordan \(f\) opfører sig langs linjen \(y=x\) (altså, vi undersøger restriktionen \(f(x,x)\)):

dtuplot.plot(f(x,x),f(0,0),(x,-0.5,1.2),axis_center="auto")
../_images/a3e119316afc56299c9c756d6a2a6687acc99a7ff572ac051bc293dbc526230c.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f91d8bdf9d0>

Vi har tilføjet en vandret linje for at angive funktionsværdien i punktet, \(f(0,0)\). Husk på, at hver \(x\)-værdi på dette plot repræsenterer et \((x,x)\)-punkt på \(f\); det er altså i \(x=0\) på denne graf, at vi finder \(f\)s stationære punkt \((0,0)\). Visuelt ligner dette punkt et saddelpunkt langs \(y=x\). Lad os undersøge forskellen \(f(x,x) - f(0,0)\), når vi nærmer os punktet fra hver side langs \(y=x\):

forskel = f(x,x) - f(0,0)
forskel.factor()
../_images/d8b2ac1c09a11757465e2aedf25e4cedafcabac423b0cfcad6f74345b293e207.png

Vi ser, at

\[f(x,x) - f(0,0) > 0 \text{ for } x < 0\,, \quad\text{og}\quad f(x,x) - f(0,0) < 0 \text{ for } 0 < x < \frac{4}{3}.\]

Da \(f(x,x)\) antager værdier, der både er større og mindre end \(f(0,0)\) for \((x,x)\) vilkårligt tæt på \((0,0)\), så kan \(f\) ikke have ekstremum i \((0,0)\). \((0,0)\) er et saddelpunkt på linjen \(y=x\) og dermed også for \(f\).

Globale ekstrema#

Lad os nu betragte en funktion \(f:\Omega \to \mathbb{R}\) med forskriften

\[\begin{equation*} f(x,y) = 3 + x - x ^ 2 - y ^ 2. \end{equation*}\]
def f(x,y):
    return 3 + x - x ** 2 - y ** 2

f(x,y)
../_images/2b60cb677458b8ae426d0d2faee8f7e15fbdb8597cddf627887cd45654f39256.png

\(f\) er veldefineret på hele \(\mathbb{R}^2\), men denne gang begrænser vi definitionsmængden ved

\[\begin{equation*} \Omega = \Bigl\{(x,y) \in [0,2]\times[-1,1]\quad |\quad y \leq 1-x\Bigr\}, \end{equation*}\]

altså som mængden, der omkranses af linjerne \(y = -1\), \(y = 1-x\) og \(x=0\).

M = dtuplot.plot_implicit(Eq(x,0),Eq(y,1-x),Eq(y,-1), (1-x >= y) & (y >= -1) & (x >= 0),(x,-0.1,2.1),(y,-1.1,1.1), show=False, adaptive=True)
M.legend = False
M.show()
../_images/af7928256dbea2383fcdf4484b41392fd4edc5f7692ac5e3d54b7dc3a840195d.png

\(f\) er differentiabel på hele \(\Omega\), så der er ingen undtagelsespunkter. Derfor er den også kontinuert på hele \(\Omega\). Da \(\Omega\) er afsluttet og begrænset, har \(f\) et globalt minimum og et globalt maksimum på \(\Omega\). Disse to værdier antages enten i stationære punkter eller på randen.

Da \(\Omega\) er en (kurve)sammenhængende mængde, ved vi, at billedmængden/værdimængden er \(f(\Omega) = [m;M]\), hvor \(m\) er det globale minimum og \(M\) det globale maksimum.

Her et hurtigt blik på \(f\)s niveaukurver:

niveaukurve = dtuplot.plot_contour(f(x,y),(x,-0.1,2.1),(y,-1.1,1.1),show=False)
niveaukurve.extend(dtuplot.plot_implicit(Eq(x,0),Eq(y,1-x),Eq(y,-1),(x,-0.1,2.1),(y,-1.1,1.1),show=False))
niveaukurve.show()
../_images/f49bfb9b67e746ec0166c474c86a314e694feedfbd56790388ca60b30d0c238a.png

Undersøgelse af stationære punkter#

Vi sætter de partielt afledte lig med nul og løser systemet:

lign1 = Eq(f(x,y).diff(x),0)
lign2 = Eq(f(x,y).diff(y),0)
losn = nonlinsolve([lign1,lign2], [x,y])
losn
../_images/5365f8c4dfc320aa84e7c417528da5b61ada220d0ccfbf9d27f3f0bdadd21522.png

Her er kun én løsning, og det er et punkt inden for definitionsmængden \(\Omega\). Derfor har \(f\) ét stationært punkt, hvilket er en kandidat til at være globalt ekstremum. Vores søgning er ikke slut endnu, så lad os placere dette punkt i en kandidatliste, der kan opdateres løbende, som yderligere kandidater dukker op:

kandidater = set(losn)
kandidater
../_images/5365f8c4dfc320aa84e7c417528da5b61ada220d0ccfbf9d27f3f0bdadd21522.png

Randundersøgelse#

Næste trin er at finde mulige kandidater for globalt ekstremum på randen af definitionsmængden. Grundet hjørnerne, er randen kun stykkevist differentiabel. Vi undersøger hvert differentiabelt randstykke individuelt.

For at gøre det, laver vi en restriktion af \(f\) til hvert randstykke, ét ad gangen, således at vi kan undersøge stykkerne for stationære punkter. De tre restriktioner dannes ved \(f(0,y)\), \(f(x,1-x)\) og \(f(x,-1)\). Lad os lægge ud med at plotte dem:

dtuplot.plot(f(x,-1),(x,0,2), title="Randrestriktion f(x, -1)")
../_images/6d7b337d7031aed24f01b4637343b0ecc093d331809c509615c3d73e01eddd1a.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f91d8a49610>
dtuplot.plot(f(x,1-x),(x,0,2), title="Randrestriktion f(x, 1-x)")
../_images/d2e3bffbec68310b489bc5695fd14479436ba036f2e2073c7cfdaa169a2dfba6.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f91d88f7910>
dtuplot.plot(f(0,y), (y,-1,1), ylim = (0,3),aspect="equal", title="Randrestriktion f(0, y)")
../_images/dff6bc969ea28fe046fede43411329471b73a23b80977331413cf1c0d58afb31.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f91d8563f10>

Plottet tyder på et stationært punkt på hvert randstykke. For at være sikre differentierer vi dem og sætter resultaterne lig nul:

lodretStykke = solve(f(0,y).diff(y))
lodretStykke
../_images/0565f454f5cd4102cef02b57066b6ec8f352dc5e3de83f0b57c38f808e7f1e1c.png
vandretStykke = solve(f(x,-1).diff(x))
vandretStykke
../_images/9c630ee8dc690b9f358a0ee968bbdc8feff8579bb3bba0813fc63d9933c5493d.png
vinkletStykke = solve(f(x,1-x).diff(x))
vinkletStykke
../_images/788e831859b5c82c10d59859750c8dbc8e4a23476b2b4b9cc0793f0457f3b056.png

Husk på, at når vi løser et udtryk uden eksplicit at angive højresiden eller variablen, så antager Sympy, at højresiden skal være nul, og at variablen skal være den eneste tilgængelige variabel. Med solve(f(0,y).diff(y)) løses altså ligningen \(\frac{\mathrm df(0,y)}{\mathrm dy}=0\) for \(y\), og vi ser herover, at resultatet blev \(y=0\).

Vi har ganske rigtigt fundet én løsning for hvert randstykke, som alle befinder sig deres respektive randstykke (altså, inden for definitionsmængden af randstykket) og derfor inden for \(f\)s definitionsmængde. De er derfor alle tre stationære punkter på randen af \(f\) og mulige kandidater til globalt ekstremum.

Lad os rekonstruere dem som punkter i \(\Omega\subset \Bbb R^2\) og tilføje dem til vores kandidatliste:

kandidater.update(set([(0,y0) for y0 in lodretStykke]))
kandidater.update(set([(x0,-1) for x0 in vandretStykke]))
kandidater.update(set([(x0,1-x0) for x0 in vinkletStykke]))
kandidater
../_images/55d186eb13e6e3e32f26b72bb41664dde60ce3beaef954aa85d0dae09c3dabec.png

Glem ikke hjørnerne#

Har vi nu undersøgt hele definitionsmængden \(\Omega\) for \(f\)? Har vi betragtet alle punkter?

Nej! Vi mangler hjørnerne\(\Omega\), som er \((0,1)\), \((0,-1)\) og \((2,-1)\). Disse punkter ligger på randen, men da randrestriktionen af \(f\) ikke er differentiabel dér, blev de ekskluderet i vores randundersøgelse ovenfor, da hvert randstykke blev betragtet som en åben mængde og dermed uden endepunkter.

Heldigvis er enkelte, manglende punkter nemme at håndtere. Tilføj dem bare til kandidatlisten. Det er blot vigtigt, at vi ikke glemmer dem.

kandidater = list(kandidater) + [(0,1),(0,-1),(2,-1)]
kandidater
../_images/a1182b10cee7f276684b122608b7fd36eb0777ad75eb5e48efc68c9a873c5272.png

Den afsluttende sammenligning#

Vi har nu samlet en lang liste af mulige kandidater til globale ekstrema efter en grundig, udtømmende søgning gennem definitionsmængden. Det sidste, der mangler, er en sammenligning af deres funktionsværdier - enten ved manuelt at taste punkterne fra kandidatlisten ind:

f(3/4,1/4),f(1/2,-1),f(1/2,0),f(0,0),f(0,1),f(0,-1),f(2,-1)
../_images/025b3505904bb1ea3c4a762cee5fabcb26b027e2e63f3b81c8699f28ca8f975b.png

eller, hvis du vil undgå risikoen for tastefejl, at lade Sympy udtrække og evaluere alle punkterne i én kodelinje:

f_vaerdier = [f(x0,y0) for x0,y0 in kandidater]
f_vaerdier
../_images/a1f8998e665a2c1488a9f880c0a080b7f0935a7d6ae75235d13d810650dd9d2c.png

Da vi har udtømt definitionsmængden, kan vi være sikre på, at det punkt i vores kandidatliste, der giver den største funktionsværdi, må være funktionens globale maksimum, mens kandidaten, der giver den mindste funktionsværdi, må være dens globale minimum. Vi kan direkte se i listen, hvilke der er tale om, eller vi kan lade Sympy udvælge den største og mindste funktionsværdi:

minimum = min(f_vaerdier)
maksimum = max(f_vaerdier)
minimum, kandidater[f_vaerdier.index(minimum)], maksimum, kandidater[f_vaerdier.index(maksimum)]
../_images/cb952f5706907c6e9091244d79d57bf5cd398ca468c0ec4237cb89a7a2e90ae9.png

Altså:

  • Det globale minimum for \(f\) er \(0\) og antages i punktet \((2,-1)\) (et af hjørnerne)

  • Det globale maksimum for \(f\) er \(\frac{13}{4}\) og antages i punktet \((\frac{1}{2},0)\) (det indre stationære punkt)

Som samlet konklusion har vi, at billedmængden af \(f\) er \(\mathrm{im} f=[0,\frac{13}{4}]\).

Et plot af grafen for \(f\) med definitionsmængden markeret samt de to globale ekstrema indtegnet:

u = symbols("u")
pf = dtuplot.plot3d(f(x,y),(x,0,2),(y,-1,1),show=False,rendering_kw={"alpha":0.7})
punkter = dtuplot.scatter([Matrix([2,-1,0]),Matrix([1/2,0,13/4])],show=False,rendering_kw={"color":"red","s":20})
l1 = dtuplot.plot3d_parametric_line(u,-1,f(u,-1),(u,0,2),use_cm=False,show=False,rendering_kw={"color":"red","linewidth":2})
l2 = dtuplot.plot3d_parametric_line(0,u,f(0,u),(u,-1,1),use_cm=False,show=False,rendering_kw={"color":"red","linewidth":2})
l3 = dtuplot.plot3d_parametric_line(u,1-u,f(u,1-u),(u,0,2),use_cm=False,show=False,rendering_kw={"color":"red","linewidth":2})
kombineret = (pf + punkter + l1 + l2 + l3)
kombineret.camera = {"azim" : 148, "elev" : 40}
kombineret.legend = False

kombineret.show()
../_images/40538d0d982a434ace56937d92e4483a4e5026bcf69194684f27a77f2e4d348e.png