IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Mathématiques et Python - Exponentiation rapide de nombres réels et de polynômes en Python
Un billet blog de Denis Hulo

Le , par User

0PARTAGES

I. Introduction

Dans un précédent billet, on a parlé des avantages du « diviser pour régner » dans le développement d'un produit de petits polynômes, on souhaite maintenant montrer comment réaliser une exponentiation rapide de nombres réels et de polynômes.

Pour cela, on va d'abord décrire cette méthode à l'aide d'exemples simples, pour ensuite l'implémenter en Python.

II. Principe de l'exponentiation rapide



II-A. Nombres réels

Si on souhaite calculer an, on peut naïvement effectuer n−1 multiplications en réalisant le produit :

an = a × a × ... × a

Cependant, l'exemple suivant montre que l'on peut calculer a32 en effectuant seulement 5 multiplications au lieu de 31 :

a32 = a25 = ((((a2)2)2)2)2

Si l'exposant est différent d'une puissance de 2, avec par exemple a41, on peut alors écrire :

a41 = a × a8 × a32 = a × a23 × a25

En remarquant que a8 est également calculé dans a32, et en conservant sa valeur, on a en fait besoin de seulement 7 multiplications au lieu de 40.

Alors que l'algorithme naïf demande de l'ordre de n multiplications pour calculer an, l'algorithme d'exponentiation rapide a besoin seulement de l'ordre de log2(n) multiplications.

II-B. Polynômes

Une multiplication entre deux polynômes nécessite de multiplier chaque terme du multiplicateur par chaque terme du multiplicande.

Si ces deux polynômes ont n termes, cela exige n2 produits. La multiplication (X + 2) × (X + 2) nécessite ainsi 4 multiplications entre termes.

Si on prend maintenant :

(X + 2)8 = (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2)

En développant le produit de gauche à droite, on obtient successivement :

(X + 2)8 = ((X + 2) × (X + 2)) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2)

La 1re multiplication nécessite donc 4 opérations de multiplication entre termes.

En poursuivant le développement :

(X + 2)8 = ((X2 + 4X + 4) × (X + 2)) × (X + 2) × (X + 2) × (X + 2) × (X + 2) × (X + 2)

On a ainsi besoin jusque là de 4 + 6 opérations de multiplication.

etc..

On obtient finalement 4 + 6 + 8 + 10 + 12 + 14 + 16 = 70 opérations de multiplication en tout pour ce produit.

Considérons maintenant la même puissance de (X + 2) réarrangée :

(X + 2)8 = (X + 2)23 = (((X + 2)2)2)2

Si on effectue le développement en respectant la règle de priorité des parenthèses, on obtient successivement :

(X + 2)8 = (((X + 2)2)2)2

2×2=4 opérations de multiplication entre termes pour la 1re produit.

Puis :

(X + 2)8 = ((X2 + 4X + 4))2)2

3×3=9 opérations de multiplication pour le 2e produit.

Et enfin :

(X + 2)8 = (X4 + 8X3 + 24X2 + 32X + 16)2

5×5=25 opérations pour le dernier produit.

Ce qui nous fait au total 4 + 9 + 25 = 38 opérations de multiplication entre termes au lieu de 70.

III. Implémentation en Python

III-A. Exponentiation rapide de nombres réels

L'algorithme est décrit sur la page Wikipedia :

Soit n un entier strictement supérieur à 1, supposons que l'on sache calculer, pour chaque réel x, toutes les puissances xk de x, pour tout k, tel que 1 ≤ k < n.

  • Si n est pair alors xn = (x2)n/2. Il suffit alors de calculer yn/2 pour y = x2.
  • Si n est impair et n > 1, alors xn = x(x2)(n – 1)/2. Il suffit de calculer y(n – 1)/2 pour y = x2 et de multiplier le résultat par x.


Cette remarque nous amène à l'algorithme récursif suivant qui calcule xn pour un entier strictement positif n :



