Objets¶
Vecteur¶
Créer une classe Vecteur3D. Chaque vecteur aura 3 attributs : x, y, z
Écrire une méthode norme qui renvoie la norme.
Écrire la méthode
__add__
pour faire la somme entre deux veteursÉcrire la méthode
__mul__
pour faire soit le produit par un scalaire (\(2\vec{u}\)) ou le produit scalaire (\(\vec{u}\cdot\vec{v}\)).
Bibliographie¶
Un livre est décrit par son titre, auteur et année de publication (pour faire les choses simplements). Écrire une classe Livre
qui enregistre ces informations. Ecrire la méthode __repr__
et __str__
.
Une bibliographe est une liste de livre. Écrire la classe Bibliographie
qui enregistre une liste de livre (on stockera la liste de livre sous forme d’une liste qui sera un attribut de la bibliographie).
L’objectif final est de pouvoir faire ceci ::
livre1 = Livre("A very nice book", "F. Dupont", 2014)
livre2 = Livre("A very smart book", "A. Einstein", 1923)
livre3 = Livre("A very stupid comic", "D. Duck", 1937)
bibliographie = Bibliographie([book1, book2, book3])
Maintenant que tout est fait sous forme d’objet, on peut imaginer écrire plusieurs méthode :
Écrire une méthode
filter_by_year
qui fait une nouvelle bibliographie ne contenant que les livres d’une année donée.Écrire une méthode
to_html
qui formate correctement la bibliographie. La méthode de la classe Bibliographie devra appeler une méthode pour chaque Livre.
Et en HTML ::
<table>
<thead>
<tr> <th>Auteur</th><th>Titre</th><th>Année</th></tr>
</thead>
<tbody>
<tr><td>F. Dupont</td><td>2014</td><td>A very nice book</td></tr>
<tr><td>A. Einstein</td><td>1923</td><td>A very smart book</td></tr>
<tr><td>D. Duck</td><td>1937</td><td>A very stupid comic</td></tr>
</tbody>
</table>
Remarque : si un objet possède une méthode _repr_html_
, alors le jupyter notebook utilsera automatiquement la représentation en HTML. Rajouter cette méthode (qui appelera to_html).
class Livre():
def __init__(self, titre, auteur, annee):
self.titre = titre
self.auteur = auteur
self.annee = annee
def __repr__(self):
return f"Livre({self.titre!r}, {self.auteur!r}, {self.annee!r})"
def to_html_table_line(self):
return f"<tr><td>{self.auteur}</td><td>{self.titre}</td><td>{self.annee}</td></tr>"
bibio_html_template="""<table>
<thead>
<tr><th>Auteur</th><th>Titre</th><th>Année</th></tr>
</thead>
<tbody>
{content}
</tbody>
</table>"""
class Bibliographie():
def __init__(self, liste_des_livres):
self._liste_livres = liste_des_livres
def __getitem__(self, key):
return self._liste_livres[key]
def __repr__(self):
return f"Bibliographie({self._liste_livres!r})"
def to_html_table(self):
content = '\n'.join([livre.to_html_table_line() for livre in self._liste_livres])
return bibio_html_template.format(content=content)
_repr_html_ = to_html_table
livre1 = Livre("A very nice book", "F. Dupont", 2014)
livre2 = Livre("A very smart book", "A. Einstein", 1923)
livre3 = Livre("A very stupid comic", "D. Duck", 1937)
bibliographie = Bibliographie([livre1, livre2, livre3])
bibliographie
Auteur | Titre | Année |
---|---|---|
F. Dupont | A very nice book | 2014 |
A. Einstein | A very smart book | 1923 |
D. Duck | A very stupid comic | 1937 |
Système de calcul formel¶
Cet exercice est à but purement pédagogique. Pour utiliser un système de calcul formel sous Python, la librairie sympy
existe et fonctionnera bien mieux que ce que l’on va faire !
L’objectif de ce TD est de réaliser un système de calcul formel qui permettra de manipuler des expressions algébriques simples et de réaliser des opérations simples. Par exemple, on souhaite pouvoir effectuer ::
x = Symbol('x')
y = Symbol('y')
s = 2*x*y + sin(x)*y
print(s.diff(x)) # Dérivée par rapport à x
Chaque expression sera représentée par un arbre. Les feuilles de l’arbre seront soit les symboles soit les constantes numériques. Les noeuds seront des fonctions à un ou plusieurs argument (sinus, somme, opposé, …). Le nom de la classe du noeud désignera la fonction. Les “enfants” du noeud seront les arguments de la fonction. Par exemple l’expression ci dessus correspondra à l’objet suivant ::
# sA : 2*x*y
sA = Prod(Prod(Number(2), Symbol('x')), Symbol('y'))
# sB : sin(x)*y
sB = Prod(Sin(Symbol('x')), Symbol('y'))
s = Sum(sA, sB)
Structure du programme¶
Voici la structure de base ::
class Expr(object):
pass
class Node(Expr):
pass
class Leave(Expr):
pass
Pour les feuilles ::
class Symbol(Leave):
pass
class Number(Leave):
pass
Ensuite on définit les fonctions ::
class Function(Node):
""" Function with an arbitrary number of arguments """
pass
Les opérateurs sont des fonctions comme les autres, mais elle seront simplement affichées différemment ::
class BinaryOperator(Function):
pass
class Sum(BinaryOperator):
pass
# Idem pour Sub, Div, Prod, Pow
class UnitaryOperator(Function):
pass
class Neg(UnitaryOperateor):
pass
Les fonction mathématiques, qui prennent un seul argument ::
class MathFunction(Function):
pass
class Sin(MathFunction):
pass
Questions¶
On va procéder étape par étape. Il sera plus facile de commencer par les feuilles avant d’écrire la structure globale.
Ecrire le
__init__
de la classe Symbol et Number
class Expr(object):
def __add__(self, other):
return Sum(self, other)
def __mul__(self, other):
return Prod(self, other)
def __repr__(self):
return self.display()
class Node(Expr):
pass
class Leave(Expr):
pass
class Symbol(Leave):
pass
class Number(Leave):
pass
class Function(Node):
""" Function with an arbitrary number of arguments """
pass
class BinaryOperator(Function):
pass
class Sum(BinaryOperator):
pass
# Idem pour Sub, Div, Prod, Pow
class UnitaryOperator(Function):
pass
class Neg(UnitaryOperator):
pass
class MathFunction(Function):
pass
class Sin(MathFunction):
pass
import numbers
class Symbol(Leave):
def __init__(self, symbole):
self.symb = symbole
class Number(Leave):
def __init__(self, nombre):
if not isinstance(nombre, numbers.Number):
raise ValueError
self.nbre = nombre
Ecrire une méthode
display
sur ces classes afin de renvoyer une chaîne de caractère contenant le symbole ou le nombre
class Symbol(Leave):
def __init__(self, symbole):
self.symb = symbole
def display(self, *args):
return self.symb
class Number(Leave):
def __init__(self, nombre):
if not isinstance(nombre, numbers.Number):
raise ValueError
self.nbre = nombre
def display(self):
return str(self.nbre)
Ecrire le
__init__
de la class Sin ainsi que le display. Le display devra appeler le display de l’argument. Par exemple ceci devra fonctionner ::
>>> x = Symbol('x')
>>> Sin(x).display()
sin(x)
>>> Sin(Sin(x)).display()
sin(sin(x))
class Sin(MathFunction):
def __init__(self, arg):
self.arg = arg
def display(self):
return f'Sin({self.arg.display()})'
x = Symbol('x')
Sin(Sin(x)).display()
'Sin(Sin(x))'
Généraliser le init et le display de
Sin
afin de le mettre dans la class MathFunction. On rajoutera un attribut de classe à chaque sous classe de MathFunction ::
class Sin(MathFunction):
funtion_name = 'sin'
class MathFunction(Function):
def display(self):
return f'{self.function_name}({self.arg.display()})'
class Sin(MathFunction):
function_name = 'sin'
def __init__(self, arg):
self.arg = arg
x = Symbol('x')
Sin(Sin(x)).display()
'sin(sin(x))'
Faire de même pour les opérateurs binaires. On pourra commencer par simplement le faire pour Sum, puis généraliser avec un attribut de classe ::
class Sum(BinaryOperator):
operator_name = '+'
class Function(Node):
""" Function with an arbitrary number of arguments """
def __init__(self, *args):
self.args = args
class BinaryOperator(Function):
def __init__(self, arg1, arg2):
self.args = (arg1, arg2)
def display(self):
return f'({self.args[0].display()}) {self.operator_name} ({self.args[1].display()})'
class Prod(BinaryOperator):
operator_name = '*'
class Sum(BinaryOperator):
operator_name = '+'
x = Symbol('x')
y = Symbol('y')
Sum(x, Sin(Prod(x, y))).display()
'(x) + (sin((x) * (y)))'
A ce stade quelque chose comme ceci devrait fonctionner ::
x = Symbol('x')
y = Symbol('y')
Sum(x, Sin(Prod(x, y)))
Rajouter les méthodes __add__
, __mul__
, etc à la classe Expr afin de pouvoir écrire :
>>> x + Sin(x*y)
x + Sin(x*y)
(x) + (sin((x) * (y)))
Ecrire les méthodes
evaluate
afin de calculer la valeur numérique d’une expression. Cette méthode fonctionnera de la sorte :
>>> expr = x + Sin(x*y)
>>> expr.evaluate(x=1, y=3)
On aura donc le protocole suivant ::
def evaluate(self, **kwd):
pass
Le dictionnaire kwd sera passé récursivement jusqu’aux feuilles et sera utilisé pour évaluer les symboles.
Les opérateurs binaires numériques sont définis dans le module operator
et les fonctions dans le module math
. Afin de factoriser le code, on rajoutera donc simplement un attribut de classe du type operator_function = operator.add
pour les opérateurs binaires et math_function = math.sin
pour les fonctions.
import math
import numbers
import operator
class Expr(object):
def __add__(self, other):
return Sum(self, other)
def __mul__(self, other):
return Prod(self, other)
def __repr__(self):
return self.display()
class Node(Expr):
pass
class Leave(Expr):
pass
class UnitaryOperator(Function):
pass
class Neg(UnitaryOperator):
pass
class Symbol(Leave):
def __init__(self, symbole):
self.symb = symbole
def display(self, *args):
return self.symb
def evaluate(self, **kwd):
try:
return kwd[self.symb]
except KeyError:
raise Exception("La valeur de {} n'est pas définie".format(self.symb))
class Number(Leave):
def __init__(self, nombre):
if not isinstance(nombre, numbers.Number):
raise ValueError
self.nbre = nombre
def display(self):
return str(self.nbre)
def evaluate(self, **kwd):
return self.nbre
class Function(Node):
""" Function with an arbitrary number of arguments """
def __init__(self, *args):
self.args = args
def evaluate(self, **kwd):
evaluated_args = [elm.evaluate(**kwd) for elm in self.args]
return self.math_function(*evaluated_args)
class BinaryOperator(Function):
def __init__(self, arg1, arg2):
self.args = (arg1, arg2)
def display(self):
return f'({self.args[0].display()}) {self.operator_name} ({self.args[1].display()})'
class Prod(BinaryOperator):
operator_name = '*'
math_function = operator.mul
class Sum(BinaryOperator):
operator_name = '+'
math_function = operator.add
class MathFunction(Function):
def display(self):
return f'{self.function_name}({self.args[0].display()})'
class Sin(MathFunction):
function_name = 'sin'
math_function = math.sin
def __init__(self, arg):
self.args = (arg,)
x = Symbol('x')
y = Symbol('y')
expr = x + Sin(x*y)
print(expr)
expr.evaluate(x=2, y=4.5)
(x) + (sin((x) * (y)))
2.4121184852417565
Maintenant que vous avez compris le principe, il devrait être facile d’écrire une méthode
diff
qui effectue la dérivée par rapport à une variable !Reste à simplifier les expressions. Une technique consiste à créer des règles de simplifications sous forme de méthode que l’on regroupe ensuite dans une liste ::
class Sum(BinaryOperator):
operator_name = '+'
operator_function = operator.add
def simplication_de_deux_nombres(self):
if isinstance(self.arg1, Number) and
isinstance(self.arg2, Number):
return Number(self.arg1.value + self.arg2.value)
def simplication_addition_avec_zero(self):
pass
liste_simplication = ['simplication_de_deux_nombres',
'simplication_addition_avec_zero']
Ensuite, il faut réussir à appeler correctement et de façon recursive ces méthodes…
Pour l’affichage des opérateurs binaires, les règles de priorité peuvent être utilisées pour éviter de mettre trop de parenthèses. Par exemple, dans le cas
a*(b+c)
, la multiplication appelle le display de l’addition. Comme elle est prioritaire, l’addition va renvoyer le résulat avec des parenthèses. Dans le cas inversea + b*c
, c’est inutile. Il faut donc que le display d’un opérateur passe sa priorité à ses enfants lors de l’appel de display. Implémenter ce principe.
import numbers
import operator
import math
class Expr(object):
def binary_operator(self, other, operator):
if isinstance(other, numbers.Number):
other = Number(other)
if isinstance(other, Expr):
return operator(self, other)
return NotImplemented
def reversed_binary_operator(self, other, operator):
if isinstance(other, numbers.Number):
other = Number(other)
if isinstance(other, Expr):
return operator(other, self)
return NotImplemented
def __add__(self, other):
return self.binary_operator(other, Sum)
def __mul__(self, other):
return self.binary_operator(other, Prod)
def __truediv__(self, other):
return self.binary_operator(other, Div)
def __sub__(self, other):
return self.binary_operator(other, Sub)
def __radd__(self, other):
return self.reversed_binary_operator(other, Sum)
def __rmul__(self, other):
return self.reversed_binary_operator(other, Prod)
def __rtruediv__(self, other):
return self.reversed_binary_operator(other, Div)
def __rsub__(self, other):
return self.reversed_binary_operator(other, Sub)
def __neg__(self):
return Neg(self)
def __repr__(self):
return self.display()
def diff(self, var):
out = self._diff(var)
return out.simplify()
class Node(Expr):
pass
class Leave(Expr):
def simplify(self):
return self
class Symbol(Leave):
def __init__(self, symbole):
self.symb = symbole
def display(self, *args):
return self.symb
def evaluate(self, **kwd):
try:
return kwd[self.symb]
except KeyError:
raise Exception("La valeur de {} n'est pas définie".format(self.symb))
def __eq__(self, other):
if not type(self)==type(other):
return False
return self.symb==other.symb
def _diff(self, var):
if self==var:
return Number(1)
return Number(0)
class Number(Leave):
def __init__(self, nombre):
if not isinstance(nombre, numbers.Number):
raise ValueError
self.nbre = nombre
def display(self, *args):
return str(self.nbre)
def evaluate(self, **kwd):
return self.nbre
def __eq__(self, other):
if isinstance(other, numbers.Number):
other = Number(other)
if isinstance(other, Number):
return other.nbre==self.nbre
return False
def _diff(self, var):
return Number(0)
class Function(Node):
""" Function with an arbitrary number of arguments """
def __init__(self, *args):
self.args = args
def evaluate(self, **kwd):
evaluated_args = [elm.evaluate(**kwd) for elm in self.args]
return self.math_function(*evaluated_args)
def __eq__(self, other):
if not type(self)==type(other):
return False
return self.args==other.args
def _diff(self, var):
partial_derivative = getattr(self, 'partial_derivative', None)
if partial_derivative is None:
raise NotImplementedError('Cannot derivate function {self.__class__}.'.format(self=self))
if len(self.args)==0:
return Number(0)
out = self.args[0].diff(var)*partial_derivative[0](*self.args)
for deriv, arg in zip(partial_derivative[1:], self.args[1:]):
out = out + arg.diff(var)*deriv(*self.args)
return out
liste_simplification = []
def simplify(self):
out = type(self)(*[elm.simplify() for elm in self.args])
for elm in self.liste_simplification:
tmp = getattr(out, elm)()
if tmp is not None:
return tmp.simplify()
return out
class BinaryOperator(Function):
commutative=False
def display(self, parent_priority=0):
if parent_priority>self.priority:
fmt_str = '({} {} {})'
else:
fmt_str = '{} {} {}'
return fmt_str.format(self.args[0].display(self.priority),
self.operator_name, self.args[1].display(self.priority))
def __eq__(self, other):
if not type(self)==type(other):
return False
if self.args==other.args:
return True
if self.commutative:
return self.args==other.args[::-1]
return False
class Sum(BinaryOperator):
priority = 0
operator_name = '+'
math_function=operator.add
commutative=True
partial_derivative = (lambda x, y:1, lambda x, y:1)
def simplication_de_deux_nombres(self):
if isinstance(self.args[0], Number) and isinstance(self.args[1], Number):
return Number(self.args[0].nbre + self.args[1].nbre)
def simplication_addition_avec_zero(self):
if self.args[0]==0:
return self.args[1]
if self.args[1]==0:
return self.args[0]
def simplification_identique(self):
if self.args[1]==self.args[0]:
return 2*self.args[0]
liste_simplification = ['simplication_de_deux_nombres',
'simplication_addition_avec_zero', 'simplification_identique']
class Prod(BinaryOperator):
priority = 1
operator_name = '*'
math_function=operator.mul
commutative=True
partial_derivative = (lambda x, y:y, lambda x, y:x)
def simplication_de_deux_nombres(self):
if isinstance(self.args[0], Number) and isinstance(self.args[1], Number):
return Number(self.args[0].nbre * self.args[1].nbre)
def simplication_multiplication_par_un(self):
if self.args[0]==1:
return self.args[1]
if self.args[1]==1:
return self.args[0]
def simplication_multiplication_par_zero(self):
if self.args[0]==0 or self.args[1]==0:
return Number(0)
liste_simplification = ['simplication_de_deux_nombres',
'simplication_multiplication_par_un', 'simplication_multiplication_par_zero']
class Div(BinaryOperator):
priority = 2
operator_name = '/'
math_function=operator.truediv
partial_derivative = (lambda x, y:1/y, lambda x, y:-x/(y*y))
class Sub(BinaryOperator):
priority = 0.5
operator_name = '-'
math_function=operator.sub
partial_derivative = (lambda x, y:1, lambda x, y:-1)
class UnitaryOperator(Function):
def display(self, parent_priority=0):
if parent_priority>self.priority:
fmt_str = '({}{})'
else:
fmt_str = '{}{}'
return fmt_str.format(self.unitary_symbol,
self.args[0].display(self.priority))
class Neg(UnitaryOperator):
priority = 0.5
unitary_symbol = "-"
math_function = operator.neg
partial_derivative = (lambda x:-1,)
class MathFunction(Function):
def display(self, *args):
return '{}({})'.format(self.function_name, self.args[0].display())
class Sin(MathFunction):
function_name = 'sin'
math_function = math.sin
partial_derivative = (lambda x:cos(x),)
class Cos(MathFunction):
function_name = 'cos'
math_function = math.cos
partial_derivative = (lambda x:-sin(x),)
sin = Sin
cos = Cos
x = Symbol('x')
sin(2*x).diff(x).simplify()
2 * cos(2 * x)