Simplifiez les nombres complexes avec Python – Real Python

By | juin 21, 2021

Cours Python en ligne

La plupart des langages de programmation à usage général n'ont pas de support ou un support limité pour nombres complexes. Vos options typiques consistent à apprendre un outil spécialisé comme MATLAB ou à trouver une bibliothèque tierce. Python est une exception rare car il est livré avec des nombres complexes intégrés.

Malgré leur nom, les nombres complexes ne sont pas compliqués ! Ils sont pratiques pour résoudre des problèmes pratiques dont vous aurez un avant-goût dans ce didacticiel. Vous explorerez graphiques vectoriels et analyse de fréquence sonore, mais les nombres complexes peuvent aussi aider à dessiner fractales, par example.

Dans ce didacticiel, vous apprendrez à :

  • Définir des nombres complexes avec littéraux en Python
  • Représenter les nombres complexes dans rectangulaire et polaire coordonnées
  • Utiliser des nombres complexes dans arithmétique expressions
  • Profitez de l'intégration cmath module
  • Traduire formules mathématiques directement au code Python

Si vous avez besoin d'un rappel rapide ou d'une introduction en douceur à la théorie des nombres complexes, vous pouvez regarder la série de vidéos de la Khan Academy. Pour télécharger l'exemple de code utilisé tout au long de ce didacticiel, cliquez sur le lien ci-dessous :

Créer des nombres complexes en Python

Créer et manipuler des nombres complexes en Python n'est pas très différent des autres types de données intégrés, en particulier les types numériques. C'est possible parce que la langue les traite comme des citoyens de première classe. Cela signifie que vous pouvez exprimer des formules mathématiques qui impliquent des nombres complexes avec peu de frais généraux.

Python vous permet d'utiliser des nombres complexes dans des expressions arithmétiques et d'appeler des fonctions dessus comme vous le feriez avec d'autres nombres en Python. Cela conduit à une syntaxe élégante qui se lit presque comme un manuel de mathématiques.

Littéral de nombre complexe

Le moyen le plus rapide de définir un nombre complexe en Python est de taper son littéral directement dans le code source :

Bien que cela ressemble à une formule algébrique, l'expression à droite du signe égal est déjà une valeur fixe qui ne nécessite aucune évaluation supplémentaire. Lorsque vous vérifierez son type, vous confirmerez qu'il s'agit bien d'un nombre complexe :

>>>

>>> taper(z)

En quoi est-ce différent de ajouter deux nombres avec l'opérateur plus ? Un cadeau clair est la lettre j collé au deuxième chiffre, ce qui change complètement le sens de l'expression. Si vous supprimiez la lettre, vous obtiendriez un résultat entier familier à la place :

>>>

>>> z = 3 + 2

>>> taper(z)

Au fait, vous pouvez également utiliser des nombres à virgule flottante pour créer des nombres complexes :

>>>

>>> z = 3.14 + 2,71j
>>> taper(z)

Les littéraux de nombres complexes en Python imitent la notation mathématique, également connue sous le nom de forme standard, les forme algébrique, ou parfois le Forme canonique, d'un nombre complexe. En Python, vous pouvez utiliser soit les minuscules j ou majuscule J dans ces littéraux.

Si vous avez appris les nombres complexes en cours de mathématiques, vous les avez peut-être vus exprimés à l'aide d'un je au lieu d'une j. Si vous êtes curieux de savoir pourquoi Python utilise j à la place de je, vous pouvez développer la section réductible ci-dessous pour en savoir plus.

La notation traditionnelle des nombres complexes utilise la lettre je à la place de j puisqu'il représente le unité imaginaire. Vous pourriez ressentir un léger inconfort avec la convention de Python si vous avez une formation mathématique. Cependant, il y a quelques raisons qui peuvent justifier le choix controversé de Python :

  • C'est une convention déjà adoptée par les ingénieurs pour éviter les collisions de noms avec le courant électrique, qui est désignée par la lettre je.
  • En informatique, la lettre je est souvent utilisé pour la variable d'indexation dans les boucles.
  • La lettre je peut être facilement confondu avec je ou alors 1 dans le code source.

Cela a été soulevé sur le bug tracker de Python il y a plus de dix ans, et le créateur de Python, Guido van Rossum lui-même, a clos le problème avec ce commentaire :

Cela ne sera pas corrigé. D'une part, la lettre "i" ou la majuscule "I" ressemble trop à des chiffres. La façon dont les nombres sont analysés soit par l'analyseur de langage (dans le code source) soit par les fonctions intégrées (int, float, complex) ne doit en aucun cas être localisable ou configurable ; cela demande d'énormes déceptions sur la route. Si vous souhaitez analyser des nombres complexes en utilisant « i » au lieu de « j », vous disposez déjà de nombreuses solutions. (La source)

Alors voilà. À moins que vous ne vouliez commencer à utiliser MATLAB, vous devrez vivre avec l'utilisation j pour désigner vos nombres complexes.

La forme algébrique d'un nombre complexe suit les règles standard de l'algèbre, ce qui est pratique pour effectuer l'arithmétique. Par exemple, l'addition a une propriété commutative, qui vous permet d'échanger l'ordre des deux parties d'un littéral de nombre complexe sans changer sa valeur :

>>>

>>> 3 + 2j == 2j + 3
Vrai

De même, vous pouvez remplacer l'addition par la soustraction dans un littéral de nombre complexe, car le signe moins n'est qu'une notation abrégée pour une forme équivalente :

>>>

>>> 3 - 2j == 3 + (-2j)
Vrai

Un littéral de nombre complexe en Python doit-il toujours comprendre deux nombres ? Peut-il en avoir plus ? Sont-ils commandés ? Pour répondre à ces questions, faisons quelques expériences. Sans surprise, si vous précisez un seul chiffre, sans la lettre j, vous obtiendrez alors un entier normal ou un nombre à virgule flottante :

>>>

>>> z = 3.14
>>> taper(z)

D'autre part, en joignant la lettre j à un littéral numérique le transformera immédiatement en un nombre complexe :

>>>

>>> z = 3.14j
>>> taper(z)

A la rigueur, d'un point de vue mathématique, vous venez de créer un pur nombre imaginaire, mais Python ne peut pas le représenter comme un type de données autonome. Par conséquent, sans l'autre partie, ce n'est qu'un nombre complexe .

Et le contraire ? Pour créer un nombre complexe sans pour autant la partie imaginaire, vous pouvez profiter de zéro et l'ajouter ou le soustraire comme ceci :

>>>

>>> z = 3.14 + 0j
>>> taper(z)

