.. -*- coding: utf-8 -*- Cálculo simbólico ================= .. index:: variable simbólica, expresión simbólica, var Variables y expresiones simbólicas :::::::::::::::::::::::::::::::::: Una **variable simbólica** es un objeto en python que representa una incógnita que puede tomar cualquier valor. Por tanto, una operación que involucra una o más variables simbólicas no devuelve un valor numérico, sino una **expresión simbólica** que involucra números, operaciones, funciones y variables simbólicas. Si en algún momento sustituimos las variables simbólicas por números (o elementos de un anillo), podremos realizar las operaciones y obtener un número (o un elemento de un anillo). Esta forma de trabajar se corresponde con los criterios de "no hacer las operaciones hasta el final", "dejar las operaciones indicadas" y "cancelar términos antes de evaluar" que aprendistéis en secundaria. :: sage: #Declaramos las variables a b c sage: var('a b c') sage: #Definimos varias expresiones simbolicas sage: s = a + b + c sage: s2 = a^2 + b^2 + c^2 sage: s3 = a^3 + b^3 + c^3 sage: s4 = a + b + c + 2*(a + b + c) sage: p = a*b*c sage: #Las imprimimos como código sage: print s sage: print s2 sage: print s3 sage: print s4 sage: print p sage: #Las mostramos en el formato matemático usual sage: show(s2) sage: show(s3) a + b + c a^2 + b^2 + c^2 a^3 + b^3 + c^3 3*a + 3*b + 3*c a*b*c .. MATH:: a^{2} + b^{2} + c^{2} .. MATH:: a^{3} + b^{3} + c^{3} :: sage: #Sustituimos las variables por numeros sage: print s(a=1, b=1, c=1) sage: print s(a=1, b=2, c=3) 3 6 :: sage: #Operamos con las expresiones sage: ex = (1/6)*(s^3 - 3*s*s2 +2*s3 ) sage: show(ex) .. MATH:: \frac{1}{6} \, {\left(a + b + c\right)}^{3} + \frac{1}{3} \, a^{3} + \frac{1}{3} \, b^{3} + \frac{1}{3} \, c^{3} - \frac{1}{2} \, {\left(a + b + c\right)} {\left(a^{2} + b^{2} + c^{2}\right)} .. index:: simplificar, expand, factor, simplify_rational, simplify_trig, simplify_exp Simplificar expresiones ~~~~~~~~~~~~~~~~~~~~~~~ Sin embargo, pese a que no es posible evaluar una expresión hasta el final, observamos que al crear la expresión se han realizado "*de oficio*" algunas simplificaciones triviales. En ejemplos como el de arriba, nos puede interesar simplificar la expresión todavía más, pero es necesario decir qué queremos exactamente. Existen varias estrategias para intentar simplificar una expresión, y cada estrategia puede tener más o menos éxito dependiendo del tipo de expresión simbólica. Algunas estrategias dan lugar a una expresión más sencilla en algunos casos pero no en otros, y con expresiones complicada pueden consumir bastante tiempo de proceso. Para la expresión anterior, como tenemos un polinomio, es buena idea expandirla en monomios que se puedan comparar unos con otros, usando el método ``expand`` . :: sage: show(ex.expand()) .. MATH:: a b c A menudo nos interesa lo contrario: factorizar la expresión usando ``factor`` . :: sage: p = a^3 + a^2*b + a^2*c + a*b^2 + a*c^2 + b^3 + b^2*c + b*c^2 + c^3 sage: show(p) sage: show(p.factor()) .. MATH:: a^{3} + a^{2} b + a^{2} c + a b^{2} + a c^{2} + b^{3} + b^{2} c + b c^{2} + c^{3} .. MATH:: {\left(a + b + c\right)} {\left(a^{2} + b^{2} + c^{2}\right)} Si observas con el tabulador los métodos de las expresiones regulares, verás que hay métodos específicos para expresiones con funciones trigonométricas, exponenciales, con radicales o fracciones (es decir, con funciones racionales). :: sage: p = sin(3*a) sage: show(p.expand_trig()) .. MATH:: -\sin\left(a\right)^{3} + 3 \, \sin\left(a\right) \cos\left(a\right)^{2} :: sage: p = sin(a)^2 - cos(a)^2 sage: show(p.simplify_trig()) .. MATH:: -2 \, \cos\left(a\right)^{2} + 1 :: sage: p = 2^a * 4^(2*a) sage: p.simplify_exp() 2^(5*a) :: sage: p = 1/a - 1/(a+1) sage: p.simplify_rational() 1/(a^2 + a) Funciones simbólicas ~~~~~~~~~~~~~~~~~~~~ A medio camino entre las expresiones simbólicas y las funciones de python, las funciones simbólicas son expresiones simbólicas en las que se fija el orden de las variables de entrada. Esto elimina la ambigüedad sobre qué variables se deben sustituir por qué argumentos y permite usarlas en sitios donde se espera una función de python. :: sage: f(a,b,c) = a + 2*b + 3*c sage: f(1,2,1) 8 :: sage: s = a + 2*b + 3*c sage: s(1,2,1) __main__:4: DeprecationWarning: Substitution using function-call syntax and unnamed arguments is deprecated and will be removed from a future release of Sage; you can use named arguments instead, like EXPR(x=..., y=...) 8 .. index:: polynomial Diferencias entre polinomios y expresiones simbólicas ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ En algunos aspectos las expresiones simbólicas pueden parecer similares a los polinomios que vimos en el bloque III, lo que puede provocar confusión. Es importante saber si tenemos entre manos una expresión simbólica que representa un polinomio como ésta: :: var('x') p = x^2 + 1 o un polinomio como éste (con coeficientes por ejemplo en ``RDF`` ): :: R. = PolynomialRing(RDF) q = t^2 + 1 Aunque ambos tienen métodos de propósito similar como ``coefficients`` o ``roots`` , el comportamiento es distinto, a veces de manera obvia y a veces de manera más sutil. Por ejemplo, las raíces o los coeficientes de una expresión simbólica son a su vez expresiones simbólicas, mientras que en el caso de un polinomio son miembros del anillo de coeficientes. :: sage: var('x') sage: p = x^2 + 3 sage: print p.coefficients() sage: print p.roots() sage: termino_indi = p.coefficients()[0][0] sage: print termino_indi, parent(termino_indi) [[3, 0], [1, 2]] [(-I*sqrt(3), 1), (I*sqrt(3), 1)] 3 Symbolic Ring :: sage: R. = PolynomialRing(RDF) sage: q = t^2 + 3 sage: print q.coefficients() sage: print q.roots() sage: termino_indi = q.coefficients()[0] sage: print termino_indi, parent(termino_indi) [3.0, 1.0] [] 3.0 Real Double Field El método ``polynomial`` permite construir un polinomio con coeficientes en un anillo dado a partir de una expresión simbólica. Para hacer lo contrario, podemos sustituir la variable del anillo de polinomios por una variable simbólica, o convertir explícitamente al tipo de datos ``SR`` (el anillo de expresiones simbólicas, o Symbolic Ring). :: sage: p.polynomial(ZZ).roots() [] :: sage: q0 = q(t=x); q0 x^2 + 3.0 :: sage: q1 = SR(q); q1 t^2 + 3.0 .. index:: relación simbólica, subtract_from_both_sides Ecuaciones e inecuaciones ::::::::::::::::::::::::: Si usamos el operador de igualdad (==) o algún operador de comparación (<, <=, >, >=, !=) sobre dos expresiones simbólicas, obtenemos una **relación simbólica**. Al igual que las expresiones simbólicas, las operaciones en una relación están indicadas pero no se pueden ejecutar hasta que todas las variables han tomado un valor concreto. Evaluar una relación ~~~~~~~~~~~~~~~~~~~~ Para evaluar la relación y obtener un booleano, usamos el operador de conversión a booleano ``bool(relacion)`` . Algunas relaciones tienen un valor definido, incluso aunque dependan de variables simbólicas: son siempre ciertas o siempre falsas. Muchas otras tendrán un valor indeterminado: dependiendo de los valores de las variables. En estos casos, el valor booleano de la relación es ``False`` , siguiendo una convención usual en otros programas de cálculo simbólico. También obtenemos ``False`` si evaluamos una expresión demasiado compleja para el motor simbólico. Tomamos buena nota de la asimetría en los resultados: - ``True`` : el programa puede demostrar que la relación se verifica siempre, para cualquier valor de las variables. - ``False`` : el programa no puede demostrar que la relación se verifica siempre, para cualquier valor de las variables. Eso *no significa* que nunca se verifique, ni siquiera que sea *ambigua* (cierta para algunos valores de las variables y falsa para otros). :: sage: var('a b') sage: r1 = (a 0 :: sage: print bool(r1), bool(r2), bool(r3), bool(r4), bool(r5) True False False False False Para distinguir entre relaciones que se puede demostrar que son falsas y relaciones ambiguas (o demasiado complicadas para el software), podemos usar la negación de las relaciones. Vemos por ejemplo, que se puede demostrar que ``r2`` es siempre falsa porque su negación se resuelve a ``True`` . :: sage: print r2.negation(), bool(r2.negation()) sage: print r3, bool(r3) sage: print r3.negation(), bool(r3.negation()) a >= a - 1 True a < b False a >= b False Por supuesto concretando los valores de algunas variables, una relación ambigua se puede concretar, y resultar cierta (o falsa). :: sage: print bool(r2(a=1, b=2)), bool(r3(a=1, b=2)) sage: print bool(r2(a=3, b=2)), bool(r3(a=3, b=2)) sage: print bool(r5(a=e^b)) False True False False True Aparte de preguntar por su valor de verdad, podemos simplificar y operar con las relaciones de forma similar a las formas de operar con una expresión cualquiera: :: sage: r1 = (a0) sage: simplify(s) x El comando dual de ``assume`` es ``forget`` , que olvida una identidad que anteriormente habíamos asumido como cierta (la sintaxis es idéntica). ``forget()`` sin argumentos olvida todas las identidades. ``assumptions()`` muestra todas las identidades activas. :: sage: assumptions() [x > 0] :: sage: forget(x>0) sage: simplify(s) sqrt(x^2) :: sage: assumptions() [] También podemos asumir otro tipo de información, como por ejemplo que una variable representa un número entero. :: sage: var('n') sage: assume(n, 'integer') sage: print sin(n*pi).simplify() sage: forget(n, 'integer') sage: print sin(n*pi).simplify() 0 sin(pi*n) :: sage: var('k t') sage: assume(k, 'integer') sage: simplify(sin(t+2*k*pi)) sin(t) Si tomamos por ciertas varias identidades incompatibles, Sage lanza un error: :: sage: var('x') sage: forget() sage: assume(x<0) sage: assume(x>0) Traceback (most recent call last): ... ValueError: Assumption is inconsistent Ejemplos :::::::: Matrices con parámetros ~~~~~~~~~~~~~~~~~~~~~~~ Si usamos el anillo de expresiones simbólicas ( ``SR`` ) para los coeficientes de las matrices, podemos trabajar con parámetros. :: sage: #Ejemplo de una matriz dependiendo de un parámetro sage: var('a') sage: M = matrix(SR,3,3, [1,2,3, 1,-3,1, 2,1,a]) sage: M [ 1 2 3] [ 1 -3 1] [ 2 1 a] :: sage: #Ejemplo: la matriz de van der Monde 3x3 sage: var('a1 a2 a3') sage: VDM = matrix([[1, a1, a1^2], [1, a2, a2^2], [1, a3, a3^2] ]) sage: show(VDM) .. MATH:: \left(\begin{array}{rrr} 1 & a_{1} & a_{1}^{2} \\ 1 & a_{2} & a_{2}^{2} \\ 1 & a_{3} & a_{3}^{2} \end{array}\right) Ejercicio resuelto: Calcula el rango de la matriz .. MATH:: \left(\begin{array}{rrr}1 & 2 & 3 \\1 & \-3 & 1 \\2 & 1 & a\end{array}\right) dependiendo del valor del parámetro *a* . :: sage: var('a') sage: M = matrix(SR,3,3, [1,2,3, 1,-3,1, 2,1,a]) sage: M [ 1 2 3] [ 1 -3 1] [ 2 1 a] Solución 1: ponemos la matriz en forma escalonada mediante operacinoes de fila y vemos que el rango depende de si a es 24/5 o no. :: sage: M.add_multiple_of_row(1,0,-1) sage: M [ 1 2 3] [ 0 -5 -2] [ 2 1 a] :: sage: M.add_multiple_of_row(2,0,-2) sage: M [ 1 2 3] [ 0 -5 -2] [ 0 -3 a - 6] :: sage: M.add_multiple_of_row(2,1,-3/5) sage: M [ 1 2 3] [ 0 -5 -2] [ 0 0 a - 24/5] Por supuesto, también podemos calcular el determinante :: sage: var('a') sage: M = matrix(SR,3,3, [1,2,3, 1,-3,1, 2,1,a]) sage: determinante = M.det() sage: determinante.factor() -5*a + 24 Sin embargo, la forma escalonada produce un resultado inesperado: como las expresiones simbólicas no nulas son invertibles, la matriz es equivalente a la identidad! :: sage: M.echelon_form() [1 0 0] [0 1 0] [0 0 1] Ejercicio resuelto ~~~~~~~~~~~~~~~~~~ Encuentra la recta afín que pasa por el punto :math:`(1,0,-1)` y corta a las rectas :math:`L_1` y :math:`L_2`, sabiendo que :math:`L_1` pasa por :math:`(1,0,1)` y :math:`(2,1,0)`, y que :math:`L_2` pasa por :math:`(3,1,-1)` y :math:`(1,2,-1)`. :: sage: p1 = vector([1,0,1]) sage: p2 = vector([2,1,0]) sage: p3 = vector([3,1,-1]) sage: p4 = vector([1,2,-1]) sage: p5 = vector([1,0,-1]) sage: var('r s t') sage: #Calculamos un punto generico de L1, y otro de L2 sage: pgL1 = (1-r)*p1+r*p2 sage: pgL2 = (1-s)*p3+s*p4 sage: #Un punto generico de la recta que pasa por pgL1 y pgL2 sage: pgL = (1-t)*pgL1 + t*pgL2 sage: pgL (-(t - 1)*(r + 1) - (2*s - 3)*t, -(t - 1)*r + (s + 1)*t, (t - 1)*(r - 1) - t) :: sage: #Imponemos las ecuaciones que representan que sage: eqs = [c1==c2 for c1,c2 in zip(pgL,p5)] sage: print eqs sage: solve(eqs,r,s,t) [-(t - 1)*(r + 1) - (2*s - 3)*t == 1, -(t - 1)*r + (s + 1)*t == 0, (t - 1)*(r - 1) - t == -1] [[r == 2, s == (1/3), t == 3]] La recta pasa por p5 y por (1-r)*p1+r*p2, con r=2, es decir :math:`(3, 2, -1)` :: sage: ((1-r)*p1+r*p2)(r=2) (3, 2, -1) **Ejercicio**: encuentra las ecuaciones implícitas y paramétricas de la recta solución .. index:: fast_float Truco avanzado: fast_float :::::::::::::::::::::::::: Si vas a evaluar muchas veces una expresión simbólica, puedes "*compilarla*" usando el comando ``fast_float`` , que devuelve una función normal de python correspondiente a la expresión simbólica. Esta función ya no se puede manipular de forma simbólica, pero se puede evaluar usando mucho menos tiempo. :: sage: var('a b c') sage: f(a,b,c) = a^3 + sin(b + 3*log(c)) sage: timeit('f(1,2,1)') sage: ff = fast_float(f) sage: timeit('ff(1,2,1)') 625 loops, best of 3: 248 µs per loop 625 loops, best of 3: 13 µs per loop :: sage: var('a b c') sage: s = a^3 + sin(b + 3*log(c)) sage: timeit('s(a=1, b=2, c=1)') sage: fs = fast_float(s, a, b, c) sage: timeit('fs(1,2,1)') 625 loops, best of 3: 109 µs per loop 625 loops, best of 3: 13 µs per loop