15 Álgebra Simbólica
Julia permite realizar operaciones y cálculos simbólicos llamando, a través de PyCall, a la librería SymPy de matemática simbólica de Python. El paquete que realiza el puente entre Julia y Python es sympy
. Para utilizar esta funcionalidad el primer paso es instalar dicho paquete:
Para usar el paquete sympy
tanto Python como SymPy deben estar instalados en el computador. Existen dos formas de instalar SymPy. La primera es instalar el paquete PyCall
en Julia mediante Pkg.add("PyCall")
, el mismo que instara los paquetes requeridos de Python vía Conda. La segunda alternativa, es instalar Anaconda en el computador que es un paquete de Python y varias librerias pesadas para su aplicación en las ciencias.
15.1 Declarar variables simbólicas
Las variables simbólicas, a diferencia de las variables típicamente utilizadas en Julia, no son evaluadas inmediatamente. En lugar de ello, se utiliza el “símbolo” para referirse a ellas. Existen dos formas equivalentes de declarar variables simbólicas. La primera es usar las funciones Sym()
o symbols()
, mientras que la segunda usa la macro @vars
o @syms
.
(x, y, z)
(a, b, c)
Al declarar las variables es posible realizar ciertos supuestos respecto de su comportamiento. El formato de declaración se realiza mediante el uso de la opción supuesto = true
de la función symbols
(en este caso no usar Sym
). La declaración también funciona con la macro @vars
. cuando se declara un suspuesto se impone una restricción respecto de los valores que puede tomar una variable. Una lista, no exaustiva per útil, de dichos supuestos es la siguiente: real
, odd
, even
, zero
, nonzero
, positive
, negative
, finite
, prime
, etc. Ver la lista completa de supuestos en el siguiente link. Algunos ejemplos:
(x, y, z)
(a, b, c)
15.2 Expresiones y ecuaciones simbólicas
Asignar expresiones matemáticas
Una vez declaradas las variables es posible combinarlas para crear ecuaciones. Al igual que antes se usa =
para asignar (note que =
en este caso es asignación y no igualdad en referencia a dos lados de una ecuación). Es posible utilizar la operaciones algebráicas +,´,*,\,^
asi como funciones, como sin()
, cos()
, log()
, exp()
entre otras, para crear expresiones.
\[\begin{equation*}2 x + 3 y^{2} + \log{\left(z \right)}\end{equation*}\]
Además es posible crear expresiones a partir de otras expresiones simbólicas.
\[\begin{equation*}x \left(2 x + 3 y^{2} + \log{\left(z \right)}\right)\end{equation*}\]
\[\begin{equation*}e^{x + y}\end{equation*}\]
Sustitución
Sympy
permite substituir variables simbólica en expresiones mediante la función subs(expresion, (subs))
o simplemente evaluando la expresión expresion(subs)
. La substitución se puede realizar entre símbolos, expresiones, o números.
\[\begin{equation*}2 x^{2}\end{equation*}\]
Por ejemplo, substituímos x
por y
en la expresión fx
y luego asignamos la nueva expresión a fy
.
\[\begin{equation*}2 y^{2}\end{equation*}\]
Podemos crear a expresión g = 3*y+z
y substituir toda esta expresión por x
en fx
:
\[\begin{equation*}2 \left(3 y + z\right)^{2}\end{equation*}\]
Alternativamente, para producir el mismo resultados podemos usar simplemente la sintaxis de una función:
\[\begin{equation*}2 \left(3 y + z\right)^{2}\end{equation*}\]
Para sustitur más de una variable en una expresión se usa subs(expresion, (subs1), (subs2),...)
:
\[\begin{equation*}3 z^{4} + 2 z\end{equation*}\]
Alternativamente se puede evaluar la expresión directamente. Es importante para esto tener en mente el orden de las variables. Para determinar dicho orden usar free_symbols(expresion)
.
\[\begin{equation*}3 z^{4} + 2 z\end{equation*}\]
Otra alternativa es usar =>
para asignar, por ejemplo:
\[\begin{equation*}3 z^{4} + 2 z\end{equation*}\]
En cualquier caso, es recomendable definir explícitamente la variable a reemplazar para evitar ambiguedades.
Evaluación numérica
Reemplazar variables por valores numéricos permite evaluar numéricamente la expresión. Por ejemplo:
\[\begin{equation*}16.0\end{equation*}\]
Este número es un objeto del typo sym
(simbólico) y no un tipo numérico con el cual trabajamos usualmente en Julia, como Int64
o Float64
. Esto implica que si pasamos este número por una función que no acepta o no trabaja con objetos tipo sym
obtendremos un error.
Sym
Para transformar la evaluación numérica de sympy
usamos la función N(expresión)
.
16.0
Float64
Ecuaciones
Existen dos formas de declarar ecuaciones. Primero, la función Eq()
permite declarar ecuaciones bajo el formato Eq(lado_izquierdo,lado_derecho)
. Por ejemplo:
\[\begin{equation*}x^{3} + y = 2\end{equation*}\]
Segundo, es posible utilizar operadores tipo unicode para diferenciar la declaración de una ecuación con respecto a la asignación de una variable. Los operadores son: \ll[tab]
(menor), \leqq[tab]
(menor o igual), \Equal[tab]
(igual), \geqq[tab]
(mayor o igual), \gg[tab]
(mayor), y \neg[tab]
(negación). Por ejemplo:
\[\begin{equation*}x^{3} + y = 2\end{equation*}\]
15.3 Manipulando Polinomios
Las funciones expand()
, factor()
, collect()
, y simplify()
La función expand(expresión)
expande un polinomio como la suma de monomios (realizando en el proceso todas las simplificanciones necesarias). Por ejemplo:
\[\begin{equation*}\left(x + y\right)^{2}\end{equation*}\]
\[\begin{equation*}x^{2} + 2 x y + y^{2}\end{equation*}\]
La función factor(expresión)
toma un polinomio y lo factoriza en sus factores irreductibles. La función factor()
es el opuesto de la función expand()
. Por ejemplo tomemos el resultado anterior y apliquemos la función factor()
:
\[\begin{equation*}\left(x + y\right)^{2}\end{equation*}\]
La función collect(expresión)
agrupa las potencias comúnes de un término presentes en una expresión. Por ejemplo, agrupemos los términos comúnes de \(x\) en la expresión \(xy + xy^2 + x^2y + x\):
\[\begin{equation*}x^{2} y + x \left(y^{2} + y + 1\right)\end{equation*}\]
Tomemos ahora los términos comunes de \(y\) de la misma expresión:
\[\begin{equation*}x y^{2} + x + y \left(x^{2} + x\right)\end{equation*}\]
En algunos contextos puede ser útil extraer el coeficiente asociado a un término común. Por ejemplo, en la anterior expresión podemos extraer el coeficiente que acompaña a \(y^2\) usando la notación expresión.coeff(término_común)
:
\[\begin{equation*}x\end{equation*}\]
La función simplify(expresión)
simplifica una expresión reduciendo el número de términos al mínimo posible.
\[\begin{equation*}x \left(x - y\right)\end{equation*}\]
La función apart(expresión)
realiza la descompocición en fracciones parciales de una función racional. Por ejemplo:
\[\begin{equation*}\frac{x^{2} + x}{x^{2} + 2 x + 1}\end{equation*}\]
\[\begin{equation*}1 - \frac{1}{x + 1}\end{equation*}\]
La función together(expresión)
realiza la operación inversa a apart(expresión)
. Por ejemplo:
\[\begin{equation*}\frac{1}{x^{2}} + \frac{1}{x^{3}}\end{equation*}\]
\[\begin{equation*}\frac{x + 1}{x^{3}}\end{equation*}\]
La función cancel(expresión)
toma una función racional y la expresa en su forma canónica estándar \(p/q\), con \(p\) y \(q\) polinomios sin factores comunes. Por ejemplo:
\[\begin{equation*}\frac{x^{3} - 6 x^{2} + 11 x - 6}{x^{2} - 5 x + 4}\end{equation*}\]
\[\begin{equation*}\frac{x^{2} - 5 x + 6}{x - 4}\end{equation*}\]
Tambien es posible realizar operaciones y simplificaciones con potencias. Por ejemplo:
\[\begin{equation*}x^{a} x^{b}\end{equation*}\]
\[\begin{equation*}x^{a + b}\end{equation*}\]
Note que usamos las notaciones sympy.powsimp(expresión)
. Esto se debe a que la función powersim
no ha sido exportada al entorno global en el paquete sympy
, por lo que tenemos que indicarle a Julia que dicha función viene de ese paquete.
15.4 Cálculo
El paquete sympy
es capaz de calcular derivadas, integrales y límites. La función diff(expresión,variable_a_derivar)
toma la derivada de la función declarada en expresión
con respecto a la variable variable_a_derivar
. Si la función tiene un solo argumento, no es necesario declarar la variable a derivar. Por ejemplo:
\[\begin{equation*}- x^{2} \sin{\left(x \right)} + 2 x \cos{\left(x \right)}\end{equation*}\]
Si la función tiene más de una variable, definimos explícitamente la variable con respecto a la cual queremos tomar la derivada parcial. Por ejemplo:
\[\begin{equation*}2 x y + y^{2} + y + 1\end{equation*}\]
\[\begin{equation*}x^{2} + 2 x y + x\end{equation*}\]
Alternativamente, si definimos un vector con todas las variables a derivar, pordemos calcular el vector gradiente de una función:
\[\begin{equation*}\left[\begin{matrix}2 x y + y^{2} + y + 1\\x^{2} + 2 x y + x\end{matrix}\right]\end{equation*}\]
Nota: El tipo de objeto obtenido al calcular el vector gradiente es SymMatrix
que es específico a sympy
. Como la funcionalidad no ha sido trasladada al 100% entre Julia y sympy de Python, la forma de acceder en este caso a los elementos del vector es indexando sus elementos partiendo en 0 (en Python el primer elemento de un vector es el que tiene posición 0):
typeof(grad) = SymMatrix
grad[0] = 2*x*y + y^2 + y + 1
grad[1] = x^2 + 2*x*y + x
Para calcular derivada de orden superior usamos diff(expresión,variable_a_derivar,orden_derivada)
.
\[\begin{equation*}2 y\end{equation*}\]
\[\begin{equation*}0\end{equation*}\]
Para calcular derivadas cruzadas usamos diff(expresión,var_primera_der,var_segunda_der,...)
.
\[\begin{equation*}2 x + 2 y + 1\end{equation*}\]
El paquete sympy
provee funcionalidad para calcular integrales tanto definidas como idefinidas. Para calcular una integral indefinida usamos integrate(expresión,variable_a_integrar)
. Por ejemplo:
\[\begin{equation*}\sin{\left(x \right)}\end{equation*}\]
Si la función tiene más de una variable, definimos la dimensión en la cual queremos integrar:
\[\begin{equation*}\frac{x^{3} y}{3} + x^{2} \left(\frac{y^{2}}{2} + \frac{y}{2} + \frac{1}{2}\right)\end{equation*}\]
Para integrales dobles usamos integrate(expresión,(var_a_integrar_interior,),(var_a_integrar_exterior,))
. Por ejemplo por ejemplo para integrar:
\[\int \int (xy + xy^2 + x^2*y + x) dx dy\]
\[\begin{equation*}\frac{x^{2} y^{3}}{6} + \frac{x^{2} y}{2} + y^{2} \left(\frac{x^{3}}{6} + \frac{x^{2}}{4}\right)\end{equation*}\]
Para calcular una integral definida usamos integrate(expresión, (variable_a_integrar, limite_inf, limite_sup))
. Note que la variable a integrar y sus límites son provisto en forma de tupla. Por ejemplo:
\[\begin{equation*}1 - e^{-1}\end{equation*}\]
Los límites \(-\infty\) e \(\infty\) sin permitidos y son declarados con doble o: oo
.
\[\begin{equation*}\infty\end{equation*}\]
Para calcular una integral definida doble usamos integrate(expresión, (variable_interior, limite_inf, limite_sup), (variable_exterior, limite_inf, limite_sup))
. Por ejemplo:
\[\begin{equation*}\pi\end{equation*}\]
Finalmente, para calcular límites usamos limit(expresión, variable, punto_convergencia)
. Por ejemplo:
\[\begin{equation*}1\end{equation*}\]
15.5 Resolviendo de Ecuaciones y Sistemas de Ecuaciones
Para resolver ecuaciones, lineales o no lineales, se usa la función solve(ecuación, variable)
sobre una ecuación generada con Eq(lado_izq,lado_der)
. Por ejemplo, para resolver por \(x\) la ecuación \(x^2+y^2=2\) usamos:
\[ \left[ \begin{array}{r}- \sqrt{2 - y^{2}}\\\sqrt{2 - y^{2}}\end{array} \right] \]
En este caso tenemos dos soluciones, por lo que solve
nos entrega un arreglo con dos elementos los mismos que pueden ser recuperados usando su respectivo índice:
solucion[1] = -sqrt(2 - y^2)
solucion[2] = sqrt(2 - y^2)
En le caso de ecuaciones lineales, tenemos la misma sintaxis:
\[ \left[ \begin{array}{r}\frac{1}{2} - \frac{y}{2}\end{array} \right] \]
Si la ecuación no es declarada y solamente se pasa una expresión a solve
, éste asume que la ecuación es expresión==0
y resuelve un problema de raíces.
\[ \left[ \begin{array}{r}-2\\2\end{array} \right] \]
Para el caso de sistemas de ecuaciones, lineales o no lineales, usamos la misma sintaxis pero declarando el sistema en un arreglo, esto es solve([ecu1, ..., ecuN], [var1,...,varN])
:
Dict{Any,Any} with 2 entries:
y => (a - 3*c)/(b*(a - c))
x => 2/(a - c)
Cómo la solución es única para \(x\) e \(y\), sympy
nos entrega un diccionario. Podemos acceder a cada una de las soluciones utilizando el identificador, en este caso el nombre de la variable.
\[\begin{equation*}\frac{2}{a - c}\end{equation*}\]
\[\begin{equation*}\frac{a - 3 c}{b \left(a - c\right)}\end{equation*}\]
Para sistema de ecuaciones no lineales usamos exactamente la misma sintaxis. La única diferencia es que, como en general los sistemas no lineales podrían tener más de una solución, el resultado es almacenado en tuplas dentro un arreglo. Por ejemplo:
2-element Array{Tuple{Sym,Sym},1}:
(-sqrt(4*a + 1)/2 - 1/2, -b - sqrt(4*a + 1)/2 - 1/2)
(sqrt(4*a + 1)/2 - 1/2, -b + sqrt(4*a + 1)/2 - 1/2)
En este caso obtenemos las soluciones individuales indexando primero el elemento de arreglo y luego el elemento de la tupla. Por ejemplo, las dos soluciones de \(x\) están en el primer elemento del arreglo y luego en los índices 1 y 2 de la tupla:
\[\begin{equation*}- \frac{\sqrt{4 a + 1}}{2} - \frac{1}{2}\end{equation*}\]
\[\begin{equation*}- b - \frac{\sqrt{4 a + 1}}{2} - \frac{1}{2}\end{equation*}\]
15.6 Matrices
Para declarar matrices simbólicas usamos arreglos en dos dimensiones, como lo hacíamos con matrices numéricas, pero cuyos elementos son simbólicos. Esto es, una matriz se declara matriz = []
y las filas y columnas se determinan con ;
y (espacio), respectivamente.
\[\left[ \begin{array}{rr}a&b\\c&d\end{array}\right]\]
\[\left[ \begin{array}{rr}x&y\\z&w\end{array}\right]\]
Las operaciones matriciales básicas (suma, resta, multiplicación y división por la izquierda) operan de la misma forma que en matrices numéricas:
\[\left[ \begin{array}{rr}a + x&b + y\\c + z&d + w\end{array}\right]\]
\[\left[ \begin{array}{rr}a - x&b - y\\c - z&d - w\end{array}\right]\]
\[\left[ \begin{array}{rr}a x + b z&a y + b w\\c x + d z&c y + d w\end{array}\right]\]
\[\left[ \begin{array}{rr}\frac{- b z + d x}{a d - b c}&\frac{- b w + d y}{a d - b c}\\\frac{a z - c x}{a d - b c}&\frac{a w - c y}{a d - b c}\end{array}\right]\]
La inversa de una matriz se obtienen ya sea usando la función inv(matriz)
o usando matriz^-1
:
\[\left[ \begin{array}{rr}\frac{d}{a d - b c}&- \frac{b}{a d - b c}\\- \frac{c}{a d - b c}&\frac{a}{a d - b c}\end{array}\right]\]
Para calcular la transpuesta de una matriz usamos la sintaxis matriz.T
.
\[\left[ \begin{array}{rr}a&c\\b&d\end{array}\right]\]
\[\left[ \begin{array}{rr}\frac{b^{2} + d^{2}}{a^{2} d^{2} - 2 a b c d + b^{2} c^{2}}&- \frac{a b + c d}{a^{2} d^{2} - 2 a b c d + b^{2} c^{2}}\\- \frac{a b + c d}{a^{2} d^{2} - 2 a b c d + b^{2} c^{2}}&\frac{a^{2} + c^{2}}{a^{2} d^{2} - 2 a b c d + b^{2} c^{2}}\end{array}\right]\]
Para calcular el determinante de una matriz usamos la sintaxis matriz.det()
.
\[\begin{equation*}a d - b c\end{equation*}\]
Para generar las matrices especiales identidad, ceros y unos usamos las funciones eye(dimension)
, zeros(nfilas,ncols)
y ones(nfilas,ncols)
, respectivamente. Estas tres funciones no han sido exportada al entorno global en el paquete sympy
, por lo que tenemos que indicarle a Julia que dicha función viene de ese paquete usando sympy.función()
.
\[\left[ \begin{array}{rr}1&0\\0&1\end{array}\right]\]
\[\left[ \begin{array}{rr}0&0\\0&0\end{array}\right]\]
\[\left[ \begin{array}{rr}1&1\\1&1\end{array}\right]\]
Finalmente, podemos calcular la matriz Jacobiana de un sistema de ecuaciones usando los aprendido hasta aquí. Definamos el sistema como una arreglo de ecuaciones:
\[ \left[ \begin{array}{r}2 x^{2} + 3 y^{2} + 5 z^{2}\\3 x y + x z + 2 x\\3 x y + 2 x + z^{3}\end{array} \right] \]
Calculamos la matriz Jacobiana del sistema diferenciando el sistema respecto de sus variables:
\(\displaystyle \left[\begin{matrix}\left[\begin{matrix}4 x\\3 y + z + 2\\3 y + 2\end{matrix}\right]\\\left[\begin{matrix}6 y\\3 x\\3 x\end{matrix}\right]\\\left[\begin{matrix}10 z\\x\\3 z^{2}\end{matrix}\right]\end{matrix}\right]\)
Note que sympy
retorna un arreglo de arreglos con los 3 gradientes. Esta no es la forma a la que estamos acostumbrados de representar la matriz Jacobiana. Podemos escribir una función que reciba el arreglo anterior y lo reorganice de tal forma que tengamos una matriz con el número de filas igual al número de ecuaciones y el número de columnas igual al número de variables. Esto es:
function jacobian(mat)
nv, nn,ne, nn = mat.shape
J = Array{Sym}(undef, ne, nv)
for i in 1:ne, j in 1:nv
J[i,j] = mat[j][1][i][1]
end
return J
end
jacobian (generic function with 1 method)
Ahora aplicamos la función:
\[\left[ \begin{array}{rrr}4 x&6 y&10 z\\3 y + z + 2&3 x&x\\3 y + 2&3 x&3 z^{2}\end{array}\right]\]