Polinomios
Contents
!wget -nc --no-cache -O init.py -q https://raw.githubusercontent.com/rramosp/introalgs.v1/main/content/init.py
import init; init.init(force_download=False);
Polinomios¶
Objetivo del módulo¶
Conocer el TAD polinomio y entender las implicaciones de las decisiones para su implementación.
Preguntas básicas¶
¿Cómo se define un TAD polinomio?
¿Qué decisiones hay que tomar para su implementación?
¿Cómo se combinan polinomios (sumas, multiplicaciones) según la representación escogida?
¿Qué compromisos de uso de memoria y rendimiento de CPU se adquieren?
Introducción¶
Un polinomio viene definido por una sumatoria de términos y exponentes de la siguiente manera:
Y el grado del mismo es el mayor \(e_i\) tal que \(c_i\ne 0\)
Por ejemplo, el polinomio:
tiene grado 5 y tiene coeficientes y exponentes:
\(C=[4,2,1]\)
\(E=[3,5,2]\)
observa que, a priori, no hacemos ninguna suposición sobre si los exponentes han de estar ordenados o han de ser únicos (sin repetidos).
TAD \(Polynom\)
\(\forall P,Q,R \in Polynom;\;\; c,d \in \mathbb{R};\;\; e,f\in\mathbb{N}\)
signatures: $$
\(3x^5 + 4x^2\) ::= \(new().add\_term(4,2).add\_term(3,5)\)
usando el axioma de suma varias veces:
\(P.sum(Q) ::=new().add\_term(4,2).add\_term(3,5).sum(Q)\)
\(::= new().add\_term(4,2).sum(Q).add\_term(3,5)\)
\(::= new().sum(Q).add\_term(4,2).add\_term(3,5)\)
\(::= new().add\_term(4,2).sum(Q).add\_term(3,5)\)
\(::= sum(Q).add\_term(4,2).add\_term(3,5)\)
observación:
Fïjate que el axioma de suma lo podríamos haber definido también de la siguiente forma:
axiom 6: \(P.sum(Q) ::= R | C_R = C_P + C_Q;\;E_R = E_P + E_Q\;\;\;\) siendo \(+\) la operación de concatenación de listas
esta última definición es más operativa ya que implica dos estructuras de almacenamiento \(C\) y \(E\) para cada polinomio, y sugiere una implementación concreta. En cambio la definición anterior, en los axiomas 4 y 5, es más abstracta e independiente de implementación.
implementación:
La siguiente clase implementa estas definiciones. Observa el constructor, usamos list
para copiar la lista pasada como argumento (si no, sería una referencia y se modificaría el original cuando hagamos operaciones).
from IPython.display import Math
import numpy as np
import numbers
class Pol:
def __init__(self, elements=[]):
self.elements = list(elements)
def isEmpty(self):
return len(self.elements==0)
def add_term(self, coef, exp):
assert (isinstance(coef, numbers.Real) or isinstance(coef,numbers.Integral)) and isinstance(exp, numbers.Integral), \
"coef/exp must be float/int"
self.elements.append((coef,exp))
return self
def sum(self, q):
r = self.__class__()
for c,e in self.elements+q.elements:
r.add_term(c,e)
return r
def show(self):
s = "+".join(["%s^{%s}"%(str(c) if e==0 else str(c)+"x" if c!=1 else "x", str(e) if e not in [0,1] else "") for c,e in self.elements if c!=0])
s = s.replace("+-", "-")
return Math(s)
p = Pol().add_term(3,5).add_term(4,2).add_term(3,0).add_term(5,10)
print(p.elements)
p.show()
[(3, 5), (4, 2), (3, 0), (5, 10)]
q = Pol().add_term(2,1).add_term(7,2)
print(q.elements)
q.show()
[(2, 1), (7, 2)]
p.sum(q).show()
seguimos definiendo axiomas
axiom 7: \(new().smult(d, f) ::= new()\)
axiom 8: \(P.add\_term(c,e).smult(d,f) ::= P.smult(d,f).add\_term(c*d, e+f)\)
práctica:
usa los axiomas anteriores para multiplicar
\(P.smult(9,3) ::=new().add\_term(4,2).add\_term(3,5).smult(9,3)\)
\(::=new().add\_term(4,2).smult(9,3).add\_term(27,8)\)
\(::=new().smult(9,3).add\_term(36,5).add\_term(27,8)\)
\(::=new().add\_term(36,5).add\_term(27,8)\)
o sea \(27x^8 + 36x^5\)
y pensamos una definición más operativa
axiom 9: \(P.smult(d,f)::=R|C_R = [c*d, \;\;\forall c\in C_P], E_R = [e+f, \;\;\forall e\in E_P]\)
y ampliamos nuestra definición de polinomio (observa que usamos herencia de clases). Fíjate que la sintaxis de Python está diseñada para que se parezca lo más posible a la notación matemática anterior. Observa el uso de clone
para evitar modificar el propio objeto.
import numbers
class Pol2(Pol):
def clone(self):
return Pol2(list(self.elements))
def smult(self, d,f):
assert (isinstance(d, numbers.Real) or isinstance(d,numbers.Integral)) and isinstance(f, numbers.Integral), "coef/exp must be float/int"
r = self.__class__()
r.elements = [(c*d, e+f) for c,e in self.elements]
return r
p = Pol2().add_term(3,5).add_term(4,2)
p.clone().show()
p.smult(9,3).show()
más axiomas
axiom 10: \(new().mult(Q) ::= new()\)
axiom 11: \(P.add\_term(c,e).mult(Q) ::= P.mult(Q).sum(Q.smult(c,e))\)
práctica:
multipliquemos:
\(P.mult(Q) ::=new().add\_term(4,2).add\_term(3,5).mult(Q)\)
\(::=new().add\_term(4,2).mult(Q).sum(Q.smult(3,5))\)
\(::=new().mult(Q).sum(Q.smult(4,2)).sum(Q.smult(3,5))\)
\(::=new().sum(Q.smult(4,2)).sum(Q.smult(3,5))\)
implementemos:
class Pol3(Pol2):
def mult(self, q):
terms = [q.smult(c,e) for c,e in self.elements]
r = terms[0]
for t in terms[1:]:
r = r.sum(t)
return r
p = Pol3().add_term(3,5).add_term(4,2)
q = Pol3().add_term(2,1).add_term(7,2)
p.show()
q.show()
p.mult(q).show()
más axiomas:
axiom 12: \(new().grade() ::= error\)
axiom 13: \(new().add\_term(c,e).grade() ::= e\)
axiom 14: \(P.add\_term(c,e).add\_term(d,f).grade() ::= P.add\_term(c,e).grade()\) if \(e>f\) else \(P.add\_term(d,f).grade()\)
axiom 15: \(new().coef(f) ::= 0\)
axiom 16: \(P.add\_term(c,e).coef(f) ::= c+P.coef(f)\) if \(e=f\) else \(P.coef(f)\)
y su implementación.
class Pol4(Pol3):
def grade(self):
return np.max([e for c,e in self.elements])
def coef(self,f):
return np.sum([c for c,e in self.elements if e==f])
p = Pol4().add_term(3,5).add_term(4,8).add_term(7,5)
p.show()
p.grade()
8
p.coef(5)
10
p.coef(8)
4
p.coef(2)
0.0
¿Cómo sería la definición axiomática más operativa de esta implementación?
axiom 17: \(P.grade() ::== max(E)\)
axiom 18: \(P.coef(f) ::= \sum_{j|e_j=f} c_j\)
finalmente añadimos un método para simplificar un polinomio
class Pol5(Pol4):
def simplify(self):
exps = np.sort(np.unique([e for c,e in self.elements]))
coefs = [self.coef(f) for f in exps]
return Pol5 ([(c,e) for c,e in zip(coefs, exps)])
p = Pol5().add_term(3,5).add_term(4,8).add_term(7,5).add_term(-2,8).add_term(2.87, 3)
p.show()
p.simplify().show()
y verficamos propiedades
p = Pol5().add_term(3,5).add_term(4,8).add_term(7,5).add_term(-2,8).add_term(2.87, 3)
q = Pol5().add_term(2,1).add_term(7,2)
p.show()
q.show()
p.mult(q).show()
p.mult(q).simplify().show()
p.simplify().mult(q).show()
Ejercicios propuestos¶
Modifica
add_term
para ignorar términos con coeficiente 0. Esto repararía el comportamiento del métodograde
Polinomios con restricciones
Define axiomas para un polinomio con restricciones en el que sólo hay un término por exponente y los términos deben de estar ordenados de manera descendente por exponente.
P.ej. si tenemos el polinomio \(4x^8 + 5x^6 – 4x^3 + 2x\) la forma como se representa según nuestra definición de polinomio con restricciones es:
\(new().add\_term(2,1).add\_term(-4,3).add\_term(5,6).add\_term(4,8)\)
el término con menor exponente será el más interno y el término con mayor exponente será el más externo, es decir, el término con mayor exponente es el correspondiente a \(c\) y \(e\) de acuerdo a la forma general definida.
Con base en esta decisión, las definiciones de los axiomas correspondientes a las funciones grado y suma se facilitan respecto al polinomio sin restricciones.
axiom: \(new.grade() ::= error\)
axiom: \(new().add\_term(c,e).grade() ::= e\)
axioms:
new().sum(Q) ::= Q
P.sum(new()) ::= P
P.add_term(c,e).sum(Q.add_term(d,f)) ::=
P.sum(Q.add_term(d,f)).add_term(c,e) if e>f else: P.add_term(c,e).sum(Q).add_term(d,f) if e<f else: P.sum(Q) if (c+d)==0 else P.sum(Q).add_term(c+d,e)
Después de realizar el laboratorio
implementa la multiplicación para la implementación v1
implementa la resta y la división para ambas implementaciones
mide y compara los rendimientos de
add_term
de la implementación v1 y v2mide y compara los rendimientos de
sum
de la implementación v1 y v2¿qué equilibrio hay entre rendimiendo de CPU y de memoria?