SymPy-demo 0: Velkommen til Sympy og Jupyter Notebooks#
Demo af Karl Johan Måstrup Kristensen og Magnus Troen. Revideret 22-10-26 af shsp.
Denne demo vil introducere dig til to værktøjer, der vil lade dig anvende Python som et egentligt CAS-værktøj:
SymPy
Jupyter Notebook
SymPy er en Python-pakke, der muliggør brugen af matematiske symboler, ligningsløsning og mere. Jupyter Notebooks er et værktøj til at skrive Python-kode og tekst i samme dokument til professionel præsentation.
Jupyter Notebook#
Installation#
Følg DTU’s Python installationsguide via https://pythonsupport.dtu.dk/index.html for at installere alle relevante værktøjer inkl. Jupyter Notebook. En Jupyter Notebook-fil har filtypenavnet .ipynb.
Disse SymPy-demoer er skrevet som Jupyter Notebooks. Download denne side via ikonet i toppen, og følg resten af demoen i din egen editor.
Code vs markdown#
For at bygge bro mellem matematisk output fra software og det tilhørende matematiske ræsonnement tillader Jupyter Notebook, at eksekverbar kode og formateret tekst sammenblandes i ét dokument. Et Jupyter Notebook-dokument opbygges af celler: python-celler til kode og markdown-celler til tekst.
Til tekst giver markdown-syntaks mulighed for overskrifter med et antal #, fed skrift med **...**, kursiv med *...* og mere - se en komplet oversigt i den officielle markdown-syntaks guide. Desuden renderes LaTex-kode skrevet med $...$ eller $$...$$, hvor et LaTeX-udtryk som $c=\sqrt{a^2+b^2}$ vises smukt som: \(c=\sqrt{a^2+b^2}\).
Vi vil ikke gå yderligere i dybden med markdown og Latex-syntaks i disse demoer.
Eksekvering og genvejstaster#
Genvejstasterne, der præsenteres i disse demoer, hører til kodeeditoren Visual Studio Code (VS Code).
Eksekvér (kør) en celle med genvejstasterne
Ctrl+Enter, ellerkør og spring til næste celle med
Shift+Enter(er der ingen næste celle, opretter Jupyter Notebook en).
Afprøv dette ved at skrive og køre følgende kode i en ny python-celle i en Jupyter Notebook-fil:
print("Søreme så, de har jo ret")
Søreme så, de har jo ret
Bemærk, at ikke kun python-celler, men også markdown-celler skal “eksekveres”, for at markdown renderes.
Tast
Escfor at forlade redigeringstilstand i en celle uden eksekvering.Tast
Entereller dobbeltklik for at genåbne redigeringstilstand.
Når du ikke er i redigeringstilstand, kan du taste
mfor at konvertere en Python-celle til en markdown-celle,yfor at konvertere en markdown-celle til en Python-celle ellerDelfor at slette cellen.
Jupyter Notebook viser automatisk værdien af den sidste linje i en python-celle.
2+2
5+4+3+2+1
2+3+4
9
Flere komma-adskilte værdier vises som et tupel.
2+3+4, 4**2, 5-6
(9, 16, -1)
SymPy#
Vi skal nu til at hente og arbejde med Python-pakken SymPy, hvis dokumentation kan findes her: https://docs.sympy.org/latest/reference/index.html. Slå op i dokumentationen ved at taste ? efter objekt- eller kommandonavnet (her udkommenteret med #, da pakken ikke er hentet endnu):
# diff?
Pakkeimport#
Din Python-installation kommer typisk med adskillige forudinstallerede Python-pakker, som kan hentes (importeres), når de skal bruges. To måder at importere pakker på er:
import cmath
import numpy as np
Indholdet af disse pakker kan nu tilgås ved at taste pakkenavnet (eller den valgte forkortelse) som præfiks: cmath.polar() eller np.array().
En alternativ importmetode, der lader os bruge indholdet uden præfiks, er:
from sympy import *
init_printing()
from sympy import * importerer alt fra SymPy-pakken. Ved risiko for pakkekonflikter kan det specificeres, hvilket indhold man ønsker: from sympy import sqrt. Kommandoen init_printing() tvinger SymPy til at udskrive matematisk output fra Python-celler ved hjælp af flot LaTeX.
pi**3 + 3 * pi**2 + pi, sqrt(2)
Eksakte tal#
Python lagrer værdier numerisk, altså som et endeligt antal \(0\)’er og \(1\)’er i hukommelsen. Dette kan føre til uventede resultater. Fx kan vi alle blive enige om, at
Men Python er ikke enig:
0.3-(0.2+0.1)
Pythons output er ikke nøjagtig \(0\). Som konsekvens heraf vil fx en logisk sammenligning med den logiske operator == fortælle
0 == 0.3-(0.2+0.1)
False
Sådanne problemer undgås ved at lade SymPy håndtere talværdier som eksakte rationelle tal. Udtrykkes tallene som brøker, kan syntaks så som S(tal), Rational(tæller, nævner) eller S('brøk') benyttes. SymPy holder herefter tallene på rationel form (som er eksakt i modsætning til den approksimerede numeriske form).
Rational(3,10)-(S(2)/10 + S('1/10'))
Objektet S kaldes en singleton. Lær mere om singletons i SymPy-dokumentationen.
Matematiske symboler og basale operatorer#
Python tillader ikke symbolsk matematik. Vi kan tildele værdier til symboler med y=5, men y=5+2*x giver en fejl, da x ikke er defineret.
Symboler og aritmetik#
Her ser vi styrken ved SymPy! Med SymPy kan vi definere vores variabel som et symbol, hvorefter symbolet kan benyttes frit.
x = symbols('x')
y=2*x+3
y
Når man løser ligninger, reducerer udtryk osv., skal alle involverede symboler defineres, hvilket kan gøres på én gang - bemærk hvordan særlige symboler, der hentes med større ord, kan defineres til et simplere symbol.
x,y,z,l,p,t = symbols('x y z lambda phi theta')
x,y,z,l,p,t
SymPy kommer med en masse matematiske operationer, så som kvadratroden sqrt(), den naturlige eksponentialfunktion exp() og fakultetsfunktionen factorial(). Bemærk, at der af og til dukker usædvanlig notation op i Python, som det er tilfældet med potenser \(x^y\), der skrives som x**y, og ikke som x^y.
sqrt(x), exp(x), factorial(x), x**y, sqrt(16), exp(2), factorial(4), 2**3
Benyt .evalf() eller N(...) til at tvinge et eksakt resultat ned på numerisk decimalform, hvor man med .evalf(antal-betydende-cifre) kan styre, hvor mange betydende cifre man ønsker at se.
exp(2).evalf(), exp(2).evalf(5), N(exp(2))
Eulers naturlige tal er desuden defineret som E, så man kan også skrive:
exp(x), E**x
Antagelser#
Som standard behandles definerede symboler som komplekse tal (altså, uden antagelser). Hvis dit symbol repræsenterer et tal, der er reelt, ikke er nul eller tilsvarende, så kan det angives som en antagelse, hvor symbolet defineres.
# a er en reel variabel
a = symbols('a', real = True)
# b kan ikke være 0
b = symbols('b', nonzero = True)
# c er en reel variabel, der ikke kan være 0
c = symbols('c', real = True, nonzero = True)
Antagelser lader SymPy eksekvere hurtigere og reducere yderligere, og af og til er de ligefrem afgørende for overhovedet at få et brugbart output, som i følgende eksempel.
Eksempel på symboler med og uden antagelser#
Lad os definere udtrykket
x1,x2 = symbols('x1,x2')
udtryk_x = sqrt(x1**2 + x2**2)/sqrt(abs(x1**2 + x2**2))
udtryk_x
Det ser umiddelbart fint ud, men bør det ikke kunne reduceres? Du prøver med .simplify(), men ser ingen forskel:
udtryk_x.simplify()
Så kommer du i tanke om, at du jo har glemt antagelsen om reelle variable! Du retter til:
x1,x2 = symbols('x1,x2', real = True)
udtryk_y = sqrt(x1**2 + x2**2)/sqrt(abs(x1**2 + x2**2))
udtryk_y
Det var en betydelig reduktion. Husk at sætte antagelser fremover, når symboler defineres.
Ligninger#
= tildeler værdier til symboler. == udfører logisk sammenligning. Med ligmedtegnet reserveret til dette, hvordan kan vi så opskrive ligninger?
Det gøres med SymPys kommando Eq(venstresiden,højresiden), hvori venstresiden og højresiden adskilles af komma. Fx skrives ligningen \(x^2=\frac13 y\) som Eq(x**2, S('1/3')*y).
x,y = symbols('x,y')
lign = Eq(x**2, y*S('1/3'))
lign
Bemærk: Får du uformaterede outputs i arbejdet med disse større udtryk, så har du formentlig glemt at køre init_printing().
Løs ligninger#
Til løsning af ligninger tilbyder SymPy kommandoen solveset(ligning, variabel).
losn = solveset(lign, x)
losn
Outputtet fra solveset() er alle løsninger i en mængde. Da en mængde ingen rækkefølge har, skal den først konverteres om til en liste, før enkelte løsninger deri kan tilgås:
liste_af_løsninger = list(losn)
liste_af_løsninger[0], liste_af_løsninger[1]
Bemærk, at Python bruger 0-indeksering (så den første i listen har indeks 0 og hentes med ...[0], den anden har indeks 1 osv.).
Et alternativ kunne være at løbe alt indholdet i mængden igennem med en løkke:
for losning in losn:
display(losning)
Der findes andre ligningsløserkommandoer, fx solve(). Fordelen ved solveset() er, at den giver alle løsninger, selv når der er uendeligt mange. Løs fx ligningen \(\sin(x) = 0\).
lign = Eq(sin(x), 0)
solveset(lign, x)
solve() giver kun nogle få af disse løsninger.
solve(lign, x)
Afgrænsning af løsninger#
solveset() giver som standard alle løsninger i \(\mathbb{C}\). Dette kan afgrænses ved angivelse af ønsket definitionsmængde: solveset(ligning, variabel, definitionsmængde). Som et eksempel har ligningen
x = symbols('x')
lign = Eq(exp(x), 1)
lign
uendeligt mange løsninger, når \(x\in \mathbb{C}\),
solveset(lign, x)
men kun en enkelt løsning, når \(x\in\mathbb{R}\),
solveset(lign, x, S.Reals)
Typiske definitionsmængder kan angives med bl.a. følgende:
S.RealsS.ComplexesInterval(0, 1)
Matematiske funktioner#
Benyt en standard Python-funktion#
Pythons indbyggede funktionssyntaks kan udnyttes til opskrivning af matematiske funktioner.
# Standard Python-funktion
def f(x):
return x**2
x = symbols('x')
f(x)
Med denne definition fås funktionsværdier med helt sædvanlig matematisk notation:
f(2), f(10)
Mens Python-funktioner fint kan håndtere SymPy-objekter, så kan de være problematiske sammen med SymPy-funktioner, fx når der differentieres med .diff().
Benyt substitution#
Et alternativ er derfor at definere matematiske funktioner som simple symboler.
x = symbols('x')
# Matematisk funktion
fx = x**2
fx
Dog kan vi nu ikke skrive fx(2) for at finde funktionsværdier, men det klarer vi blot med .subs(variabel,værdi), der erstatter \(x\) med en værdi:
# Substituterer x = 2
fx.subs(x, 2)
.subs() kan også håndtere dictionaries med syntaksen {variabel1:værdi1, variabel2:værdi2, ...} eller dict(variabel1=værdi1, variabel2=værdi2, ...), hvis der skal indsættes flere værdier på én gang.
x,y = symbols('x,y')
# Funktion af to variable
fxy = x*y
# Indsætter x=1, y=2
fxy.subs({x: 1, y: 2}), fxy.subs(dict(x = 1, y = 2))
Matematiske funktioner defineret på denne måde kan differentieres med diff(funktion, variabel, orden).
diff(fx, x, 1), diff(fx, x, 2) # den første- og andenafledte
diff(fxy, y,1), diff(fxy,x,1) # partielt afledte
Komplekse tal#
I SymPy er den imaginære enhed defineret som I.
# Med S(1)/2 vises 1/2 som brøk fremfor som 0.5
z = S(1)/2 + I * sqrt(3)/2
z
SymPy indeholder adskillige værktøjer til arbejde med komplekse tal, bl.a. følgende - bemærk, at modulus findes med abs().
z.as_real_imag(), abs(z), arg(z), conjugate(z)
Husk at prøve med simplify(), hvis et udtryk ikke reduceres fuldt af sig selv.
z/(5*z)
simplify(z/(5*z))
Skift imellem komplekse former#
Skal et komplekst tal omskrives til polær form, gøres det nemmest manuelt.
z_pol = abs(z) * exp(arg(z) * I)
z_pol
I nogle tilfælde, men ikke altid, kan .rewrite(exp) benyttes:
t = symbols('t')
w = cos(pi/3 * t) + I*sin(pi/3 * t)
w
w.rewrite(exp)
En omskrivning til rektangulær form kan gøres med .rewrite(sin) eller .rewrite(cos).
z_rekt = z_pol.rewrite(sin)
z_rekt
Plots, et kort indblik#
Et komplet overblik over SymPys plotmuligheder findes i SymPy-dokumentationen: https://docs.sympy.org/latest/modules/plotting.html.
Bemærk, at SymPy tegner sine plots vha. Matplotlib-pakken. SymPy importerer pakken selv, så du ikke behøver at gøre det manuelt. Hvis den ikke er installeret og derfor ikke kan hentes, vil dine plots ligne grove tekstbaserede skitser fremfor figurer. I så fald skal du installere Matplotlib-pakken, fx ved at køre pip install matplotlib i en terminal.
Lad os definere nogle symboler til brug i nedenstående eksempler.
x,y,t = symbols('x,y,t')
Plot funktion af én variabel#
plot()-kommandoen plotter funktioner af én variabel. Der kan angives et væld af parametre for at tilpasse dit plot.
funk = 3*x**2
plot(funk, (x, -5, 10), ylim = [0,100], xlabel = 'x', ylabel = '$3x^2$');
Bemærk, at definitionsmængden, der ønskes plottet over, angives som en parentes (variabel, fra, til) straks efter funktionen, der plottes.
Bemærk også, at plot()-linjen afsluttes med ;. Uden dette vises en tekstlinje med objektinformation under plottet, hvilket sjældent er relevant.
plot() tillader også plotting af flere udtryk i samme omgang.
udtryk1 = exp(-x/2)*cos(3*x)
udtryk2 = exp(-x/2)
udtryk3 = -exp(-x/2)
plot(udtryk1, udtryk2, udtryk3, (x, 0, 10),
legend=True,
title='Dæmpet svingning',
xlabel='x',
ylabel='u(x)'
);
Bemærk, hvordan linjeskift inde i den ellers lange kodelinje kan give bedre overblik.
Plot ligninger og uligheder#
Med plot_implicit() plottes ligninger og uligheder. Først en ligning:
lign = Eq((x - 1)**2 + (y+1)**2, 10)
plot_implicit(lign, (x,-5, 5), (y,-5, 5), aspect="equal");
Og her en ulighed:
plot_implicit(x**2 + y**2 <= 4, (x,-3,3), (y,-3,3), aspect="equal");
Plot parameterfremstillinger (parametriseringer)#
Kommandoen plot_parametric() plotter parameterfremstillinger i to dimensioner.
parameterfrem = Matrix([t*cos(t), sin(t)])
plot_parametric(*parameterfrem, (t,0,2*pi));
Plot i 3D#
3-dimensionelle plots kan laves med plot3d(), men denne kommando er ikke inkluderet i SymPy-importen. Den skal derimod importeres fra sympy.plotting.
from sympy.plotting import plot3d
plot3d(x**2 - y**2, (x,-2,2), (y,-2,2));
sympy.plotting er en underpakke i sympy-pakken, så du har den allerede installeret sammen med SymPy. Kodelinjen from sympy import * importerer de mest almindelige kommandoer samt nogle fra underpakker (så som plot()) men ikke alt. Resten skal importeres fra relevante underpakker, en adskillelse der er gjort for at undgå konflikter og rod. Heldigvis er intet indhold i sympy.plotting i konflikt med noget topniveau-indhold i sympy, så fremover kan du uden problemer importere alt fra begge med * i starten af et dokument.
from sympy import *
from sympy.plotting import *
init_printing()