En fait, les deux parties du nombre complexe sont toujours là. Lorsque vous n'en voyez pas, cela signifie qu'il a une valeur de zéro. Voyons ce qui se passe lorsque vous essayez d'ajouter plus de termes dans la somme qu'auparavant :

>>>

>>> 2 + 3j + 4 + 5j
(6+8j)

Cette fois, votre expression n'est plus un littéral car Python l'a évaluée en un nombre complexe comprenant seulement deux parties. N'oubliez pas que les règles de base de l'algèbre s'appliquent aux nombres complexes, donc si vous regroupez des termes similaires et appliquez une addition par composant, vous vous retrouverez avec 6 + 8j.

Remarquez comment Python affiche les nombres complexes par défaut. Leur représentation textuelle contient une paire de parenthèses englobantes, une lettre minuscule j, et pas d'espace. De plus, la partie imaginaire vient en second.

Les nombres complexes qui sont également des nombres imaginaires purs apparaissent sans parenthèses et ne révèlent que leur partie imaginaire :

>>>

>>> 3 + 0j
(3+0j)
>>> 0 + 3j
3j

Cela permet de différencier les nombres imaginaires de la plupart des nombres complexes composés de parties réelles et imaginaires.

complexe() Fonction d'usine

Python a une fonction intégrée, complexe(), que vous pouvez utiliser comme alternative au littéral de nombre complexe :

Sous cette forme, il ressemble à un tuple ou à une paire ordonnée de nombres ordinaires. L'analogie n'est pas si farfelue. Les nombres complexes ont une interprétation géométrique dans le système de coordonnées cartésiennes que vous explorerez un peu. Vous pouvez considérer les nombres complexes comme bidimensionnels.

La fonction fabrique de nombres complexes accepte deux paramètres numériques. Le premier représente le partie réelle, tandis que le second représente le partie imaginaire désigné par la lettre j dans le littéral que vous avez vu plus tôt :

>>>

>>> complexe(3, 2) == 3 + 2j
Vrai

Les deux paramètres sont facultatifs, avec des valeurs par défaut de zéro, ce qui rend moins compliqué la définition de nombres complexes sans la partie imaginaire ou les parties réelle et imaginaire :

>>>

>>> complexe(3) == 3 + 0j
Vrai
>>> complexe() == 0 + 0j
Vrai

La version à argument unique peut être utile dans moulage de type. Par exemple, vous pouvez passer une valeur non numérique comme un littéral de chaîne pour obtenir une valeur correspondante complexe objet. Notez que la chaîne ne peut pas contenir d'espace blanc :

>>>

>>> complexe("3+2j")
(3+2j)

>>> complexe("3 + 2j")
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur de valeur: complex() arg est une chaîne mal formée

Plus tard, vous découvrirez comment rendre vos classes compatibles avec ce mécanisme de casting de type. Fait intéressant, lorsque vous transmettez un nombre complexe à complexe(), vous récupérerez la même instance :

>>>

>>> z = complexe(3, 2)
>>> z est complexe(z)
Vrai

Cela est cohérent avec le fonctionnement des autres types de nombres en Python, car ils sont tous immuable. Faire un copie distincte d'un nombre complexe, vous devez appeler à nouveau la fonction avec les deux arguments ou déclarer une autre variable avec le littéral du nombre complexe :

>>>

>>> z = complexe(3, 2)
>>> z est complexe(3, 2)
Faux

Lorsque vous fournissez deux arguments à la fonction, ils doivent toujours être des nombres, tels que entier, flotter, ou alors complexe. Sinon, vous obtiendrez une erreur d'exécution. Techniquement parlant, bool est une sous-classe de entier, donc ça fonctionnera aussi :

>>>

>>> complexe(Faux, Vrai)  # Booléens, comme complex(0, 1)
1j

>>> complexe(3, 2)  # Entiers
(3+2j)

>>> complexe(3.14, 2,71)  # Nombres à virgule flottante
(3.14+2.71j)

>>> complexe("3", "2")  # chaînes
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: complex() ne peut pas prendre le deuxième argument si le premier est une chaîne

Les choses deviennent apparemment plus bizarres lorsque vous fournissez le complexe() fonction d'usine avec des nombres complexes comme arguments. Si vous ne fournissez que le premier argument, cependant, il se comportera comme un proxy comme avant :

>>>

>>> complexe(complexe(3, 2))
(3+2j)

Cependant, lorsque deux arguments sont présents et qu'au moins l'un d'entre eux est un nombre complexe, vous obtiendrez des résultats qui peuvent être difficiles à expliquer à première vue :

>>>

>>> complexe(1, complexe(3, 2))
(-1+3j)

>>> complexe(complexe(3, 2), 1)
(3+3j)

>>> complexe(complexe(3, 2), complexe(3, 2))
(1+5j)

Pour obtenir les réponses, jetons un coup d'œil à la docstring de la fonction d'usine ou à la documentation en ligne, qui explique ce qui se passe sous le capot lorsque vous appelez complexe (réel, image):

Renvoie un nombre complexe avec la valeur réel + image*1j ou convertir une chaîne ou un nombre en nombre complexe. (La source)

Dans cette explication, réel et image sont les noms des arguments de la fonction. Le deuxième argument est multiplié par l'unité imaginaire j, et le résultat est ajouté au premier argument. Ne vous inquiétez pas si cela n'a toujours aucun sens. Vous pourrez revenir à cette partie lorsque vous aurez lu sur l'arithmétique des nombres complexes. Les règles que vous apprendrez rendront cela simple.

Quand voudriez-vous utiliser le complexe() fonction d'usine sur le littéral? Cela dépend, mais appeler la fonction peut être plus pratique lorsque vous traitez des données générées dynamiquement, par exemple.

Apprendre à connaître les nombres complexes Python

En mathématiques, les nombres complexes sont un sur-ensemble de nombres réels, ce qui signifie que chaque nombre réel est également un nombre complexe dont la partie imaginaire est égale à zéro. Python modélise cette relation à travers un concept appelé le tour numérique, décrit dans le PEP 3141 :

>>>

>>> importer Nombres
>>> est une sous-classe(Nombres.Réel, Nombres.Complexe)
Vrai

Le intégré Nombres module définit une hiérarchie de types numériques à travers cours abstraits qui peut être utilisé pour la vérification de type et la classification des numéros. Par exemple, pour déterminer si une valeur appartient à un ensemble spécifique de nombres, vous pouvez appeler isinstance() dessus:

>>>

>>> isinstance(3.14, Nombres.Complexe)
Vrai
>>> isinstance(3.14, Nombres.Intégral)
Faux

La valeur à virgule flottante 3.14 est un nombre réel qui se trouve être également un nombre complexe mais pas un nombre entier. Notez que vous ne pouvez pas utiliser de types intégrés directement dans un tel test :

