Skip to content

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 :

H(s)=b2s2+b1s+b0a2s2+a1s+a0

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 :

H(s)=k(sz1)(sz2)(sp1)(sp2)

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 :

H(s)=b0+b1s+b2s2a0+a1s+a2s2

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 z1 (puissances croissantes).

Exemple de référence

Dans ce tutorial, nous utiliserons un filtre passe-bas d'ordre 2 avec ω0=1000 rad/s et m=0.5. La fonction de transfert est :

H(s)=ω02s2+2mω0s+ω02=106s2+1000s+106

Code d'initialisation

python
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 :

  1. Trouver les racines du numérateur (zéros)
  2. Trouver les racines du dénominateur (pôles)
  3. Calculer le gain k=bN/aN (rapport des coefficients de plus haut degré)
python
# 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.0

Fonction scipy équivalente

python
z, p, k = signal.tf2zpk(b, a)

Conversion ZPK vers BA

Méthode manuelle

Pour convertir de ZPK vers BA, il faut :

  1. Construire le polynôme du numérateur à partir des zéros
  2. Construire le polynôme du dénominateur à partir des pôles
  3. Multiplier le numérateur par le gain k
python
# 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

python
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) :

python
# 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

python
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

python
# 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ésentationConventionExemple ordre 2
BAPuissances décroissantesb = [b_2, b_1, b_0]
SOSPuissances croissantessos = [b_0, b_1, b_2, a_0, a_1, a_2]

Fonctions de conversion scipy

ConversionFonction scipy
BA → ZPKsignal.tf2zpk(b, a)
ZPK → BAsignal.zpk2tf(z, p, k)
BA → SOSsignal.tf2sos(b, a)
SOS → BAsignal.sos2tf(sos)
ZPK → SOSsignal.zpk2sos(z, p, k)
SOS → ZPKsignal.sos2zpk(sos)