À l’instar de Geogebra, j’avais envie de mélanger POO et géométrie en Python, histoire de s’amuser un peu.
POO et géométrie en Python: rappels sur la POO et introduction
C’est quoi la POO ?
La POO est la Programmation Orientée Objet. C’est un paradigme de programmation consistant à considérer chaque “notion” comme un “objet”, que l’on va alors appeler une classe (voir cette page par exemple).
En géométrie, ça donne quoi ?
En géométrie, tout est objet: un point, une droite, un cercle, … tout ça constitue une collection d’objets. Et il en est de même pour la dimension 3, bien sûr. Il faut juste voir comment on définit chaque objet… C’est l’objet de cet article.
Ainsi, par la suite, nous sortirons du cadre euclidien pour nous mettre dans un cadre cartésien ( essayez de le répéter 50 fois de suite…. 🙂 ).
POO, géométrie et Python: le point
On va commencer par très simple… En effet, un point peut se définir par ses coordonnées (n’oublions pas que nous sommes en géométrie cartésienne, donc le plan est muni d’un repère, que l’on pas considéré comme orthonormé).
Définition d’un point
Ainsi, on peut définir une classe “Point” ainsi:
class Point: def __init__(self,abscisse,ordonnee): self.x = abscisse self.y = ordonnee
On peut à présent définir un point de la manière suivante :
>>> A = Point(-3,2) >>> A <main.Point object at 0x035A4BB0>
La dernière ligne stipule que A est bien vu comme un objet de type ‘Point’ et que cet objet a été mis en mémoire à l’adresse 0x035A4BB0 de l’ordinateur.
Comme j’ai envie d’aller plus loin, j’aimerais comparer deux points (dire si deux points sont au même endroit ou pas). Je vais donc ajouter les opérations suivantes:
class Point: def __init__(self,abscisse,ordonnee): self.x = abscisse self.y = ordonnee def __eq__(self,other): if self.x == other.x and self.y == other.y: return True else: return False def __hash__(self): return hash(self.x) ^ hash(self.y)
Ainsi, en tapant:
>>> A, B, C = Point(-3,2), Point(2,-3), Point(-3,2) >>> A == B False >>> A == C True
Lorsque l’on définit un opérateur de classe, comme celui de l’égalité avec “__eq__”, il est fortement conseillé d’implémenter un opérateur de hachage (l’opération que l’on y met est à notre convenance, mais son résultat doit être unique afin que le hachage soit unique).
Pour être honnête avec vous, j’ai ici juste repris un opérateur de hachage que j’ai vu à plusieurs endroit, qui semble être performant.
Calcul de la distance entre deux points
Maintenant que l’objet “Point” est défini, on peut implémenter une fonction retournant la distance euclidienne entre les deux points.
def distance(P1,P2): if isinstance(P1,Point) and isinstance(P2,Point): return ((P1.x-P2.x)**2 + (P1.y-P2.y)**2)**0.5 else: return "Les deux arguments doivent être de la classe 'Point'."
qui donne par exemple:
>>> A, B = Point(2,3), Point(5,8) >>> distance(A,B) 5.830951894845301
POO, géométrie et Python: le vecteur
Définition de base de la classe
Maintenant que l’on a définit le point, on peut par exemple définir un vecteur défini par deux points :
class Vecteur: def __init__(self,P1,P2): self.x = P2.x - P1.x self.y = P2.y - P1.y self.norme = (self.x**2 + self.y**2)**0.5
Ainsi, pour définir un vecteur \(\overrightarrow{AB}\), on pourra utiliser la manière suivante:
>>> A, B = Point(2,3), Point(5,8) >>> V = Vecteur(A,B) >>> V.norme 5.830951894845301
Définition des opérations
class Vecteur: def __init__(self,P1,P2): self.x = P2.x - P1.x self.y = P2.y - P1.y self.norme = (self.x**2 + self.y**2)**0.5 def __add__(self,other): return Vecteur(Point(0,0) , Point(self.x + other.x , self.y + other.y) ) def __sub__(self,other): return Vecteur(Point(0,0) , Point(self.x - other.x , self.y - other.y) ) def __mul__(self,other): return self.x*other.x + self.y*other.y def __str__(self): return '({} ; {})'.format(self.coord()[0],self.coord()[1]) def coord(self): return (self.x,self.y) def is_orth(self,other): if self*other == 0: return True else: return False def is_col(self,other): if self.x*other.y == self.y*other.x: return True else: return False
On définit ainsi respectivement:
- l’addition de deux vecteurs
- la différence de deux vecteurs
- le produit scalaire de deux vecteurs
- la fonction d’affichage des coordonnées d’un vecteur
- les coordonnées du vecteur (un tuple de deux éléments)
- un méthode “is_orth” retournant “True” si le vecteur est orthogonal à un autre (mis en argument), et “False” dans le cas contraire
- une méthode “is_col” qui retourne “True” si le vecteur est colinéaire à un autre vecteur (mis en aragument)
>>> A, B, C, D = Point(-3,-2), Point(3,5), Point(-1,4), Point(6,-3) >>> V1, V2 = Vecteur(A,B), Vecteur(C,D) >>> print(V1+V2) (13 ; 0) >>> print(V1-V2) (-1 ; 14) >>> V1*V2 -7 >>> V1.is_orth(V2) False >>> V1.is_col(V2) False
POO, géométrie et Python: la droite
Suivons la même logique que pour la définition d’un vecteur, et définissons une droite à partir de deux points:
class Droite: def __init__(self,P1,P2): self.coef = (P2.y - P1.y) / (P2.x - P1.x) self.ord = P1.y - self.coef*P1.x
Ici, à chaque droite créée sont affectées deux valeurs: son coefficient directeur et son ordonnées à l’origine.
>>> A, B = Point(-1,-2) , Point(3,4) >>> D = Droite(A,B) >>> D.coef, D.ord (1.5, -0.5)
On pourrait alors implémenter un moyen d’afficher son équation réduite :
class Droite: def __init__(self,P1,P2): self.coef = (P2.y - P1.y) / (P2.x - P1.x) self.ord = P1.y - self.coef*P1.x def __str__(self): if self.ord < 0: end = ' - {}' . format(abs(self.ord)) elif self.ord > 0: end = ' + {}' . format(self.ord) else: end = '' return 'y = {}x'.format(self.coef) + end
>>> A, B = Point(-1,-2) , Point(3,4) >>> D = Droite(A,B) >>> print(D) y = 1.5x - 0.5
mais aussi un manière de voir si deux droites sont confondues (en comparant leurs coefficients directeur et ordonnée à l’origine):
def __eq__(self,other): if self.coef == other.coef and self.ord == other.ord: return True else: return False def __hash__(self): return hash(self.coef) ^ hash(self.ord)
On peut aussi imaginer une méthode permettant de voir si deux droites sont parallèles (strictement ou pas):
def is_parallel(self,other,strict=False): if self.coef != other.coef: return False else: # si les deux coef sont égaux if self == other: if strict: return False else: return True else: return True
ou encore une méthode pour voir si deux droites sont perpendiculaires :
def is_perp(self,other): if self.coef * other.coef == -1: return True else: return False
POO, géométrie et Python: le cercle
C’est sans doute l’objet le plus intéressant (de ceux que nous allons voir ici). On peut définir un cercle par son centre et son rayon:
class Cercle: def __init__(self,centre,rayon): self.centre = centre self.rayon = rayon
mais aussi, à l’instar de ce que nous avons fait pour la droite, souhaiter écrire l’équation (réduite) du cercle:
def __str__(self): if self.centre.x < 0: rx = '(x + {})²' . format( abs(self.centre.x) ) elif self.centre.x > 0: rx = '(x - {})²' . format( self.centre.x ) else: rx = 'x²' if self.centre.y < 0: ry = '(y + {})²' . format( abs(self.centre.y) ) elif self.centre.y > 0: ry = '(y - {})²' . format( self.centre.y ) else: ry = 'y²' return rx + ' + ' + ry + ' = {}'.format(self.rayon**2)
>>> A = Point(-3,2) >>> C = Cercle(A,3) >>> print(C) (x + 3)² + (y - 2)² = 9
et implémenter l’opérateur de comparaison:
def __eq__(self,other): if self.centre == other.centre and self.rayon == other.rayon: return True else: return False def __hash__(self): return hash(self.centre.x * self.centre.y) ^ hash(self.rayon)
et voir si deux cercles sont tangents:
def is_tgt(self,other): d = Vecteur(self.centre , other.centre).norme # distance entre les deux centres if self.rayon + other.rayon == d: return True else: return False
>>> A = Point(-5,2) >>> B = Point(2,2) >>> C1 = Cercle(A,3) >>> C2 = Cercle(B,4) >>> C1.is_tgt(C2) True
Intersections
De deux droites
Il serait plutôt cool de pouvoir trouver les intersections de divers objets. On va commencer par deux droites:
def inter(obj1, obj2): if obj1 == obj2: return 'Objets identiques' else: if isinstance(obj1,Droite) and isinstance(obj2,Droite): if obj1.is_parallel(obj2,strict=True): return 'Les deux droites sont strictement parallèles.' else: abscisse = (obj2.ord - obj1.ord)/(obj1.coef - obj2.coef) ordonnee = obj1.coef * abscisse + obj1.ord return (abscisse,ordonnee)
Je teste ici le type des objets: si ce sont des droites alors je regarde avant tout si elles sont strictement parallèles.
>>> A, B, C, D = Point(-3,2), Point(3,-2), Point(-4,-1), Point(5,1) >>> D1, D2 = Droite(A,B), Droite(C,D) >>> inter(D1,D2) (0.12500000000000006, -0.08333333333333337)
De deux cercles
Là, c’est plus chaud… Il a fallu que j’ajoute une méthode à ma classe Cercle que j’ai nommée intersectCC(self,other) [disponible dans le fichier final à télécharger pour les abonné·e·s), et qui retourne les coordonnées des points d’intersection quand les cercles sont concourants. Ainsi, j’ai pu compléter ma fonction inter:
elif isinstance(obj1,Cercle) and isinstance(obj2,Cercle): if obj1 == obj2: return 'Objets identiques.' elif obj1.centre == obj2.centre: return 'Cercles non sécants.' else: return obj1.intersectCC(obj2)
>>> A = Point(-5,2) >>> B = Point(2,2) >>> C1 = Cercle(A,3) >>> C2 = Cercle(B,5) >>> inter(C1,C2) [(-37/14, 3.85576872239523), (-37/14, 0.144231277604774)]
D’une droite et d’un cercle
Là encore, il a fallu que j’ajoute une méthode (cette fois-ci, je l’ai mise dans la classe Droite, histoire de changer) intersectDC(self,cercle) qui retourne les coordonnées des éventuels points d’intersection.
>>> A=Point(-5,2) >>> C1=Cercle(A,3) >>> C,D = Point(-10,2), Point(-3,6) >>> D1=Droite(C,D) >>> inter(C1,D1) [(-7.69554297786382, 3.31683258407782), (-4.76599548367464, 4.99085972361449)]
je veux créer mon propre module qui sera nommé géométrie comme fonction aire de cercle