>>>

>>> isinstance(3.14, complexe)
Faux

La différence entre complexe et nombres.Complexe est qu'ils appartiennent à des branches séparées dans l'arbre hiérarchique de type numérique, et ce dernier est une classe de base abstraite sans aucune implémentation :

Hiérarchie de types pour les nombres en Python
Hiérarchie de types pour les nombres en Python

Les classes de base abstraites, qui sont indiquées en rouge sur le diagramme ci-dessus, peuvent contourner le mécanisme de vérification d'héritage régulier en enregistrant des classes non liées comme leurs sous-classes virtuelles. C'est pourquoi une valeur à virgule flottante dans l'exemple semble être une instance de nombres.Complexe mais non complexe.

Accéder aux parties réelles et imaginaires

Pour obtenir les parties réelles et imaginaires d'un nombre complexe en Python, vous pouvez atteindre le correspondant .réel et .image les attributs:

>>>

>>> z = 3 + 2j
>>> z.réel
3.0
>>> z.image
2.0

Les deux propriétés sont lecture seulement parce que les nombres complexes sont immuables, essayer d'attribuer une nouvelle valeur à l'un d'eux échouera :

>>>

>>> z.réel = 3.14
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
AttributeError: attribut en lecture seule

Étant donné que chaque nombre en Python est un type plus spécifique d'un nombre complexe, les attributs et les méthodes définis dans nombres.Complexe sont également disponibles dans tous les types numériques, y compris entier et flotter:

>>>

>>> X = 42
>>> X.réel
42
>>> X.image
0

La partie imaginaire de ces nombres est toujours zéro.

Calcul du conjugué d'un nombre complexe

Les nombres complexes Python n'ont que trois membres publics. Séparé de .réel et .image propriétés, ils exposent les .conjuguer() méthode, qui inverse le signe de la partie imaginaire :

>>>

>>> z = 3 + 2j
>>> z.conjuguer()
(3-2j)

Pour les nombres dont la partie imaginaire est égale à zéro, cela n'aura aucun effet :

>>>

>>> X = 3.14
>>> X.conjuguer()
3.14

Cette opération est son propre inverse, donc l'appeler deux fois vous obtiendrez le numéro d'origine avec lequel vous avez commencé :

>>>

>>> z.conjuguer().conjuguer() == z
Vrai

Bien qu'il puisse sembler de peu de valeur, le conjugué complexe possède quelques propriétés arithmétiques utiles qui peuvent aider à calculer la division de deux nombres complexes avec un stylo et du papier, entre autres choses.

Arithmétique des nombres complexes

Depuis complexe est un type de données natif en Python, vous pouvez brancher des nombres complexes dans expressions arithmétiques et appelez la plupart des fonctions intégrées sur eux. Des fonctions plus avancées pour les nombres complexes sont définies dans le cmath module, qui fait partie de la bibliothèque standard. Vous en aurez une introduction dans une partie ultérieure de ce didacticiel.

Pour l'instant, se souvenir d'une seule règle vous permettra d'appliquer vos connaissances en arithmétique à l'école primaire pour calculer des opérations de base impliquant des nombres complexes. La règle à retenir est la définition du unité imaginaire, qui vérifie l'équation suivante :

La définition de l'unité imaginaire

Ça n'a pas l'air bien quand on pense à j comme un nombre réel, mais ne paniquez pas. Si vous l'ignorez un instant et remplacez chaque occurrence de j2 avec -1 comme si c'était une constante, alors vous serez réglé. Voyons comment cela fonctionne.

Une addition

La somme de deux nombres complexes ou plus équivaut à additionner leurs parties réelles et imaginaires par composant :

>>>

>>> z1 = 2 + 3j
>>> z2 = 4 + 5j
>>> z1 + z2
(6+8j)

Plus tôt, vous avez découvert que les expressions algébriques composées de nombres réels et imaginaires suivent les règles standard de l'algèbre. Lorsque vous l'écrivez algébriquement, vous pourrez appliquer la propriété distributive et simplifier la formule en factorisant et en regroupant les termes courants :

Ajouter des nombres complexes

Python promeut automatiquement les opérandes au complexe type de données lorsque vous ajoutez des valeurs de types numériques mixtes :

>>>

>>> z = 2 + 3j
>>> z + 7  # Ajouter un complexe à un entier
(9+3j)

Cela est similaire à la conversion implicite de entier à flotter, que vous connaissez peut-être mieux.

Soustraction

La soustraction de nombres complexes est analogue à leur addition, ce qui signifie que vous pouvez également l'appliquer par élément :

>>>

>>> z1 = 2 + 3j
>>> z2 = 4 + 5j
>>> z1 - z2
(-2-2j)

Contrairement à la somme, cependant, l'ordre des opérandes est significatif et donne des résultats différents, tout comme avec les nombres réels :

>>>

>>> z1 + z2 == z2 + z1
Vrai
>>> z1 - z2 == z2 - z1
Faux

Vous pouvez également utiliser le opérateur moins unaire (-) faire la négative d'un nombre complexe :

>>>

>>> z = 3 + 2j
>>> -z
(-3-2j)

Cela inverse à la fois les parties réelle et imaginaire du nombre complexe.

Multiplication

Le produit de deux nombres complexes ou plus devient plus intéressant :

>>>

>>> z1 = 2 + 3j
>>> z2 = 4 + 5j
>>> z1 * z2
(-7+22j)

Comment diable vous êtes-vous retrouvé avec un négatif nombre parmi les seuls positifs ? Pour répondre à cette question, il faut rappeler la définition de l'unité imaginaire et réécrire l'expression en termes de parties réelles et imaginaires :

Multiplication de nombres complexes

La principale observation à faire est que j fois j donne j2, qui peut être remplacé par -1. Cela inverse le signe de l'une des commandes, tandis que le reste des règles reste exactement le même qu'auparavant.

Division

La division de nombres complexes peut sembler intimidante au premier contact :

>>>

>>> z1 = 2 + 3j
>>> z2 = 4 + 5j
>>> z1 / z2
(0.5609756097560976+0.0487804878048781j)

Croyez-le ou non, vous pouvez obtenir le même résultat en utilisant rien de plus qu'un stylo et du papier ! (D'accord, une calculatrice peut vous éviter des maux de tête.) Lorsque les deux nombres sont exprimés sous leur forme standard, l'astuce consiste à multiplier le numérateur et le dénominateur par le conjugué de ce dernier :

Division de nombres complexes

Le dénominateur devient un carré module du diviseur. Vous en apprendrez plus sur le module des nombres complexes plus tard. Lorsque vous continuez à dériver la formule, voici ce que vous obtiendrez :

Division de nombres complexes

