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, eller

  • kø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 Esc for at forlade redigeringstilstand i en celle uden eksekvering.

  • Tast Enter eller dobbeltklik for at genåbne redigeringstilstand.

Når du ikke er i redigeringstilstand, kan du taste

  • m for at konvertere en Python-celle til en markdown-celle,

  • y for at konvertere en markdown-celle til en Python-celle eller

  • Del for 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)
../_images/1113dae1d4e144df6f6433cf2315273bb07db92cc78e18abeff2a03636e5c231.png

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

\[ 0.3-(0.2+0.1) = 0. \]

Men Python er ikke enig:

0.3-(0.2+0.1)
../_images/74d2ea4ae6251692e6558a2b6961e4939990fbedbf8c8899631926cf6a2dd1a8.png

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'))
../_images/651416cddb7ade65e68d31c70e7a3dbe2ef01977dfec97a06aaaa159fde31487.png

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
../_images/d948adcec2587d27a6cdb8f0fde0c5e35ec2ec9da8e7accb0a57884c86cd979a.png

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
../_images/d5a01ff6ea2dfe866a219636f1dd6fc9b771bebf8c2970a414da1669964ee4dd.png

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
../_images/627c0ebade249f5b4b3d7c4e00f9afe29874cee52803b858afe5b610436b88d7.png

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))
../_images/c537655e98f6050940dd7aaf65f6e46c393484c09f41158e1fe2c612115063b5.png

Eulers naturlige tal er desuden defineret som E, så man kan også skrive:

exp(x), E**x
../_images/063c6916f5f8a675d6a706df718ffe588536b83ee31f24486c52d6d9520d1cb6.png

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

\[\displaystyle\frac{\sqrt{x_1^2 + x_2^2}}{\sqrt{|x_1^2 + x_2^2|}},\quad x_1,x_2 \in \mathbb{R}.\]
x1,x2 = symbols('x1,x2')

udtryk_x = sqrt(x1**2 + x2**2)/sqrt(abs(x1**2 + x2**2))
udtryk_x
../_images/75d943d5ebdc24c9873aafa10726e283b2fd5f9e4625add8ebd668f9e4127bb5.png

Det ser umiddelbart fint ud, men bør det ikke kunne reduceres? Du prøver med .simplify(), men ser ingen forskel:

udtryk_x.simplify()
../_images/a033927be437021a776c05fd7b388f570370542439b66e78b7de10bdd6e2117b.png

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
../_images/8afd07bb98dcdae0c434e74411ac4bbaa5cc7f81f2576de6208431b0c1a461ad.png

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
../_images/0ae433145df50de380e453a822cc9c6cb86c283f86b7cf77d80ff4add009a0f6.png

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
../_images/7f40eb779b95cb07b6d26063430edde16ffb07c64e3a501efd598e158f1427ef.png

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]
../_images/db3d11f5a903cc1873875cd9f9f747753787fc2768629729cc8fef9c7a68d07e.png

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)
../_images/a5c75b701ec1351933cf8528459679f11c3d47610e276081170c477644713cc5.png ../_images/d17ea7c280c2d1a7fe3c016e1fc2135cf20b5069c8f3199255ef3b62dc2e0241.png

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)
../_images/c857dfba2ec5eac40592004428eab6a21454b0843dca710cf16ba15abf224299.png

solve() giver kun nogle få af disse løsninger.

solve(lign, x)
../_images/d9efad5cba5d17e0dec9c89c28ef2ca91ba33f32296804c7366f73f153e9a1cc.png

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
../_images/ed27a4ad03030fd9191f68483482a9c13bd3e63cae84e13905da2ad1ae92b730.png

uendeligt mange løsninger, når \(x\in \mathbb{C}\),

solveset(lign, x)
../_images/e0e4a1b6c69a31bfaf746569cabf82e2f2bfc1b2077607bb579208c307d4952b.png

men kun en enkelt løsning, når \(x\in\mathbb{R}\),

solveset(lign, x, S.Reals)
../_images/bd2382c71536f82a1be6ed3427595c10e4bdb84861106aeb440092fdb1fada12.png

Typiske definitionsmængder kan angives med bl.a. følgende:

  • S.Reals

  • S.Complexes

  • Interval(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)
../_images/d361e591288f3fa0c5d5afc9439df90f6e2156993391def041fad2f1e91f8c93.png

Med denne definition fås funktionsværdier med helt sædvanlig matematisk notation:

f(2), f(10)
../_images/2c50bbca15085a70ba94cfd2aa24ca7765b7c4abec1c9ef63e3303c3f16ac298.png

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
../_images/d361e591288f3fa0c5d5afc9439df90f6e2156993391def041fad2f1e91f8c93.png

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)
../_images/5b6183f48aaf4a849095dd50f6b294d716bb05269f903a5807b204e3d987bb6f.png

.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))
../_images/5b5fa32d1a84db625031fc8c221dd5e5308ecf9ba77ae5a48be82368ea07b1b9.png

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
../_images/673f31ed926ddcfbb7288dbe5e5c6119f89bd2ef5e76a9e646b4605aa3f7c36c.png
diff(fxy, y,1), diff(fxy,x,1) # partielt afledte
../_images/22d58c2e477c3c63f6fcb6dc2afe6fb9e65586b8fa70914956474812a41ced8a.png

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
../_images/c6ad3be32de23d51825ff1222da965f02a1525109e747489a707c1fda870f86d.png

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)
../_images/b0f5b32b6581b163f651cc2360778c2226ab14cbab02f498ec3e52d0ad13b43e.png

Husk at prøve med simplify(), hvis et udtryk ikke reduceres fuldt af sig selv.

z/(5*z)
../_images/1f5d8ca90f6302c20e16b0e2678843418fb45483286b9a656f4fd9696615fdf7.png
simplify(z/(5*z))
../_images/527bf242ea0b2c340f3a8805090562a43927e18bf2a52f6644707fda8c6d4af6.png

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
../_images/85d8b44220a18e9ec40d39bd428577877bbc0c0699cbbdc4614113896107d7f7.png

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
../_images/4dc1a92f514e360fcec5039c23e7f1cd97feb7317d9f5face2466328ab98861b.png
w.rewrite(exp)
../_images/ab0a8c77ec28899eda404d88d60298b2daa0c14ccd6537acd42e1d329bd08f2a.png

En omskrivning til rektangulær form kan gøres med .rewrite(sin) eller .rewrite(cos).

z_rekt = z_pol.rewrite(sin)
z_rekt
../_images/c6ad3be32de23d51825ff1222da965f02a1525109e747489a707c1fda870f86d.png

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$');
../_images/fdd368fdfba3d84c406d690b4f79e9f0fb9c86d9dfa486480f181037dec1a777.png

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)'
     );
../_images/57e0ad509680fd7676e64fc10127860b00b8f20e721fb2d36c6ea1a603ca434c.png

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");
../_images/f97714c811fc0854eff3f0a77a436e850018c82c4dd990f534500adb7b04f1dd.png

Og her en ulighed:

plot_implicit(x**2 + y**2 <= 4, (x,-3,3), (y,-3,3), aspect="equal");
../_images/ceeef820406a296c2ab77765d50a0aac5203d6e229362571a16707382222bc45.png

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));
../_images/6ff98f9edd724ee26d781068b006e7d13043321e11da0bddcc889ba6edf0c86d.png

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));
../_images/73c8bde747e2809046c5338285de2f57ca4c8c553eb780a4d470bddee6efe69f.png

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()