5.1. Hvad er SymPy?#

I denne sektion vil vi kigge på, hvordan vi benytter SymPy. Selve SymPy er en pakke i Python, der lader os arbejde med symbolske udtryk. Vi starter med at importere SymPy på samme måde. Derfor vil vi starte alle notebooks med at importere SymPy og benytte forkortelsen “sp” for pakken: import sympy as sp.
SymPy har flere indbyggeret printe-værktøjer, der gør det muligt at vise symbolske udtryk i LaTeX, som er et typesetting-system der er særligt velegnet til matematisk notation (du kender måske LaTeX fra Words Wordmat-plugin). Den normale print() i Python skriver output i “string”-format, mens funktionen display() (som er indbygget i Jupyter) vil være vores foretrukne valg til at vise SymPy-udtryk som LaTeX. Når vi skriver et udtryk til sidst i en celle, vil Jupyter oftest skrive resultatet og vil automatisk bruge display.

import sympy as sp                    # Importer SymPy

5.1.1. Symboler og tal#

På samme måde som vi kan have variable i Python som strings, logiske booleans eller talværdier, så tilføjer SymPy muligheden for symbolske variable, svarende til hvad vi f.eks. kalder en ubekendt i ligningsløsning eller den (uafhængige) variabel i en funktion. Vi ønsker således at have muligheden for angive en variabel som \(x\) i \(f(x) = x^2+3x-2\) og opfatte den som et abstrakt objekt i modsætning til at tildele \(x\) en værdi. Den nemmeste måde at definere symboler på er at importere dem fra underbiblioteket sympy.abc, som indeholder de fleste symboler, som vi til dagligt bruger. Vi kan definere f.eks. \(x, a, b\) og \(\phi\) som symboler ved at skrive:

from sympy.abc import x, a, b, phi

Nu kan vi benytte disse variable i beregninger:

a + b + phi # Vi kan lægge dem sammen
\[\displaystyle a + b + \phi\]

Vi kan også danne nye udtryk med symbolske værdier. Udtrykkene kan sættes sammen ved at benytte normale Python-operationer såsom: +, -, *,/ eller **. Derudover kan vi benytte en del andre regneoperationer ved at skrive sp. foran operationen. Her er samlet de typiske regneoperationer, som man kan finde i SymPy

  • Kvadratrødder: sp.sqrt(x) (benyt sp.root(x, n) til at beregne \(\sqrt[n]{x}\), altså den n’te rod af x)

  • Trigonometriske funktioner: sp.cos(x), sp.sin(x), sp.tan(x). De inverse trigonometriske funktioner findes ved eksempelvis acos(x)

  • Exponentialfunktion sp.exp(x)

  • Logaritmer: sp.log(x). For at få 10-tals-logaritmefunktionen skrives log(x, 10)

En mere omfattende liste over regneoperationer kan findes her.

Så eksempelvis kan vi sammensætte et udtryk ved at benytte exponentialfunktionen sp.exp sammen med vores symboler:

a * sp.exp(2*x + phi)
\[\displaystyle a e^{\phi + 2 x}\]

Vi kan også danne nye symbolske variable ud fra eksisterende variable. Vi kan f.eks. definere en funktion \(f\) baseret på tal og eksisterende symbolske variable:

f = a * sp.exp(2*x + phi)

Bemærk at vi ikke behøver at definere \(f\) som symbolsk variabel. Python/SymPy kan godt regne ud at f bliver en symbolsk variabel idet den er opbygget af andre symbolske variable. Resultatet kan vises med display()-funktionen:

display(f)
\[\displaystyle a e^{\phi + 2 x}\]

5.1.2. Eksakt repræsentation af tal#

Python opfatter / som en numerisk operation, og når vi vil have eksakte tal-brøker, må vi eksplicit bede SymPy om at opfatte dem som sådan ved hjælp af Rational(a, b)

brøk = sp.Rational(1, 3)
display(brøk)
\[\displaystyle \frac{1}{3}\]

En brøk bestående bestående af symboler lider ikke under samme problem, så der kan vi bare bruge almindelig division.

c = a/b
display(c)
\[\displaystyle \frac{a}{b}\]

Vi kan regne med brøkerne ved hjælp af de almindelige regnearter:

p = sp.Rational(1, 3)
q = sp.Rational(2, 5)
display(p - q)

p = sp.Rational(1, 3)
q = sp.Rational(2, 5)
display(p*a/b - q)
\[\displaystyle - \frac{1}{15}\]
\[\displaystyle \frac{a}{3 b} - \frac{2}{5}\]