Notez que les nombres complexes ne prennent pas en charge la division par étage, également appelée division entière :

>>>

>>> z1 // z2
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: ne peut pas prendre la parole de nombre complexe.

>>> z1 // 3.14
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: ne peut pas prendre la parole de nombre complexe.

Cela fonctionnait auparavant dans Python 2.x, mais a ensuite été supprimé pour éviter toute ambiguïté.

Exponentiation

Vous pouvez élever des nombres complexes à une puissance en utilisant le binaire opérateur d'exponentiation (**) ou l'intégré pow() mais pas celui défini dans le math module, qui ne prend en charge que les valeurs à virgule flottante :

>>>

>>> z = 3 + 2j

>>> z**2
(5+12j)

>>> pow(z, 2)
(5+12j)

>>> importer math
>>> math.pow(z, 2)
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: ne peut pas convertir complexe en flottant

Les deux base et le exposant peut être de n'importe quel type numérique, y compris entier, virgule flottante, imaginaire ou complexe :

>>>

>>> 2**z
(1.4676557979464138+7.86422192328995j)

>>> z**2
(5+12j)

>>> z**0,5
(1.8173540210239707+0.5502505227003375j)

>>> z**3j
(-0.13041489185767086-0.11115341486478239j)

>>> z**z
(-5.409738793917679-13.410442370412747j)

L'exponentiation manuelle des nombres complexes devient très difficile lorsqu'ils sont exprimés sous leur forme standard. Il est beaucoup plus pratique de réécrire le nombre dans le forme trigonométrique et calculer la puissance en utilisant une trigonométrie de base. Si vous êtes intéressé par les mathématiques impliquées, consultez la formule de De Moivre, qui vous permet de le faire.

Utilisation de nombres complexes Python comme vecteurs 2D

Vous pouvez visualiser les nombres complexes comme points ou alors vecteurs sur un plan euclidien dans le cartésien ou rectangulaire système de coordonnées:

Plan complexe

L'axe X sur le plan complexe, également connu sous le nom de plan de Gauss ou alors Diagramme d'Argand, représente la partie réelle d'un nombre complexe, tandis que l'axe Y représente sa partie imaginaire.

Ce fait conduit à l'une des caractéristiques les plus intéressantes de la complexe type de données en Python, qui incarne gratuitement une implémentation rudimentaire d'un vecteur bidimensionnel. Bien que toutes les opérations ne fonctionnent pas de la même manière dans les deux, les vecteurs et les nombres complexes partagent de nombreuses similitudes.

Obtenir les coordonnées

Le Triangle des Bermudes est une région légendaire connue pour ses phénomènes paranormaux qui s'étendent sur la pointe sud de la Floride, Porto Rico et la petite île des Bermudes. Ses sommets sont approximativement désignés par les trois grandes villes dont les coordonnées géographiques sont les suivantes :

  1. Miami : 25° 45' 42,054" N, 80° 11' 30,438" O
  2. San Juan: 18° 27' 58,8" N, 66° 6' 20,598" O
  3. Hamilton : 32° 17' 41,64" N, 64° 46' 58,908" O

Après avoir converti ces coordonnées en degrés décimaux, vous obtiendrez deux nombres à virgule flottante pour chaque ville. Vous pouvez utiliser le complexe type de données pour stocker des paires ordonnées de nombres. Depuis le latitude est la coordonnée verticale et le longitude est l'horizontale, il peut être plus pratique de les inverser pour suivre l'ordre traditionnel des coordonnées cartésiennes :

miami_fl = complexe(-80.191788, 25.761681)
San Juan = complexe(-66.105721, 18.466333)
hamilton = complexe(-64.78303, 32.2949)

Les valeurs de longitude négatives représentent l'hémisphère ouest, tandis que les valeurs de latitude positives représentent l'hémisphère nord.

Gardez à l'esprit que ce sont coordonnées sphériques. Pour les projeter correctement sur un plan plat, vous devez tenir compte de la courbure de la Terre. L'une des premières projections cartographiques utilisées en cartographie était la projection Mercator, qui aidait les marins à naviguer sur leurs navires. Mais ignorons tout cela et supposons que les valeurs sont déjà exprimées dans le système de coordonnées rectangulaires.

Lorsque vous tracez les nombres sur un plan complexe, vous obtenez une représentation approximative du triangle des Bermudes :

Le triangle de Bermudes

Dans les documents d'accompagnement, vous trouverez un Jupyter Notebook interactif qui trace le triangle des Bermudes à l'aide de la bibliothèque Matplotlib. Pour télécharger le code source et les matériaux de ce didacticiel, cliquez sur le lien ci-dessous :

Si vous n'aimez pas appeler le complexe() fonction d'usine, vous pouvez créer un alias de type avec un nom mieux adapté ou utiliser la forme littérale d'un nombre complexe pour économiser quelques frappes :

Coordonnées de la ville = complexe
miami_fl = Coordonnées de la ville(-80.191788, 25.761681)
miami_fl = -80.191788 + 25.761681j

Si vous aviez besoin de regrouper plus d'attributs sur une ville, vous pouvez utiliser un tuple nommé ou une classe de données ou créer une classe personnalisée.

Calcul de la magnitude

le ordre de grandeur, également connu sous le nom de module ou alors rayon d'un nombre complexe, est la longueur du vecteur qui le représente sur un plan complexe :

Nombre complexe comme vecteur

Vous pouvez le calculer à partir du théorème de Pythagore en prenant la racine carrée de la somme de la partie réelle au carré et de la partie imaginaire au carré :

Calcul de la magnitude complexe

On pourrait penser que Python vous laisserait calculer la longueur d'un tel vecteur avec la fonction intégrée len(), mais ce n'est pas le cas. Pour obtenir la magnitude d'un nombre complexe, vous devez appeler une autre fonction globale nommée abdos(), qui est généralement utilisé pour calculer le valeur absolue d'un nombre :

>>>

>>> longueur(3 + 2j)
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: l'objet de type 'complex' n'a pas de len()

>>> abdos(3 + 2j)
3.605551275463989

Cette fonction supprime le signe des entiers que vous transmettez, mais pour les nombres complexes, elle renvoie la magnitude ou la longueur du vecteur :

>>>

>>> abdos(-42)
42

>>> z = 3 + 2j

>>> abdos(z)
3.605551275463989

>>> de math importer carré
>>> carré(z.réel**2 + z.image**2)
3.605551275463989

Vous vous souvenez peut-être d'une section précédente qu'un nombre complexe multiplié par son conjugué produit sa grandeur au carré.

Trouver la distance entre deux points

Trouvons le Triangle des Bermudes centre géométrique et les distances à elle des trois villes qui forment ses limites. Tout d'abord, vous devez additionner toutes les coordonnées et diviser le résultat par leur nombre pour faire la moyenne :

