Demo 1: Funktioner, plots og partielt afledte#
Demo af Christian Mikkelstrup og Hans Henrik Hermansen. Revideret marts 2026 af shsp.
from sympy import *
from dtumathtools import *
init_printing()
Velkommen tilbage efter jul og januar og velkommen til et forår med Matematik 1b. Vi ser frem imod en mængde spændende, nyt matematisk pensum og blandt en hel del 3D plots! Til det formål har vi udviklet Python-biblioteket dtumathtools, der vil støtte dig semesteret igennem. Det indeholder dtuplot, der er designet til effektiv plottearbejde, samt flere andre gode hjælpefunktioner. Du bør allerede have dtumathtools installeret på din computer fra Matematik 1a; hvis ikke, så kør følgende kommando i en terminal:
conda install dtumathtools==2025.2.0 # Erstat conda med pip eller pip3, hvis du benytter dette til pakkeimport
Funktioner af én variabel#
At definere funktioner og evaluere funktionsværdier#
Vi kan definere en matematisk funktion, så som \(f: \mathbb{R} \to \mathbb{R}\), \(f(x)=x \operatorname{e}^x\), som en Python-funktion med følgende velkendte struktur af def-kommandoen:
def f(x):
return x * exp(x)
\(f\) kan herefter evalueres i punktet \(x = -2\) ved:
f(-2)
der som decimaltal er:
f(-2).evalf()
Det er ofte ikke nødvendigt at definere vores matematiske funktioner som Python-funktioner med def-kommandoen. Du vil ofte foretrække blot at lagre et funktionsudtryk som et symbol:
x = symbols('x', real = True) # Definér x som symbolsk (Sympy-)variabel (vi har sat assumption real=True, da R -> R, selvom det ikke er strengt nødvendigt her)
f_udtryk = x * exp(x)
f_udtryk
Med denne definition vil det selvfølgelig ikke virke at taste f(-2). I stedet vil vi benytte substitution, når der skal evalueres funktionsværdier:
f_udtryk.subs(x, -2)
Bemærk, at en Python-funktion og en matematisk funktion er forskellige koncepter (en Python-funktion behøver ikke at repræsentere en matematisk funktion). Når vi bruger begrebet “funktion” i disse demoer, er der som udgangspunkt altid tale om en matematisk funktion; ellers vil vi eksplicit skrive “Python-funktion”.
Afledte, grænseværdier og plots#
Funktionen \(f\) kan differentieres ved:
f_maerke = f_udtryk.diff(x)
f_maerke
Man kan undersøge grænseværdier, fx når \(x \to -\infty\), \(x \to \infty\) og \(x \to -2\), med kommandoerne:
f_udtryk.limit(x, -oo), f_udtryk.limit(x, oo), f_udtryk.limit(x, -2)
Da funktionen er kontinuert, bør det ikke være overraskende, at \(\displaystyle\lim_{x \to -2} f(x) = f(-2)\).
Man plotter graferne for en funktion og dens afledte i samme vindue ved:
plot(f_udtryk, f_maerke, (x, -5, 1))
<sympy.plotting.backends.matplotlibbackend.matplotlib.MatplotlibBackend at 0x7fad9abd56d0>
Sammenlign dette plot fra den indbyggede plot-kommando med følgende fra dtuplot:
dtuplot.plot(f_udtryk, f_maerke, (x, -5, 1))
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad7af110d0>
Vi ser her, at visse plotegenskaber, så som grid og legend, er sat som standard.
Stykkevist definerede funktioner#
Et eksempel, der er en smule mere kompliceret, kunne være den stykkevist defineret funktion \(g: \mathbb{R} \to \mathbb{R}\),
som er sværere at gemme direkte som et simpelt symbol. Her vil det være nemmere at definere den som en Python-funktion:
def g(x):
if x < 0:
return -x
else:
return exp(x)
Dog kan vi med Sympy-biblioteket ved hånden undgå Python-funktionsdefinitioner i dette specifikke tilfælde ved i stedet at anvende den belejlige kommando:
g_udtryk = Piecewise((-x, x < 0), (exp(x), x >= 0))
g_udtryk
der evalueres som forventet:
g_udtryk.subs(x, -2)
Stykkevist definerede funktioner, der er lagret på denne måde, plottes ligesom alle andre funktioner:
dtuplot.plot(g_udtryk,(x,-5,2), ylabel='g(x)')
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad7ab90bd0>
Grafen antyder umiddelbart en diskontinuitet (et “hop” indenfor definitionsmængden) i \(x = 0\). Husk dog på, at selvom man bemærker sådanne ting om en graf på en illustration, så er det blot en indikation; vi kan ikke drage en endelig konklusion udelukkende baseret på, hvad vi ser på et plot, da vi nemt snydes grundet zoom-niveau, kamera-orientering og andet. Desværre giver Python/CAS-værktøjer ikke mulighed for direkte at bevise eller modbevise kontinuitet for os. Sympy vil for eksempel gerne differentiere en funktion i et punkt, selvom funktionen slet ikke er differentiabel i det punkt:
g_udtryk.diff(x)
Når kontinuitet og differentiabilitet skal bevises, er den manuelle vej den sikre vej. Så, modbevis egenskaben ved at finde et modeksempel eller bevis den vha. et epsilon-delta-argument og tilsvarende.
Partielt afledte med diff#
Vi vil nu introducere partielt afledte for funktioner af flere variable. Betragt funktionen:
x, y = symbols('x y')
f = x*y**2+x
f
Vi kan differentiere den ved brug af diff-kommandoen, men da vi har med en funktion af to variable at gøre, skal vi vælge, med hensyn til hvilken variabel vi gerne vil differentiere:
f.diff(x), f.diff(y)
Disse kaldes funktionens partielt afledte, \(\frac{\partial f}{\partial x}\) og \(\frac{\partial f}{\partial y}\). Hver partielt afledte kan selvfølgelig differentieres endnu en gang med hensyn til hver variabel, hvilket vil give de andenordens partielt afledte. Funktionen herover af to variable har fire sådanne andenordens partielt afledte:
f.diff(x).diff(x), f.diff(x).diff(y), f.diff(y).diff(x), f.diff(y).diff(y)
som hurtigere kan findes ved:
f.diff(x,2), f.diff(y,2), f.diff(x,y), f.diff(y,x)
Vi kan indsætte værdier for \(x\) og \(y\) og udregne fx \(\frac{\partial}{\partial x}f(-2,3)\):
f.diff(x).subs({x:-2,y:3})
eller \(\frac{\partial^2}{\partial y\partial x}f(5,-13)\):
f.diff(x,y).subs({x:5,y:-13})
Plots#
Styring af orientering#
Vi vil nu se på, hvordan vi plotter funktioner af to variable, altså plots i 3D! (Bemærk, at det ikke er muligt at plotte grafer for funktioner af mere end to variable, da det jo ville kræve mere end tre akser.) Det er nemt at plotte grafer i 3D, og vi kan endda vælge, hvilken vinkel vi ønsker at se plottet fra. Som standard vælger dtuplot en ofte passende vinkel, men når vi skal inspicere grafen fra andre retninger, kan camera-argumentet bruges. Prøv at ændre på værdierne af elev og azim i følgende plotkommando:
f = 4-x**2-y**2
p=dtuplot.plot3d(f, (x,-3,3),(y,-3,3), camera = {"elev": 25, "azim": 45})
Interaktive plots#
Plottet herover blev genereret som en statisk PNG-fil, hvilket er perfekt til print og eksport af Notebook’en til PDF, samt til hvis du vil kopiere plottet som et billede over til en projektrapport eller lignende. Alle plots bliver statiske, hvis vi ikke gør noget, samt hvis vi bruger kommandoen %matplotlib inline.
Hvis vi i stedet kører kommandoen %matplotlib qt (som i følgende celle er udkommenteret; prøv at fjerne # og køre cellen), så slår vi interaktive plots til. Alle efterfølgende plots vil derfor “poppe ud” af vores VS Code-program og vises i eget, separat vindue, hvori de kan roteres, når der trykkes-og-trækkes med musemarkøren.
# %matplotlib qt
Når denne kommando benyttes, ændres matplotlib’s backend til Qt, hvilket påvirker hele Jupyter-kernen. Med andre ord vil alle plots fremover åbne i separate vinduer fremfor at blive vist direkte på Jupyter Notebook-siden. Dette påvirker alle senere plots i samme session, også i andre Jupyter Notebooks der benytter samme kerne. Det er dog ikke permanent og nulstilles, når kernen genstartes. For at skifte tilbage til standard (inline)plots uden genstart af kernen kan man køre kommandoen: %matplotlib inline.
Bemærk, at %matplotlib qt kun virker, når Python køres på din egen computer. Det vil fx ikke virke, hvis du kører Python på en online server som Google Colab. I sådanne tilfælde skal man i stedet benytte en widget så som %matplotlib ipympl. Dette vil dog kræve installation af en ekstra pakke, i dette tilfælde ipympl.
Overblik over kommandoer:
# %matplotlib inline # For statiske plots
# %matplotlib qt # QT (cute) for interaktive "pop-ud"-plots
# %matplotlib ipympl # Widget/ipynpl for interaktive inline-plots (ikke ligeså stabilt som QT; kræver måske genstart af kernen)
# %matplotlib --list # Liste med alle backends
Æstetik#
Man kan ændre på udseendet af plots genereret med dtuplot ved at angive renderings-keywords med rendering_kw={...}. Med disse kan man angive indstillinger for farve med color, gennemsigtighed med alpha og mere:
dtuplot.plot3d(f, (x,-3,3),(y,-3,3), wireframe = True, rendering_kw = {"color": "red", "alpha": 0.5})
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad7a817d90>
Nogle æstetiske valg er særlige nok til at fortjene deres eget argument udenfor rendering_kw så som wireframe, der blev benyttet i ovenstående plot, samt use_cm, der aktiverer et såkaldt color map i følgende plot:
p=dtuplot.plot3d(f, (x,-3,3),(y,-3,3), use_cm=True, legend=True)
Niveaumængder#
I arbejde med funktioner af to eller tre variable vil man ofte foretrække at visualisere dem med et konturplot (dvs. et plot over udvalgte niveaumængder) som et alternativ til et plot af deres graf. Bemærk den dimensionelle forskel: For at plotte en (skalar)funktions graf kræves en ekstra akse udover akserne til hver variabel, hvilket ikke er tilfældet på konturplots over niveaumængder. En funktion af to variable vil fx have et konturplot i 2D, mens dens graf vil skulle tegnes i et 3D plot:
dtuplot.plot_contour(f, (x,-3,3),(y,-3,3), is_filled=False)
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad7a113410>
Man kan manuelt angive de specifikke funktionsværdier, man ønsker niveaukurver for (husk dog, at et konturplot kun visualiserer “stejlhed” korrekt, hvis niveauerne er jævnt fordelt):
z_niveauer = [-2,-1,0,1]
dtuplot.plot_contour(f, (x,-3,3),(y,-3,3), rendering_kw={"levels":z_niveauer, "alpha":0.5}, is_filled=False)
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad7803fd50>
Gradienter og gradientvektorfelter#
Betragt funktionen \(f:\mathbb R^2\to\mathbb R\):
Dens graf kan visualiseres som en to-dimensionel flade i et 3D koordinatsystem:
f = cos(x)+sin(y)
p = dtuplot.plot3d(f, (x,-pi/2,3/2*pi),(y,0,2*pi),use_cm=True, camera={"elev":45, "azim":-65}, legend=True)
Gradienten af \(f\) i et punkt \((x,y)\) er en vektor, som vi symboliserer med nabla-symbolet: \(\nabla f(x,y)\). Den konstrueres fra de to partielt afledte af \(f\) som følger:
nf = Matrix([f.diff(x), f.diff(y)])
nf
Gradienten kan også beregnes med dtutools.gradient (selvom det bør nævnes, at vi med denne kommando ikke altid har magt over i hvilken rækkefølgen, de variable tages).
dtutools.gradient(f)
Oventående gradientudtryk associerer en vektor med ethvert punkt, hvilket betyder, at vi kan tale om gradienten som en vektorfunktion \(\nabla f:\mathbb R^2\to \mathbb R^2\), der udtrykker en mængde af vektorer fordelt over hele \((x,y)\)-planen. Dette kaldes et vektorfelt, her specifikt et gradientvektorfelt. Det plottes som følger:
dtuplot.plot_vector(nf, (x,-pi/2,3/2*pi),(y,0,2*pi),scalar=False)
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad734026d0>
eller, hvis vi gør os umage (bemærk, at vi her har separeret nogle keywords, så pile og konturer styles hver for sig, hvilket giver bedre kontrol):
dtuplot.plot_vector(nf, (x,-pi/2,3/2*pi),(y,0,2*pi),
quiver_kw={"color":"black"},
contour_kw={"cmap": "Blues_r", "levels": 20},
grid=False, xlabel="x", ylabel="y",n=15)
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7fad734278d0>