Représentations des Systèmes Continus
Ce tutorial présente les différentes représentations d'un système continu sous scipy.signal et montre comment passer de l'une à l'autre. L'objectif est de comprendre que ces représentations décrivent le même système, mais sous des formes différentes.
Les trois représentations
Représentation BA (numérateur/dénominateur)
La représentation BA exprime la fonction de transfert sous forme polynomiale :
Sous scipy.signal, cette représentation est stockée dans deux tableaux avec les puissances décroissantes :
b = [b_2, b_1, b_0](coefficients du numérateur)a = [a_2, a_1, a_0](coefficients du dénominateur)
Représentation ZPK (zéros, pôles, gain)
La représentation ZPK exprime la fonction de transfert sous forme factorisée :
Sous scipy.signal, cette représentation est stockée dans :
z = [z_1, z_2](zéros)p = [p_1, p_2](pôles)k(gain)
Représentation SOS (sections d'ordre 2)
La représentation SOS exprime la fonction de transfert comme une cascade de cellules d'ordre 2. Pour un système d'ordre 2, il n'y a qu'une seule section.
Attention à la convention
Contrairement à la représentation BA, la représentation SOS utilise les puissances croissantes :
Chaque ligne de la matrice SOS contient : [b_0, b_1, b_2, a_0, a_1, a_2]
Cette incohérence est historique : la convention BA vient de l'écriture mathématique classique des polynômes (puissances décroissantes), tandis que la convention SOS vient du traitement du signal numérique où les filtres IIR s'expriment avec des retards
Exemple de référence
Dans ce tutorial, nous utiliserons un filtre passe-bas d'ordre 2 avec
Code d'initialisation
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# Paramètres du filtre
omega_0 = 1000
m = 0.5
# Coefficients BA (puissances décroissantes)
b = [omega_0**2] # [b_0]
a = [1, 2*m*omega_0, omega_0**2] # [a_2, a_1, a_0]
print("Représentation BA:")
print("b =", b)
print("a =", a)Conversion BA vers ZPK
Méthode manuelle
Pour convertir de BA vers ZPK, il faut :
- Trouver les racines du numérateur (zéros)
- Trouver les racines du dénominateur (pôles)
- Calculer le gain
(rapport des coefficients de plus haut degré)
# Conversion manuelle BA -> ZPK
z = np.roots(b) # Zéros = racines du numérateur
p = np.roots(a) # Pôles = racines du dénominateur
k = b[0] / a[0] # Gain = rapport des coefficients dominants
print("Représentation ZPK:")
print("z =", z)
print("p =", p)
print("k =", k)Résultat attendu
Représentation ZPK:
z = []
p = [-500.+866.02540378j -500.-866.02540378j]
k = 1000000.0Fonction scipy équivalente
z, p, k = signal.tf2zpk(b, a)Conversion ZPK vers BA
Méthode manuelle
Pour convertir de ZPK vers BA, il faut :
- Construire le polynôme du numérateur à partir des zéros
- Construire le polynôme du dénominateur à partir des pôles
- Multiplier le numérateur par le gain
# Conversion manuelle ZPK -> BA
b_rebuilt = k * np.atleast_1d(np.poly(z)) # Numérateur = k × polynôme des zéros
a_rebuilt = np.poly(p) # Dénominateur = polynôme des pôles
# Conversion en réels (les coefficients sont réels pour un système physique)
b_rebuilt = np.real(b_rebuilt)
a_rebuilt = np.real(a_rebuilt)
print("Représentation BA reconstruite:")
print("b =", b_rebuilt)
print("a =", a_rebuilt)Fonction scipy équivalente
b, a = signal.zpk2tf(z, p, k)Conversion BA vers SOS
Méthode manuelle
Pour convertir de BA vers SOS, il faut inverser l'ordre des coefficients (passer des puissances décroissantes aux puissances croissantes) :
# Conversion manuelle BA -> SOS
# BA : [b_2, b_1, b_0] -> SOS : [b_0, b_1, b_2]
b_sos = list(reversed(b))
a_sos = list(reversed(a))
# Compléter à 3 coefficients si nécessaire
while len(b_sos) < 3:
b_sos.append(0)
while len(a_sos) < 3:
a_sos.append(0)
# Construction de la matrice SOS (une seule ligne pour un ordre 2)
sos_manual = np.array([b_sos + a_sos])
print("Représentation SOS:")
print(sos_manual)Résultat attendu
Représentation SOS:
[[1.e+06 0.e+00 0.e+00 1.e+06 1.e+03 1.e+00]]Fonction scipy équivalente
sos = signal.tf2sos(b, a)Vérification des conversions
Pour vérifier que les différentes représentations décrivent bien le même système, nous pouvons comparer leurs réponses fréquentielles.
Code
# Calcul des réponses fréquentielles
w = np.logspace(1, 5, 500)
# Réponse BA
_, H_ba = signal.freqs(b, a, w)
# Réponse ZPK
sys_zpk = signal.ZerosPolesGain(z, p, k)
_, H_zpk = signal.freqresp(sys_zpk, w)
# Réponse SOS (en inversant l'ordre pour freqs)
b_sos_row = sos_manual[0, :3]
a_sos_row = sos_manual[0, 3:]
_, H_sos = signal.freqs(b_sos_row[::-1], a_sos_row[::-1], w)
# Tracé
fig, axes = plt.subplots(2, 1, figsize=(8, 6))
axes[0].semilogx(w, 20*np.log10(np.abs(H_ba)), label='BA')
axes[0].semilogx(w, 20*np.log10(np.abs(H_zpk)), '--', label='ZPK')
axes[0].semilogx(w, 20*np.log10(np.abs(H_sos)), ':', label='SOS')
axes[0].set_ylabel('Module (dB)')
axes[0].legend()
axes[0].grid(True)
axes[1].semilogx(w, np.angle(H_ba, deg=True), label='BA')
axes[1].semilogx(w, np.angle(H_zpk, deg=True), '--', label='ZPK')
axes[1].semilogx(w, np.angle(H_sos, deg=True), ':', label='SOS')
axes[1].set_xlabel('Pulsation (rad/s)')
axes[1].set_ylabel('Phase (°)')
axes[1].legend()
axes[1].grid(True)
plt.tight_layout()
plt.savefig("06_fig1.png", dpi=150)
plt.show()Résultat
Les trois courbes doivent être parfaitement superposées, confirmant que les représentations BA, ZPK et SOS décrivent bien le même système.

Récapitulatif
Conventions scipy
| Représentation | Convention | Exemple ordre 2 |
|---|---|---|
| BA | Puissances décroissantes | b = [b_2, b_1, b_0] |
| SOS | Puissances croissantes | sos = [b_0, b_1, b_2, a_0, a_1, a_2] |
Fonctions de conversion scipy
| Conversion | Fonction scipy |
|---|---|
| BA → ZPK | signal.tf2zpk(b, a) |
| ZPK → BA | signal.zpk2tf(z, p, k) |
| BA → SOS | signal.tf2sos(b, a) |
| SOS → BA | signal.sos2tf(sos) |
| ZPK → SOS | signal.zpk2sos(z, p, k) |
| SOS → ZPK | signal.sos2zpk(sos) |