centre_géométrique = somme(miami_fl, San Juan, hamilton) / 3

Cela vous donnera un point situé dans l'océan Atlantique, quelque part dans le triangle :

Centre géométrique du Triangle des Bermudes

Vous pouvez maintenant créer des vecteurs ancrés dans les villes et dirigés vers le centre géométrique du triangle. Les vecteurs sont créés en soustrayant la source du point cible :

v1 = centre_géométrique - miami_fl
v2 = centre_géométrique - San Juan
v3 = centre_géométrique - hamilton

Puisque vous soustrayez des nombres complexes, chaque vecteur est également un nombre complexe composé de deux parties. Pour obtenir vos distances, calculez la magnitude de chaque vecteur :

>>>

>>> abdos(v1)
9.83488994681275

>>> abdos(v2)
8.226809506084367

>>> abdos(v3)
8.784732429678444

Ces longueurs vectorielles ne reflètent pas des distances significatives mais sont de bonnes approximations pour un exemple de jouet comme celui-ci. Pour représenter des résultats précis en unités tangibles, vous devez d'abord convertir les coordonnées sphériques en coordonnées rectangulaires ou calculer la distance à l'aide de la méthode des grands cercles.

Traduction, retournement, mise à l'échelle et rotation

Cela pourrait vous déranger que le triangle apparaisse dans le deuxième quadrant du système de coordonnées cartésiennes. Déplaçons-le pour que son centre géométrique s'aligne avec l'origine. Les trois sommets seront traduit par la longueur du vecteur indiqué par le centre géométrique mais en sens inverse :