On obtient ainsi la fonction d'exponentiation rapide de nombres réels :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def puissance(x,n): 
    # exponentiation rapide de x^n 
  
    # si n=1 alors on renvoie x 
    if n==1: 
        return x 
    else: # sinon 
        # si n est pair 
        if (n % 2 == 0): 
            # appel récursif : puissance(x^2,n/2) 
            return puissance(x**2,n//2) 
        else: # sinon si n est impair 
            # appel récursif : x*puissance(x^2,(n-1)/2) 
            return x*puissance(x**2,n//2)

Testons maintenant notre fonction avec ces quelques lignes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# nombre réel 
x = 12 
  
# exposant 
n = 8 
  
# exponentiation du polynôme 
xn =  puissance(x,n) 
  
print("x^n = {0}^{1}".format(x,n)) 
print() 
  
print("x^n = " + str(xn)) 
print() 
  
print("nombre de multiplications de l'ordre de log2({0})".format(n))

Le code affiche :

x^n = 12^8

x^n = 429981696

nombre de multiplications de l'ordre de log2(8)


III-B. Puissance de polynômes

On utilise à nouveau notre classe Polynome, comme dans le billet précédent, en ajoutant un attribut permettant de compter le nombre d'opérations de multiplication entre termes nécessaires pour développer le produit de polynômes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Polynome: 
  
    def __init__(self, liste_coefs=[0], nbre_operations=0): # méthode constructeur de la classe 
  
	# on définit la liste des coefficients du polynôme [a0, a1, ..., an] 
        self.coefs = liste_coefs 
  
        # on initialise le nombre d'opérations de multiplication entre termes 
        self.nombre_operations = nbre_operations 
  
	# suppression si nécessaire des zéros en queue de liste de coefficients. Exemple : [2, 3, 1, 0, 0] -> [2, 3, 1] 
        self.reduire()  
  
    ...

Le code de la méthode permettant de multiplier deux polynômes devient alors :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Polynome: 
  
    ...     
  
    def __mul__(self, other): # méthode permettant de redéfinir l'opérateur « * » pour 2 polynômes : (2 + X) * (1 + 2x) =  1 + 3x + 2x^2 
  
        # initialisation de la liste des coefficients qu'avec des zéros 
        liste_coefs=[0]*(len(self.coefs)+len(other.coefs)-1) # exemple : [0, 0, 0] 
        for i1 in range(len(self.coefs)): # parcours des indices des coefs du polynôme n°1 
            for i2 in range(len(other.coefs)): # parcours des indices des coefs du polynôme n°2 
                # multiplication des coefficients d'indices i1 et i2 
                liste_coefs[i1+i2] = liste_coefs[i1+i2] + self.coefs[i1]*other.coefs[i2] 
  
        poly = Polynome(liste_coefs) # création de l'objet Polynome basé sur la liste 
  
        # ajout du nombre d'opérations de multiplication entre termes effectuées lors de la multiplication entre polynômes 
        poly.compteur_operations = self.compteur_operations + other.compteur_operations +  len(self.coefs)*len(other.coefs) 
  
        return poly # renvoie le polynôme résultat de la multiplication

Rappel important : les objets Polynome de notre classe sont représentés par une liste de coefficients dont la longueur est égale au degré du polynôme + 1:

Par exemple le polynôme X3 + 2 = 2 + 0.X + 0.X2 + X3 sera représenté par [2, 0, 0, 1].

III-B-1. Méthode classique

On doit d'abord réécrire le code de la méthode permettant de développer une puissance de polynôme afin de pouvoir comptabiliser le nombre de multiplications entre termes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Polynome: 
  
    ...     
  
    def __pow__(self, n): # méthode permettant de redéfinir l'opérateur de puissance : self ** n 
  
        if n==0: # si n=0 
            return Polynome([1]) # on renvoie le polynôme égal à 1 
  
        # copie de self initialisé avec le même nombre d'opérations de multiplication entre termes  
        poly = Polynome(self.coefs, self.nombre_operations) 
  
        # copie de self avec un nombre d'opérations de multiplication à 0 
        pol = Polynome(self.coefs)  
  
        for i in range(n-1): # on multiplie n fois poly par pol à l'aide de l'opérateur *             
            poly = poly*pol # équivalent à : poly = poly.__mul__(pol) 
  
        return poly # renvoie le polynôme résultat de l'opération (self ** n)

Testons maintenant l'opérateur de puissance « ** » pour les polynômes avec la méthode classique :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# création du polynôme p = 2 + X = X + 2 
p = Polynome([2,1]) 
  
# exposant 
n = 8 
  
# exponentiation du polynôme 
pn = p**n 
  
print("p^n = ({0})^{1}".format(p,n)) 
print() 
  
print("p^n = " + str(pn)) 
print() 
  
print("nombre d'opérations = " + str(pn.nombre_operations))


Le code affiche :

p^n = (2 + X)^8

p^n = 256 + 1024⋅X + 1792⋅X^2 + 1792⋅X^3 + 1120⋅X^4 + 448⋅X^5 + 112⋅X^6 + 16⋅X^7 + X^8

nombre d'opérations = 70


III-B-2. Exponentiation rapide de polynômes

L'algorithme d'exponentiation rapide de polynômes est le même que celui décrit sur la page Wikipedia pour les nombres réels :

Soit n un entier strictement supérieur à 1, supposons que l'on sache développer, pour chaque polynôme p, toutes les puissances pk de p, pour tout k, tel que 1 ≤ k < n.

  • Si n est pair alors pn = (p2)n/2. Il suffit alors de développer qn/2 pour q = p2.
  • Si n est impair et n > 1, alors pn = p(p2)(n – 1)/2. Il suffit de développer q(n – 1)/2 pour q = p2 et de multiplier le résultat par p.


Cette remarque nous amène à l'algorithme récursif suivant qui développe pn pour un entier strictement positif n :



On peut alors écrire en Python la fonction d'exponentiation rapide de polynômes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def puissance_polynome(p,n): 
    # exponentiation rapide de p^n 
  
    # si n=1 alors on renvoie p 
    if n==1: 
        return p 
    else: # sinon 
        # si n est pair 
        if (n % 2 == 0): 
            # appel récursif : puissance_polynome(p^2,n/2) 
            return puissance_polynome(p**2,n//2) 
        else: # sinon si n est impair 
            # appel récursif : p*puissance_polynome(p^2,(n-1)/2) 
            return p*puissance_polynome(p**2,n//2)

Testons notre fonction avec ces quelques lignes :

Code Python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# création du polynôme p = 2 + X = X + 2 
p = Polynome([2,1]) 
  
# exposant 
n = 8 
  
# exponentiation du polynôme 
pn = puissance_polynome(p,n) 
  
print("p^n = ({0})^{1}".format(p,n)) 
print() 
  
print("p^n = " + str(pn)) 
print() 
  
print("nombre d'opérations = " + str(pn.nombre_operations))


Le code affiche :

p^n = (2 + X)^8

p^n = 256 + 1024⋅X + 1792⋅X^2 + 1792⋅X^3 + 1120⋅X^4 + 448⋅X^5 + 112⋅X^6 + 16⋅X^7 + X^8

nombre d'opérations = 38


III-C. Module complet

On donne pour finir le contenu du module complet :

[CODE=Python]class Polynome:

def __init__(self, liste_coefs=[0], nbre_operations=0): # méthode constructeur de la classe

# on définit la liste des coefficients du polynôme [a0, a1, ..., an]
self.coefs = liste_coefs

# on initialise le nombre d'opérations de multiplication entre termes
self.nombre_operations = nbre_operations

# suppression si nécessaire des zéros en queue de liste de coefficients. Exemple : [2, 3, 1, 0, 0] -> [2, 3, 1]
self.reduire()

def __str__(self): # permet d'afficher le polynôme sous la forme 1 + 2x + 3x^2
s="" # initialisation de la chaîne de caractères
# on vérifie d’abord si le degré du polynôme est nul
if (len(self.coefs)-1==0):
return str(self.coefs[0])
else: # sinon
if self.coefs[0]!=0:
s=str(self.coefs[0]) + " + "
for i in range(1, len(self.coefs)): # parcours des indices des coefficients du polynôme : [a1, a2, ..., an]
if self.coefs[i]!=0: # si le coefficient de degré i n'est pas nul
if self.coefs[i]!=1: # si le coefficient de degré i est différent de 1
s+="{}⋅X^{} + ".format(self.coefs[i],i)
else: s+="X^{} + ".format(i)

# élimination des caractères en trop
s = s[:-3].replace("+ -", "- ").replace("X^1 ","X ").replace(" 1⋅X"," X")
if s[-2:]=="^1": s = s[:-2]
if s[:3]=="1⋅X": s = s[3:]

return s # on retourne l'expression du polynôme

def degre(self): # retourne le degré du polynôme
return (len(self.coefs)-1)

def __add__(self, other): # méthode permettant de redéfinir l'opérateur « + » pour 2 polynômes : (1 + 2x + x^2) + (2 + X) = 2 + 3x + x^2
# p1 = self, p2 = other
if len(other.coefs) >len(self.coefs): # si degré de p2 > degré de p1
liste_coefs = other.coefs[:]; n = len(self.coefs) # on copie les coefs du polynôme de degré le plus élevé et la longueur de la liste de coefs la plus petite.
else: liste_coefs = self.coefs[:]; n = len(other.coefs) # sinon, ...

for i in range(n): # parcours des indices de liste_coefs
liste_coefs[i] = self.coefs[i] + other.coefs[i] # addition des coefficients de degré i

return Polynome(liste_coefs) # renvoie le polynôme résultat de l'addition

def __sub__(self, other): # méthode permettant de redéfinir l'opérateur « + » pour 2 polynômes : (1 + 2x + x^2) + (2 + X) = 2 + 3x + x^2
# p1 = self, p2 = other
if len(other.coefs) >len(self.coefs): # si degré de p2 > degré de p1
liste_coefs = other.coefs[:]; n = len(self.coefs) # on copie les coefs du polynôme de degré le plus élevé et la longueur de la liste de coefs la plus petite.
else: liste_coefs = self.coefs[:]; n = len(other.coefs) # sinon, ...

for i in range(n): # parcours des indices de liste_coefs
liste_coefs[i] = self.coefs[i] - other.coefs[i] # addition des coefficients de degré i

return Polynome(liste_coefs) # renvoie le polynôme résultat de l'addition

def reduire(self):
# tant que le dernier élément de la liste est nul
while round(self.coefs[-1],12) == 0 and len(self.coefs)>1:
self.coefs.pop() # supprimer le dernier élément

for i in range(len(self.coefs)):
self.coefs[i] = round(self.coefs[i],12)

def __mul__(self, other): # méthode permettant de redéfinir l'opérateur « * » pour 2 polynômes : (2 + X) * (1 + 2x) = 1 + 3x + 2x^2

# initialisation de la liste des coefficients qu'avec des zéros
liste_coefs=[0]*(len(self.coefs)+len(other.coefs)-1) # exemple : [0, 0, 0]
for i1 in range(len(self.coefs)): # parcours des indices des coefs du polynôme n°1
for i2 in range(len(other.coefs)): # parcours des indices des coefs du polynôme n°2
# multiplication des coefficients d'indices i1 et i2
liste_coefs[i1+i2] = liste_coefs[i1+i2] + self.coefs[i1]*other.coefs[i2]

poly = Polynome(liste_coefs) # création de l'objet Polynome basé sur la liste

# ajout du nombre d'opérations de multiplication entre termes effectuées lors de la multiplication entre polynômes
poly.nombre_operations = self.nombre_operations + other.nombre_operations + len(self.coefs)*len(other.coefs) #

return poly # renvoie le polynôme résultat de la multiplication

def __pow__(self, n): # méthode permettant de redéfinir l'opérateur de puissance : self ** n

if n==0: # si n=0
return Polynome([1]) # on renvoie le polynôme égal à 1

# copie de self initialisé avec le même nombre d'opérations de multiplication entre termes
poly = Polynome(self.coefs, self.nombre_operations)

# copie de self avec un nombre d'opérations de multiplication à 0
pol = Polynome(self.coefs)

for i in range(n-1): # on multiplie n fois poly par pol à l'aide de l'opérateur *
poly = poly*pol # équivalent à : poly = poly.__mul__(pol)

return poly # renvoie le polynôme résultat de l'opération (self ** n)

def __eq__(poly1, other):[/1]...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !