|
|
import streamlit as st |
|
|
import numpy as np |
|
|
import matplotlib.pyplot as plt |
|
|
import pandas as pd |
|
|
from scipy import linalg |
|
|
import plotly.graph_objects as go |
|
|
from plotly.subplots import make_subplots |
|
|
import time |
|
|
|
|
|
def show_pid_control(): |
|
|
"""Interface pour le contrôle PID""" |
|
|
|
|
|
st.header("🎯 Contrôle PID") |
|
|
|
|
|
|
|
|
with st.expander("📖 Théorie du contrôle PID", expanded=True): |
|
|
st.markdown(r""" |
|
|
### Contrôleur PID |
|
|
|
|
|
La loi de commande PID est : |
|
|
|
|
|
$$ |
|
|
u(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{de(t)}{dt} |
|
|
$$ |
|
|
|
|
|
où : |
|
|
- $e(t)$ : erreur de suivi |
|
|
- $K_p$ : gain proportionnel |
|
|
- $K_i$ : gain intégral |
|
|
- $K_d$ : gain dérivé |
|
|
|
|
|
### Effets des gains |
|
|
|
|
|
- **Kp** : Rapidité de réponse, mais oscillations si trop élevé |
|
|
- **Ki** : Élimination de l'erreur statique |
|
|
- **Kd** : Amortissement des oscillations |
|
|
""") |
|
|
|
|
|
|
|
|
st.subheader("🧪 Simulateur de contrôle PID") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### Paramètres du système") |
|
|
|
|
|
system_type = st.selectbox("Type de système", |
|
|
["Masse-ressort", "Double intégrateur", "Robot souple"]) |
|
|
|
|
|
if system_type == "Masse-ressort": |
|
|
st.info("Système masse-ressort : m=1kg, k=10N/m, c=0.1Ns/m") |
|
|
|
|
|
elif system_type == "Double intégrateur": |
|
|
st.info("Double intégrateur : position et vitesse") |
|
|
|
|
|
elif system_type == "Robot souple": |
|
|
st.info("Robot souple : poutre en flexion") |
|
|
|
|
|
with col2: |
|
|
st.markdown("### Gains PID") |
|
|
|
|
|
Kp = st.slider("Kp (Proportionnel)", 0.1, 100.0, 5.0, 0.1) |
|
|
Ki = st.slider("Ki (Intégral)", 0.0, 50.0, 1.0, 0.1) |
|
|
Kd = st.slider("Kd (Dérivé)", 0.0, 10.0, 0.1, 0.01) |
|
|
|
|
|
reference = st.slider("Consigne", -5.0, 5.0, 1.0, 0.1) |
|
|
|
|
|
if st.button("🎯 Lancer le contrôle PID", type="primary"): |
|
|
simulate_pid_control(system_type, Kp, Ki, Kd, reference) |
|
|
|
|
|
def simulate_pid_control(system_type, Kp, Ki, Kd, reference): |
|
|
"""Simule le contrôle PID""" |
|
|
|
|
|
dt = 0.01 |
|
|
n_steps = 1000 |
|
|
t = np.linspace(0, (n_steps-1)*dt, n_steps) |
|
|
|
|
|
|
|
|
if system_type == "Masse-ressort": |
|
|
A = np.array([[0, 1], [-10, -0.1]]) |
|
|
B = np.array([[0], [1]]) |
|
|
C = np.array([[1, 0]]) |
|
|
elif system_type == "Double intégrateur": |
|
|
A = np.array([[0, 1], [0, 0]]) |
|
|
B = np.array([[0], [1]]) |
|
|
C = np.array([[1, 0]]) |
|
|
else: |
|
|
|
|
|
A = np.array([[0, 1], [-1, -0.1]]) |
|
|
B = np.array([[0], [1]]) |
|
|
C = np.array([[1, 0]]) |
|
|
|
|
|
|
|
|
x = np.zeros((2, n_steps)) |
|
|
u = np.zeros(n_steps) |
|
|
error = np.zeros(n_steps) |
|
|
integral = 0 |
|
|
prev_error = 0 |
|
|
|
|
|
for i in range(1, n_steps): |
|
|
|
|
|
error[i] = (reference - C @ x[:, i-1])[0] |
|
|
|
|
|
|
|
|
integral += error[i] * dt |
|
|
derivative = (error[i] - prev_error) / dt |
|
|
u[i] = Kp * error[i] + Ki * integral + Kd * derivative |
|
|
|
|
|
|
|
|
x_dot = A @ x[:, i-1] + B.flatten() * u[i] |
|
|
x[:, i] = x[:, i-1] + x_dot * dt |
|
|
|
|
|
prev_error = error[i] |
|
|
|
|
|
|
|
|
fig = make_subplots(rows=2, cols=1, |
|
|
subplot_titles=("Réponse du système", "Commande PID"), |
|
|
vertical_spacing=0.15) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=t, y=x[0, :], name='Sortie', |
|
|
line=dict(color='blue')), |
|
|
row=1, col=1) |
|
|
fig.add_trace(go.Scatter(x=t, y=np.ones_like(t) * reference, name='Consigne', |
|
|
line=dict(color='red', dash='dash')), |
|
|
row=1, col=1) |
|
|
fig.add_trace(go.Scatter(x=t, y=error, name='Erreur', |
|
|
line=dict(color='green')), |
|
|
row=1, col=1) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=t, y=u, name='Commande u(t)', |
|
|
line=dict(color='purple')), |
|
|
row=2, col=1) |
|
|
|
|
|
fig.update_layout(height=600, showlegend=True) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
steady_state_error = np.abs(error[-100:]).mean() |
|
|
overshoot = (np.max(x[0, :]) - reference) / reference * 100 if reference != 0 else 0 |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.metric("Erreur statique", f"{steady_state_error:.4f}") |
|
|
|
|
|
with col2: |
|
|
st.metric("Dépassement (%)", f"{overshoot:.1f}%") |
|
|
|
|
|
with col3: |
|
|
settling_time = np.where(np.abs(error) < 0.05 * np.abs(reference))[0] |
|
|
settling_time = settling_time[0] * dt if len(settling_time) > 0 else n_steps * dt |
|
|
st.metric("Temps de réponse", f"{settling_time:.2f} s") |