Triangle = miami_fl, San Juan, hamilton
décalage = -centre_géométrique
centre_triangle = [[[[sommet + décalage pour sommet dans Triangle]

Notez que vous ajoutez deux nombres complexes ensemble, ce qui effectue leur addition par élément. Il s'agit d'une transformation affine car elle ne modifie pas la forme du triangle ni le placement relatif de ses sommets :

Traduction du Triangle des Bermudes

Une réflexion miroir du triangle autour de l'axe réel ou imaginaire nécessite d'inverser la composante respective dans ses sommets. Par exemple, pour retourner horizontalement, vous devrez utiliser le négatif de la partie réelle, qui correspond à la direction horizontale. Pour le retourner verticalement, vous prendrez le négatif de la partie imaginaire :

renversé_horizontalement = [[[[complexe(-v.réel, v.image) pour v dans centre_triangle]
renversé_verticalement = [[[[complexe(v.réel, -v.image) pour v dans centre_triangle]

Ce dernier est essentiellement le même que le calcul d'un nombre complexe conjugué, vous pouvez donc appeler .conjuguer() sur chaque sommet directement pour faire le travail à votre place :

renversé_verticalement = [[[[v.conjuguer() pour v dans centre_triangle]

Naturellement, rien ne vous empêche d'appliquer la symétrie dans l'un ou l'autre sens ou dans les deux sens simultanément. Dans un tel cas, vous pouvez utiliser l'opérateur moins unaire devant le nombre complexe pour inverser ses parties réelle et imaginaire :

flipped_in_both_directions = [[[[-v pour v dans centre_triangle]

Allez-y et jouez avec les différentes combinaisons de flips à l'aide du cahier interactif Jupyter disponible dans les documents téléchargeables. Voici à quoi ressemblera le triangle lorsque vous le retournerez le long des deux axes :

Symétrie du Triangle des Bermudes

Mise à l'échelle est similaire à la traduction, mais au lieu d'ajouter un décalage, vous allez multiplier chaque sommet par un facteur constant, qui doit être un réel numéro:

triangle_échelle = [[[[1.5*sommet pour sommet dans centre_triangle]

Cela a pour résultat de multiplier les deux composants de chaque nombre complexe par la même quantité. Il devrait étirer votre triangle des Bermudes, le faisant paraître plus grand sur l'intrigue :

Triangle des Bermudes à l'échelle

Multiplier les sommets du triangle par un autre complexe nombre, d'autre part, a pour effet de tournant autour de l'origine du système de coordonnées. C'est très différent de la façon dont vous multipliez généralement les vecteurs les uns par les autres. Par exemple, un produit scalaire de deux vecteurs donnera un scalaire, tandis que leur produit vectoriel renvoie un nouveau vecteur dans l'espace tridimensionnel, qui est perpendiculaire à la surface qu'ils définissent.

Lorsque vous multipliez les sommets par l'unité imaginaire, le triangle fait pivoter de 90 ° dans le sens inverse des aiguilles d'une montre. Si vous continuez à le répéter, vous finirez par arriver là où vous avez commencé :

Rotation du Triangle des Bermudes

Comment trouvez-vous un nombre complexe spécifique qui fera pivoter un autre nombre complexe selon l'angle souhaité lorsque les deux sont multipliés ? Tout d'abord, regardez le tableau suivant, qui résume les rotations consécutives de 90° :

Rotation à 90° Angle total Formule Exposant Valeur
0 z j0 1
1 90° z × j j1 j
2 180° z × j × j j2 -1
3 270° z × j × j × j j3 j
4 360° z × j × j × j × j j4 1
5 450° z × j × j × j × j × j j5 j
6 540° z × j × j × j × j × j × j j6 -1
7 630° z × j × j × j × j × j × j × j j7 j
8 720° z × j × j × j × j × j × j × j × j j8 1

Lorsque vous exprimez la multiplication répétée par j en termes d'exposants entiers positifs, alors un modèle émerge. Remarquez comment élever l'unité imaginaire aux puissances suivantes lui fait parcourir les mêmes valeurs à plusieurs reprises. Vous pouvez extrapoler cela sur des exposants fractionnaires et vous attendre à ce qu'ils correspondent aux angles intermédiaires.

Par exemple, l'exposant à mi-chemin de la première rotation est égal à 0,5 et représente un angle de 45° :

Rotation d'un nombre complexe

Donc, si vous savez qu'une puissance de un représente l'angle droit et que tout ce qui se trouve entre les échelles est proportionnel, vous pouvez alors dériver cette formule générique pour des rotations arbitraires :

déf tourner(z: complexe, degrés: flotter) -> complexe:
    revenir z * 1j**(degrés/90)

Notez que la rotation devient plus naturelle lorsque vous exprimez vos nombres complexes en coordonnées polaires, qui décrivent déjà l'angle. Vous pourrez alors profiter de la forme exponentielle pour simplifier les calculs :

Il existe deux façons de faire pivoter un nombre à l'aide de coordonnées polaires :

importer math, cmath

déf rotation1(z: complexe, degrés: flotter) -> complexe:
    rayon, angle = cmath.polaire(z)
    revenir cmath.rectifier(rayon, angle + math.radians(degrés))

déf rotation2(z: complexe, degrés: flotter) -> complexe:
    revenir z * cmath.rectifier(1, math.radians(degrés))

Vous pouvez additionner des angles ou multiplier votre nombre complexe par un vecteur unitaire.

Vous en apprendrez plus sur ceux-ci dans la section suivante.

Exploration du module mathématique pour les nombres complexes : cmath

Vous avez déjà vu que certaines fonctions intégrées comme abdos() et pow() accepter les nombres complexes, tandis que d'autres ne le font pas. Par exemple, vous ne pouvez pas rond() un nombre complexe car une telle opération n'a pas de sens :

>>>

>>> rond(3 + 2j)
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: le type complexe ne définit pas la méthode __round__

De nombreuses fonctions mathématiques avancées telles que trigonométrique, hyperbolique, ou alors logarithmique les fonctions sont disponibles dans la bibliothèque standard. Malheureusement, même si vous savez tout sur le Python math module, cela n'aidera pas car aucune de ses fonctions ne prend en charge les nombres complexes. Vous devrez le combiner avec le cmath module, qui définit les fonctions correspondantes pour les nombres complexes.

le cmath module redéfinit toutes les constantes à virgule flottante de math pour qu'ils soient à portée de main sans avoir besoin d'importer les deux modules :

>>>

>>> importer math, cmath
>>> pour Nom dans "e", "pi", "tau", "nan", "inf":
...     imprimer(Nom, obtenir(math, Nom) == obtenir(cmath, Nom))
...
e Vrai
pi vrai
tau vrai
nan Faux
inf Vrai

Noter que nan est une valeur spéciale qui n'est jamais égale à autre chose, y compris elle-même ! C'est pourquoi vous voyez un solitaire Faux dans la sortie ci-dessus. En plus de ceux-ci, cmath fournit deux contreparties complexes pour NaN (pas un nombre) et l'infini, les deux n'ayant aucune partie réelle :

>>>

>>> de cmath importer nanj, infj
>>> nanj.réel, nanj.image
(0,0, nan)
>>> infj.réel, infj.image
(0,0, inf)

Il y a environ la moitié moins de fonctions dans cmath comme il y en a dans la norme math module. La plupart d'entre eux imitent le comportement d'origine, mais quelques-uns sont uniques aux nombres complexes. They will let you do the conversion between two coordinate systems that you’ll explore in this section.

Converting Between Rectangular and Polar Coordinates

Geometrically, you can look at a complex number twofold. On the one hand, it’s a point whose horizontal and vertical distances from the origin uniquely identify its location. These are known as rectangular coordinates comprising the real and imaginary parts.

On the other hand, you can describe the same point in polar coordinates that also let you find it unambiguously with two distances:

  1. Radial distance is the length of the radius measured from the origin.
  2. Angular distance is the angle measured between the horizontal axis and the radius.

le radius, also known as the modulus, corresponds to the complex number’s magnitude, or the vector’s length. The angle is commonly referred to as the phase or argument of a complex number. It’s useful to express the angle in radians rather than degrees when working with trigonometric functions.

Here’s a depiction of a complex number in both coordinate systems:

Polar Coordinates

Therefore, a point (3, 2) in the Cartesian coordinate system has a radius of approximately 3.6 and an angle of about 33.7°, or roughly π over 5.4 radians.

The conversion between the two coordinate systems is made possible with a couple of functions buried in the cmath module. Specifically, to get the polar coordinates of a complex number, you must pass it to cmath.polar():

>>>

>>> import cmath
>>> cmath.polar(3 + 2j)
(3.605551275463989, 0.5880026035475675)

It will return a tuple, where the first element is the radius and the second element is the angle in radians. Note that the radius has the same value as the magnitude, which you can calculate by calling abs() on your complex number. Conversely, if you were only interested in getting the angle of a complex number, then you could call cmath.phase():

>>>

>>> z = 3 + 2j

>>> abs(z)  # Magnitude is also the radial distance
3.605551275463989

>>> import cmath
>>> cmath.phase(3 + 2j)
0.5880026035475675

>>> cmath.polar(z) == (abs(z), cmath.phase(z))
True

The angle can be obtained using basic trigonometry since the real part, the imaginary part, and the magnitude together form a right triangle:

Using the Inverse Trigonometric Functions

You can use the inverse trigonometric functions, such as arcsine, either from math or cmath, but the latter will produce complex values with the imaginary part equal to zero:

>>>

>>> z = 3 + 2j

>>> import math
>>> math.acos(z.real / abs(z))
0.5880026035475675
>>> math.asin(z.imag / abs(z))
0.5880026035475676
>>> math.atan(z.imag / z.real)  # Prefer math.atan2(z.imag, z.real)
0.5880026035475675

>>> import cmath
>>> cmath.acos(z.real / abs(z))
(0.5880026035475675-0j)

There’s one small detail to be careful about when using the arctangent function, though, which led many programming languages to develop an alternative implementation called atan2(). Calculating the ratio between the imaginary and the real part can sometimes produce a singularity due to, for instance, division by zero. Moreover, the individual signs of the two values are lost in the process, making it impossible to tell the angle with certainty:

>>>

>>> import math

>>> math.atan(1 / 0)
Traceback (most recent call last):
  File "", line 1, in 
ZeroDivisionError: division by zero

>>> math.atan2(1, 0)
1.5707963267948966

>>> math.atan(1 / 1) == math.atan(-1 / -1)
True

>>> math.atan2(1, 1) == math.atan2(-1, -1)
Faux

Notice how atan() fails to recognize two different points located in the opposite quadrants of the coordinate system. D'autre part, atan2() expects two arguments instead of one to preserve the individual signs before dividing one by another and avoids other problems as well.

To get degrees instead of radians, you can make the necessary conversion using the math module again:

>>>

>>> import math
>>> math.degrees(0.5880026035475675)  # Radians to degrees
33.690067525979785
>>> math.radians(180)  # Degrees to radians
3.141592653589793

Reversing the process—that is, converting polar to rectangular coordinates—relies on another function. However, you can’t just pass the same tuple that you got from cmath.polar() since cmath.rect() expects two separate arguments:

>>>

>>> cmath.rect(cmath.polar(3 + 2j))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: rect expected 2 arguments, got 1

It’s a good idea to unpack the tuple first when doing an assignment and give those elements more descriptive names. Now you can call cmath.rect() correctly:

>>>

>>> radius, angle = cmath.polar(3 + 2j)
>>> cmath.rect(radius, angle)
(3+1.9999999999999996j)

You might encounter rounding errors along the way while Python makes the calculations. Behind the scenes, it calls the trigonometric functions to retrieve the real and imaginary parts:

>>>

>>> import math
>>> radius*(math.cos(angle) + math.sin(angle)*1j)
(3+1.9999999999999996j)

>>> import cmath
>>> radius*(cmath.cos(angle) + cmath.sin(angle)*1j)
(3+1.9999999999999996j)

Again, it doesn’t matter whether you use math or cmath in this case as the results will be identical.

Representing Complex Numbers Differently

Regardless of the coordinate system, you can express the same complex number in a few mathematically equivalent forms:

  • Algebraic (standard)
  • Geometric
  • Trigonometric
  • Exponential

This list isn’t exhaustive as there are more representations, such as the matrix representation of complex numbers.

Having the choice lets you pick the most convenient one to tackle a given problem. For example, you’re going to need the exponential form to calculate discrete Fourier transform in an upcoming section. Using this form is also suitable for multiplying and dividing complex numbers.

Here’s a quick rundown of the individual complex number forms and their coordinates:

Form Rectangular Polar
Algebraic z = x + yj
Geometric z = (x, y) z = (r, φ)
Trigonometric z = |z|(cos(x/|z|) + jsin(y/|z|)) z = r(cos(φ) + jsin(φ))
Exponential z = |z|eatan2(y/x)j z = r(ejφ)

The algebraic form is native to Python when you specify complex numbers using their literals. You can also view them as points on a Euclidean plane in the Cartesian or polar coordinate systems. While there aren’t separate representations for the trigonometric or exponential form in Python, you can verify if mathematical principles hold.

For example, plugging in Euler’s formula to the trigonometric form will turn it into the exponential one. You can either call the cmath module’s exp() or raise the e constant to a power to get the same result:

>>>

>>> import cmath

>>> algebraic = 3 + 2j
>>> geometric = complex(3, 2)
>>> radius, angle = cmath.polar(algebraic)
>>> trigonometric = radius * (cmath.cos(angle) + 1j*cmath.sin(angle))
>>> exponential = radius * cmath.exp(1j*angle)

>>> for numéro in algebraic, geometric, trigonometric, exponential:
...     imprimer(format(numéro, "g"))
...
3+2j
3+2j
3+2j
3+2j

All forms are indeed different ways of encoding the same number. However, you can’t compare them directly because of the rounding errors that may occur in the meantime. Use cmath.isclose() for a safe comparison or format() the numbers as strings appropriately. You’ll find out how to format such strings in the upcoming section.

The explanation of why different forms of a complex number are equivalent requires calculus and goes far beyond the scope of this tutorial. However, if you’re interested in math, then you’ll find the connections between different fields of mathematics that are exhibited by complex numbers to be fascinating.

Dissecting a Complex Number in Python

You’ve already learned a bunch about Python complex numbers and have seen preliminary examples. However, before moving further, it’s worthwhile to cover some final topics. In this section, you’re going to look into comparing complex numbers, formatting strings that contain them, and more.

Testing Equality of Complex Numbers

Mathematically, two complex numbers are equal when they have identical values irrespective of the adopted coordinate system. However, converting between polar and rectangular coordinates typically introduces rounding errors in Python, so you need to watch out for minute differences when comparing them.

For example, when you consider a point on a unit circle whose radius is equal to one and is tilted at 60°, then the trigonometry works out nicely, making the conversion with pen and paper straightforward:

>>>

>>> import math, cmath

>>> z1 = cmath.rect(1, math.radians(60))
>>> z2 = complex(0,5, math.sqrt(3)/2)

>>> z1 == z2
Faux

>>> z1.real, z2.real
(0.5000000000000001, 0.5)
>>> z1.imag, z2.imag
(0.8660254037844386, 0.8660254037844386)

Even though you know that z1 et z2 are the same point, Python can’t determine that because of the rounding errors. Fortunately, the PEP 485 document defined functions for approximate equality, which are available in the math et cmath modules:

>>>

>>> math.isclose(z1.real, z2.real)
True

>>> cmath.isclose(z1, z2)
True

Remember to always use them when comparing complex numbers! If the default tolerance isn’t good enough for your calculations, you can change it by specifying additional arguments.

Ordering Complex Numbers

If you’re familiar with tuples, then you know that Python can sort them:

>>>

>>> planets = [[[[
...     (6, "saturn"),
...     (4, "mars"),
...     (1, "mercury"),
...     (5, "jupiter"),
...     (8, "neptune"),
...     (3, "earth"),
...     (7, "uranus"),
...     (2, "venus"),
... ]
>>> de pprint import pprint
>>> pprint(sorted(planets))
[(1'mercury')[(1'mercury')[(1'mercury')[(1'mercury')
    (2, 'venus'),
    (3, 'earth'),
    (4, 'mars'),
    (5, 'jupiter'),
    (6, 'saturn'),
    (7, 'uranus'),
    (8, 'neptune')]

By default, the individual tuples are compared left to right:

>>>

>>> (6, "saturn") < (4, "mars")
Faux
>>> (3, "earth") < (3, "moon")
True

In the first case, the number 6 is greater than 4, so the planet names aren’t considered at all. They can help resolve a tie, though. However, that’s not the case with complex numbers since they ne pas define a natural ordering relation. For example, you’ll get an error if you try to compare two complex numbers:

>>>

>>> (3 + 2j) < (2 + 3j)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '<' not supported between instances of 'complex' and 'complex'

Should the imaginary dimension have more weight than the real one? Should their magnitudes be compared? It’s up to you, and the answers will vary. Since you can’t compare complex numbers directly, you need to tell Python how to sort them by specifying a custom key function, such as abs():

>>>

>>> cities = 
...     complex(-64.78303, 32.2949): "Hamilton",
...     complex(-66.105721, 18.466333): "San Juan",
...     complex(-80.191788, 25.761681): "Miami"
... 

>>> for city in sorted(cities, key=abs, sens inverse=True):
...     imprimer(abs(city), cities[[[[city])
...
84.22818453809096 Miami
72.38647347392259 Hamilton
68.63651945864338 San Juan

This will sort the complex numbers by their magnitude in descending order.

Formatting Complex Numbers as Strings

There aren’t any format codes specific to complex numbers, but you can format their real and imaginary parts separately using standard codes for floating-point numbers. Below, you’ll find a few techniques that demonstrate this. Some of them will actually apply your format specifier to both the real and imaginary parts in one go.

Let’s take the following complex number as an example and format it with two decimal places on both parts:

>>>

>>> z = pow(3 + 2j, 0,5)
>>> imprimer(z)
(1.8173540210239707+0.5502505227003375j)

A quick way to do this is either by calling format() with a numeric format specifier or by creating an appropriately formatted f-string:

>>>

>>> format(z, ".2f")
'1.82+0.55j'

>>> F"z:.2f"
'1.82+0.55j'

If you want more control, for example, to add extra padding around the plus operator, then the f-string will be a better choice:

>>>

>>> F"z.real:.2f    + z.imag:.2fj"
'1.82 + 0.55j'

You can also call .format() on a string object and pass positional or keyword arguments to it:

>>>

>>> "0:.2f    + 0:.2fj".format(z.real, z.imag)
'1.82 + 1.82j'

>>> "re:.2f    + im:.2fj".format(re=z.real, im=z.imag)
'1.82 + 0.55j'

The positional arguments provide a sequence of values, while the keyword arguments let you refer to them by name. Similarly, you can use the string modulo operator (%) with either a tuple or a dictionary:

>>>

>>> "%.2f    + %.2fj" % (z.real, z.imag)
'1.82 + 0.55j'

>>> "%(re).2f    + %(im).2fj" % "re": z.real, "im": z.imag
'1.82 + 0.55j'

However, this uses a different placeholder syntax and is slightly old-fashioned.

Creating Your Own Complex Data Type

The Python data model defines a set of special methods that you can implement to make your classes compatible with certain built-in types. Say you were working with points and vectors and wanted to get the angle between two bound vectors. You might calculate their dot product and do some trigonometry. Alternatively, you can take advantage of complex numbers.

Let’s define your classes first:

de typing import NamedTuple

classer Point(NamedTuple):
    x: float
    y: float

classer Vector(NamedTuple):
    start: Point
    end: Point

UNE Point has the x et y coordinates, while a Vector connects two points. You might remember cmath.phase(), which calculates the angular distance of a complex number. Now, if you treated your vectors as complex numbers and knew their phases, then you could subtract them to obtain the desired angle.

To make Python recognize vector instances as complex numbers, you have to supply .__complex__() in the class body:

classer Vector(NamedTuple):
    start: Point
    end: Point

    def __complex__(self):
        real = self.end.x - self.start.x
        imag = self.end.y - self.start.y
        return complex(real, imag)

The code inside must always return an instance of the complex data type, so it typically constructs a new complex number out of your object. Here, you’re subtracting the initial and terminal points to get the horizontal and vertical displacements, which serve as the real and imaginary parts. The method will run through delegation when you call the global complex() on a vector instance:

>>>

>>> vector = Vector(Point(-2, -1), Point(1, 1))
>>> complex(vector)
(3+2j)

In some cases, you don’t have to make this kind of type casting yourself. Let’s see an example in practice:

>>>

>>> v1 = Vector(Point(-2, -1), Point(1, 1))
>>> v2 = Vector(Point(dix, -4), Point(8, -1))

>>> import math, cmath
>>> math.degrees(cmath.phase(v2) - cmath.phase(v1))
90.0

You’ve got two vectors identified by four distinct points. Next, you pass them directly to cmath.phase(), which does the conversion to a complex number for you and returns the phase. The phase difference is the angle between the two vectors.

Wasn’t that beautiful? You’ve saved yourself from typing a lot of error-prone code by piggybacking on the complex numbers and a bit of Python magic.

Calculating the Discrete Fourier Transform With Complex Numbers

While you can use real numbers to calculate the sine and cosine coefficients of a periodic function’s frequencies with the Fourier transform, it’s usually more convenient to deal with only one complex coefficient per frequency. le discrete Fourier transform in the complex domain is given by the following formula:

The Discrete Fourier Transform

For each frequency bin k, it measures the correlation of the signal and a particular sine wave expressed as a complex number in the exponential form. (Thank you, Leonhard Euler!) The angular frequency of the wave can be calculated by multiplying the round angle, which is 2π radians, by k over the number of discrete samples:

The Angular Frequency

Coding this in Python looks quite neat when you take advantage of the complex data type:

de cmath import pi, exp

def discrete_fourier_transform(x, k):
    omega = 2 * pi * k / (N := len(x))
    return sum(x[[[[m] * exp(-1j * omega * m) for m in intervalle(N))

This function is a literal transcription of the formulas above. Now you can run a frequency analysis on a sound that you load from an audio file using Python’s wave module or that you synthesize from scratch. One of the Jupyter Notebooks accompanying this tutorial lets you play with audio synthesis and analysis interactively.

To plot the frequency spectrum with Matplotlib, you must know the sampling frequency, which determines your frequency bin resolution as well as the Nyquist limit:

import matplotlib.pyplot comme plt

def plot_frequency_spectrum(
    samples,
    samples_per_second,
    min_frequency=0,
    max_frequency=None,
):
    num_bins = len(samples) // 2
    nyquist_frequency = samples_per_second // 2

    magnitudes = []
    for k in intervalle(num_bins):
        magnitudes.append(abs(discrete_fourier_transform(samples, k)))

    # Normalize magnitudes
    magnitudes = [[[[m / max(magnitudes) for m in magnitudes]

    # Calculate frequency bins
    bin_resolution = samples_per_second / len(samples)
    frequency_bins = [[[[k * bin_resolution for k in intervalle(num_bins)]

    plt.xlim(min_frequency, max_frequency or nyquist_frequency)
    plt.bar(frequency_bins, magnitudes, width=bin_resolution)

The number of frequency bins in the spectrum is equal to half the samples, while the Nyquist frequency limits the highest frequency you can measure. The transform returns a complex number whose magnitude corresponds to the amplitude of a sine wave at the given frequency, whereas its angle is the phase.

Here’s a sample frequency plot of a sound wave comprising three tones—440 Hz, 1.5 kHz, and 5 kHz—having equal amplitudes:

Frequency Spectrum
Frequency spectrum plot

Note this was a purely academic example since calculating the discrete Fourier transform with nested iterations has O(m2) time complexity, making it unusable in practice. For real-life applications, you want to use the fast Fourier transform (FFT) algorithm best implemented in a C library, such as the FFT in SciPy.

Conclusion

The ease of using complex numbers in Python makes them a surprisingly fun and practical tool. You saw two-dimensional vectors implemented practically for free, and you were able to analyze sound frequencies thanks to them. Complex numbers let you elegantly express mathematical formulas in code without much boilerplate syntax standing in the way.

In this tutorial, you learned how to:

  • Define complex numbers with literals in Python
  • Represent complex numbers in rectangular et polar coordinates
  • Use complex numbers in arithmetic expressions
  • Take advantage of the built-in cmath module
  • Translate mathematical formulas directly to Python code

What has your experience been with Python complex numbers so far? Were you ever intimidated by them? What other interesting problems do you think they’ll let you solve?

You can click the link below to get the full source code for this tutorial:

[ad_2]