POO et géométrie en Python

poo géométrie python

POO et géométrie en Python

À 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)]         

Téléchargement du module complet

Les abonné·e·s de mathweb.fr trouveront sur cette page le module mathweb-geometrie.py regroupant toutes les classes et la fonction inter.

Stéphane Pasquet
Stéphane Pasquet

Laissez votre message