Vi får ofte brug for eksakte værdier af \(\pi\) og evt. andre særlige tal. En eksakt værdi af \(\pi\) får vi ved at skrive from sympy import pi. Når vi sammensætter symbolske variable og \(\pi\) med tal i brøker, kan vi som nævnt ovenfor godt bruge almindelige division istedet for sp.Rational(tæller, nævner), da Python på grund af symbolerne ikke kan behandle udtrykkene som numerisk repræsenterede decimaltal (floats):

from sympy import pi
value = sp.sqrt(3) * pi / 2
display(value)
\[\displaystyle \frac{\sqrt{3} \pi}{2}\]

Desuden kan vi hente nogle andre brugbare symboler fra SymPy, som eksempelvis uendelig, oo (der skrives som to små o’er og ligner et uendelighedstegn, hvis man har lidt fantasi).

from sympy import oo # Importer værdien uendelig
1 / oo               # 1 divideret med uendelig giver 0, i hvert tilfælde for fysikere :o)
\[\displaystyle 0\]

På samme måde kan vi også importere den imaginære enhed \(i = \sqrt{-1}\), som i SymPy er angivet ved et stort I. Hvis man hellere vil lave numeriske beregninger med komplekse tal, benytter man i stedet j og kan f.eks. skrive 2 + 3j. Hvis du ikke kender til de komplekse tal, så gå ikke i panik. De bliver introduceret i slutningen af blok 1

from sympy import I # Importer I
z = 3 + 3 * I       # definer to tal
q = 1 - 2 * I
display(z, q)       # Vi viser dem med display()
\[\displaystyle 3 + 3 i\]
\[\displaystyle 1 - 2 i\]

Vi vil vende tilbage til imaginære tal i SymPy i en senere notebook.

5.1.3. Evaluer udtryk#

Forestil dig at vi har et symbolsk udtryk, der indeholder den variable \(a\), og vi ønsker at indsætte værdien \(a = 2\). Til dette vil vi nu indføre de to metoder .subs() og .evalf(). For et udtryk \(f\), som eksempelvis kunne være f = a ** 2 + b, kan vi indsætte \(a = 2\) ved at skrive f.subs(a, 2).

Symbolsk substituering:

Vi kunne for eksempel for et funktion \(f(x) = cos(x \cdot \pi / 4)\) ønske at finde værdien af \(f\) for forskellige værdier af x:

# Vi definerer funktionen f:
f = sp.cos(x * pi / 4)
display(f)

# Vi kan nu finde værdien for x = 1 ved at skrive:
display(f.subs(x, 1))
\[\displaystyle \cos{\left(\frac{\pi x}{4} \right)}\]
\[\displaystyle \frac{\sqrt{2}}{2}\]

Vi kan også indsætte et udtryk ind i et andet:

from sympy.abc import x, a, b, c    # Vi har allerede x, a, og b til rådighed fra ovenfor, men skal have c med.
f = a * b * x + x
display(f)

g = b ** 2 + c ** 2
display(g)

f.subs(x, g)
\[\displaystyle a b x + x\]
\[\displaystyle b^{2} + c^{2}\]
\[\displaystyle a b \left(b^{2} + c^{2}\right) + b^{2} + c^{2}\]

Her har vi altså defineret to udtryk og erstattet alle forekomster af x i det første udtryk med \(g = a^2 + b^2\). Vi kunne have opået det samme (nemmere, men mindre generelt) ved at skrive:

g = b ** 2 + c ** 2
f = a * b * g + g

display(f)
\[\displaystyle a b \left(b^{2} + c^{2}\right) + b^{2} + c^{2}\]

Numerisk evaluering:

Når vi har et matematisk udtryk, er det en fordel også at kunne finde en numerisk approksimation. Til at gøre dette benytter vi metoden .evalf(cifre), som giver en approksimation med det angivne antal cifre. Det simpleste eksempel er, hvis vi ønsker at finde det første 10 cifre af pi:

pi.evalf(10)         # evalf(10), giver os de første 10 cifre
\[\displaystyle 3.141592654\]

Vi kan benytte de to metoder i kombination, hvis vi eksempelvis vil finde værdien af et udtryk og så approksimere det.

f = sp.exp(x / 5)
display(f)

f_3 = f.subs(x, 3) # Substituerer x med 3 og gemmer det i en variabel, der hedder f_3.
# Det er sådan vi gerne vil have svaret skrevet i opgaverne i LinALys: så simpelt som muligt men stadig eksakt.
display(f_3)

f_3.evalf(3) # Lad os finde værdien som decimaltal med 3 cifre.
# ... hvilket er praktisk f.eks. hvis vi skulle tegne resultatet ind i en illustration
\[\displaystyle e^{\frac{x}{5}}\]
\[\displaystyle e^{\frac{3}{5}}\]
\[\displaystyle 1.82\]