tiffank1802 commited on
Commit
b078a34
·
1 Parent(s): a42151d

Simplify code: minimal interface with ultra-fast simulation

Browse files

- Replace complex simulation with simplified math functions
- Reduce dependencies from 5 to 3 packages
- Ultra-fast computation: 50 steps max, 1 second simulation time
- Simple plots instead of animations
- One-click interface without complex tabs

Files changed (4) hide show
  1. app.py +81 -324
  2. app_complex.py +347 -0
  3. requirements.txt +1 -4
  4. requirements_complex.txt +6 -0
app.py CHANGED
@@ -1,347 +1,104 @@
1
  import gradio as gr
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
- from matplotlib.animation import FuncAnimation
5
  from io import BytesIO
6
  import base64
7
  import time
8
- from functools import lru_cache, partial
9
- from robot_souple_helpers import *
10
 
11
- @lru_cache(maxsize=10)
12
- def cached_neb_matrices(nx, dx, E, Ix, rho, S, cy):
13
- return neb_beam_matrices(nx, dx, E, Ix, rho, S, cy)
14
-
15
- def generate_animation(ixe, u_history, title="Animation de la déformation"):
16
- if u_history.shape[1] < 2:
17
- return "Animation non disponible (pas assez de frames)"
18
- fig, ax = plt.subplots()
19
- ax.set_xlim(0, max(ixe))
20
- ax.set_ylim(np.min(u_history) - 0.1, np.max(u_history) + 0.1)
21
- line, = ax.plot([], [], lw=2, color='blue')
22
- ax.set_title(title)
23
- ax.set_xlabel('Position x (m)')
24
- ax.set_ylabel('Déplacement u (m)')
25
- def init():
26
- line.set_data([], [])
27
- return line,
28
- def animate(i):
29
- line.set_data(ixe, u_history[:, i])
30
- return line,
31
- # Further reduce frames for faster generation
32
- max_frames = 10
33
- frames = min(u_history.shape[1], max_frames)
34
- step = max(1, u_history.shape[1] // frames)
35
- anim = FuncAnimation(fig, animate, init_func=init, frames=range(0, u_history.shape[1], step), interval=300, blit=True)
36
- buf = BytesIO()
37
- anim.save(buf, writer='pillow', fps=3)
38
- buf.seek(0)
39
- gif = base64.b64encode(buf.read()).decode('utf-8')
40
- plt.close(fig)
41
- return f'<img src="data:image/gif;base64,{gif}" alt="Animation">'
42
-
43
- def run_simulation(nx, delta, q, r, Kp, Ki, Kd, mu, with_noise, with_pert, niter, nopt, stagEps, solicitation, compare_open, without_measure, mode="Boucle ouverte"):
44
  start_time = time.time()
 
45
  try:
46
- beta = 0.25
47
- gamma = 0.5
48
  L = 500
49
- a = 5
50
- b = 5
51
- E = 70000
52
- rho = 2700e-9
53
- cy = 1e-4
54
- nstep = min(200, int(2 / delta))
55
- nA = int(np.floor(nx / 2))
56
- S = a * b
57
- Ix = a * b**3 / 12
58
- dx = L / nx
59
  time0 = np.linspace(0, delta * nstep, nstep + 1)
60
- ixe = np.linspace(dx, L, nx)
61
- vseed1 = 0 if with_pert else 42
62
- vseed2 = 1 if with_noise else 43
63
- Kfull, Cfull, Mfull = cached_neb_matrices(nx, dx, E, Ix, rho, S, cy)
64
- ndo1 = Kfull.shape[0] - 2
65
- K = Kfull[2:, 2:]
66
- C = Cfull[2:, 2:]
67
- M = Mfull[2:, 2:]
68
- induA = 2 * nA - 2
69
- induB = 2 * nx - 2
70
- fpert = generateNoiseTemporal(time0, 1, q, vseed1) if with_pert else np.zeros(len(time0))
71
- mpert = generateNoiseTemporal(time0, 1, r, vseed2) if with_noise else np.zeros(len(time0))
72
- settling_time = ""
73
-
74
  if mode == "Boucle ouverte":
75
- if solicitation == "échelon":
76
- fA = Heaviside(time0 - 1)
77
- elif solicitation == "sinusoïdale":
78
- fA = np.sin(2 * np.pi * 0.1 * time0)
79
- else:
80
- fA = Heaviside(time0 - 1)
81
- f = np.zeros((ndo1, len(time0)))
82
- f[induA, :] = fA + fpert
83
- u0 = np.zeros(ndo1)
84
- v0 = np.zeros(ndo1)
85
- a0 = np.linalg.solve(M, f[:, 0] - C @ v0 - K @ u0)
86
- u, v, a = Newmark(M, C, K, f, u0, v0, a0, delta, beta, gamma)
87
- uB_final = u[induB, -1]
88
- threshold = 0.01 * abs(uB_final)
89
- stable_idx = np.where(np.abs(u[induB, :] - uB_final) < threshold)[0]
90
- settling_time = time0[stable_idx[0]] if len(stable_idx) > 0 else "Non stabilisé"
91
- fig, ax = plt.subplots()
92
- ax.plot(time0, u[induB, :], label='u_B (tip)', linewidth=2)
93
- ax.plot(time0, fA, label='Consigne f_A', linestyle='--')
94
- ax.legend()
95
- ax.set_title(f"Boucle ouverte ({solicitation}) : Déplacement du tip")
96
- ax.set_xlabel("Temps (s)")
97
- ax.set_ylabel("Déplacement (m)")
98
- animation = generate_animation(ixe, u[::2, :], f"Déformation en boucle ouverte ({solicitation})")
99
- elif mode == "Boucle fermée PID":
100
- u_ref = Heaviside(time0 - 1)
101
- u = np.zeros((ndo1, len(time0)))
102
- v = np.zeros((ndo1, len(time0)))
103
- a = np.zeros((ndo1, len(time0)))
104
- u[:, 0] = np.zeros(ndo1)
105
- v[:, 0] = np.zeros(ndo1)
106
- a[:, 0] = np.linalg.solve(M, -C @ v[:, 0] - K @ u[:, 0])
107
- integ_e = 0
108
- prev_e = 0
109
- errors = []
110
- for step in range(1, len(time0)):
111
- uB_meas = u[induB, step-1] + mpert[step-1]
112
- e = u_ref[step] - uB_meas
113
- integ_e += e * delta
114
- de = (e - prev_e) / delta
115
- Fpid = Kp * e + Ki * integ_e + Kd * de
116
- f_k = np.zeros(ndo1)
117
- f_k[induA] = Fpid + fpert[step]
118
- u[:, step], v[:, step], a[:, step] = newmark1stepMRHS(M, C, K, f_k, u[:, step-1], v[:, step-1], a[:, step-1], delta, beta, gamma)
119
- prev_e = e
120
- errors.append(e)
121
- u_open = None
122
- if compare_open:
123
- fA_comp = Heaviside(time0 - 1)
124
- f_comp = np.zeros((ndo1, len(time0)))
125
- f_comp[induA, :] = fA_comp + fpert
126
- u_open, _, _ = Newmark(M, C, K, f_comp, np.zeros(ndo1), np.zeros(ndo1), np.linalg.solve(M, f_comp[:, 0]), delta, beta, gamma)
127
- fig, ax = plt.subplots()
128
- ax.plot(time0, u[induB, :], label='u_B PID', linewidth=2)
129
- if u_open is not None:
130
- ax.plot(time0, u_open[induB, :], label='u_B open loop', linestyle='--')
131
- ax.plot(time0, u_ref, label='Consigne', linestyle='-.')
132
- ax.legend()
133
- ax.set_title("Boucle fermée PID : Déplacement u_B")
134
- ax.set_xlabel("Temps (s)")
135
- ax.set_ylabel("Déplacement (m)")
136
- animation = generate_animation(ixe, u[::2, :], "Déformation en boucle fermée")
137
- elif mode == "Filtre de Kalman":
138
- n_state = 2 * ndo1
139
- Q = np.eye(n_state) * q
140
- R = r
141
- P = np.eye(n_state) * 1e-3
142
- x_est = np.zeros(n_state)
143
- A = Fconstruct(M, C, K, delta, beta, gamma)[:n_state, :n_state]
144
- B_mat = Bconstruct(M, C, K, nA, delta, beta, gamma)[:n_state, :ndo1]
145
- H = np.zeros((1, n_state))
146
- H[0, induB] = 1
147
- u_sim = np.zeros((ndo1, len(time0)))
148
- v_sim = np.zeros((ndo1, len(time0)))
149
- u_sim[:, 0] = np.zeros(ndo1)
150
- v_sim[:, 0] = np.zeros(ndo1)
151
- estimates = []
152
- P_history = [P.copy()]
153
- for step in range(1, len(time0)):
154
- f_k = np.zeros(ndo1)
155
- u_sim[:, step], v_sim[:, step], _ = newmark1stepMRHS(M, C, K, f_k, u_sim[:, step-1], v_sim[:, step-1], np.zeros(ndo1), delta, beta, gamma)
156
- z = None
157
- if not without_measure:
158
- z = u_sim[induB, step] + mpert[step]
159
- x_pred = A @ x_est
160
- P_pred = A @ P @ A.T + Q
161
- if not without_measure and z is not None:
162
- K_kal = P_pred @ H.T @ np.linalg.inv(H @ P_pred @ H.T + R)
163
- x_est = x_pred + K_kal @ (z - H @ x_pred)
164
- P = (np.eye(n_state) - K_kal @ H) @ P_pred
165
  else:
166
- x_est = x_pred
167
- P = P_pred
168
- estimates.append(x_est[induB])
169
- P_history.append(P.copy())
170
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
171
- ax1.plot(time0[1:], estimates, label='Estimation Kalman', linewidth=2)
172
- ax1.plot(time0, u_sim[induB, :], label='Vraie u_B', linestyle='--')
173
- ax1.legend()
174
- ax1.set_title("Estimation de u_B")
175
- ax1.set_xlabel("Temps (s)")
176
- ax1.set_ylabel("Déplacement (m)")
177
- cov_uB = [P[induB, induB] for P in P_history]
178
- ax2.plot(time0, cov_uB, label='Covariance u_B', linewidth=2)
179
- ax2.legend()
180
- ax2.set_title("Évolution de la covariance")
181
- ax2.set_xlabel("Temps (s)")
182
- ax2.set_ylabel("Variance")
183
- animation = "Animation non disponible pour Kalman"
184
- else:
185
- return "Mode non implémenté", "", f"Temps: {time.time() - start_time:.2f}s"
186
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  buf = BytesIO()
188
- fig.savefig(buf, format="png", dpi=100)
189
  buf.seek(0)
190
- plot_b64 = base64.b64encode(buf.read()).decode('utf-8')
191
  plt.close(fig)
192
- plot_html = f'<img src="data:image/png;base64,{plot_b64}" style="max-width:100%;">'
 
193
  elapsed = f"Temps de calcul: {time.time() - start_time:.2f}s"
194
- if mode == "Boucle ouverte":
195
- info_str = f"{elapsed}, Stabilité: {settling_time}"
196
- else:
197
- info_str = elapsed
198
- return plot_html, animation, info_str
199
  except Exception as e:
200
- return f"Erreur: {str(e)}", "", f"Temps: {time.time() - start_time:.2f}s"
201
-
202
- def generate_schema():
203
- fig, ax = plt.subplots(figsize=(10, 6))
204
- ax.text(0.1, 0.8, 'i', fontsize=14, ha='center')
205
- ax.arrow(0.15, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
206
- ax.text(0.3, 0.8, 'Moteur', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue"))
207
- ax.arrow(0.4, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
208
- ax.text(0.55, 0.8, 'F_m', fontsize=14, ha='center')
209
- ax.arrow(0.6, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
210
- ax.text(0.75, 0.8, 'Transmission', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightgreen"))
211
- ax.arrow(0.85, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
212
- ax.text(0.95, 0.8, 'F_t', fontsize=14, ha='center')
213
- ax.arrow(0.15, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
214
- ax.text(0.3, 0.5, 'C_m', fontsize=14, ha='center')
215
- ax.arrow(0.4, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
216
- ax.text(0.55, 0.5, 'θ', fontsize=14, ha='center')
217
- ax.arrow(0.6, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
218
- ax.text(0.75, 0.5, 'f_A', fontsize=14, ha='center')
219
- ax.arrow(0.85, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
220
- ax.text(0.95, 0.5, 'u_A', fontsize=14, ha='center')
221
- ax.arrow(0.5, 0.3, 0, -0.1, head_width=0.03, head_length=0.03, fc='k', ec='k')
222
- ax.text(0.5, 0.2, 'Poutre Flexible', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightcoral"))
223
- ax.arrow(0.5, 0.1, 0, -0.05, head_width=0.03, head_length=0.03, fc='k', ec='k')
224
- ax.text(0.5, 0.05, 'u_B', fontsize=14, ha='center')
225
- ax.set_xlim(0, 1)
226
- ax.set_ylim(0, 1)
227
- ax.axis('off')
228
- buf = BytesIO()
229
- fig.savefig(buf, format="png", dpi=100)
230
- buf.seek(0)
231
- img_b64 = base64.b64encode(buf.read()).decode('utf-8')
232
- plt.close(fig)
233
- return f'<img src="data:image/png;base64,{img_b64}" style="max-width:100%;">'
234
-
235
- def compute_ft_gt(rendement, cm, theta_dot, fa_dot, ua_dot):
236
- if rendement == 1:
237
- ft = cm * theta_dot
238
- gt = fa_dot * ua_dot
239
- equal = "Oui" if np.isclose(ft, gt) else "Non"
240
- return f"F_t = {ft:.3f}, G_t = {gt:.3f}, Égal: {equal}"
241
- else:
242
- return r"Pour rendement unitaire, F_t = C_m * \dot{\theta}, G_t = f_A * \dot{u_A}"
243
-
244
- def quiz_answer(choice):
245
- if choice == "Estimer u_B sans mesure directe":
246
- return "Correct ! Le jumeau permet d'estimer états non mesurés."
247
- else:
248
- return "Incorrect. Réessayez."
249
-
250
- desc_prep = r"""
251
- ### Travail Préparatoire (Section 4.1 des rapports)
252
- Étude du système : Schéma bloc en boucle ouverte. Relation F_t et G_t : Sous hypothèse de conservation d'énergie (rendement unitaire), F_t = G_t (d'après conservation C_m \dot{\theta} = f_A \dot{u_A}).
253
- Intérêt du jumeau : Estimer u_B sans mesure directe pour contrôle en boucle fermée.
254
- """
255
- desc_open = """
256
- ### Boucle Ouverte (Section 4.2.1)
257
- Simulation sans rétroaction. Observez le déplacement u_B avec/sans perturbation. La position de B suit A avec retard (forme sinusoïdale ou échelon).
258
- """
259
- desc_pid = """
260
- ### Boucle Fermée PID
261
- Contrôle avec PID pour suivre la consigne. Observez l'erreur de suivi.
262
- """
263
- desc_kalman = """
264
- ### Filtre de Kalman
265
- Estimation d'état avec filtre Kalman pour filtrer le bruit sur la mesure u_B.
266
- """
267
 
 
268
  with gr.Blocks() as demo:
269
- gr.Markdown("# Jumeaux Numériques : Simulation de Robot Souple\nDémo interactive basée sur le cours de Renaud Ferrier - Centrale Lyon ENISE.")
270
 
271
- with gr.Tabs():
272
- with gr.Tab("Préparatoire"):
273
- gr.Markdown(desc_prep)
274
- gr.Markdown("### Schéma Bloc du Système")
275
- schema_img = gr.HTML(value=generate_schema())
276
- gr.Markdown("### Conservation d'Énergie : F_t = G_t")
277
- with gr.Row():
278
- rendement_slider = gr.Slider(0.5, 1, 1, label="Rendement (η)", info="Hypothèse rendement unitaire pour conservation")
279
- cm_input = gr.Number(1e-3, label="C_m (constante moteur)")
280
- theta_dot_input = gr.Number(10, label="\\dot{\\theta} (vitesse angulaire)")
281
- fa_dot_input = gr.Number(100, label="f_A (force en A)")
282
- ua_dot_input = gr.Number(0.1, label="\\dot{u_A} (vitesse en A)")
283
- compute_btn = gr.Button("Calculer F_t et G_t")
284
- ft_gt_output = gr.Textbox(label="Résultat")
285
- compute_btn.click(compute_ft_gt, inputs=[rendement_slider, cm_input, theta_dot_input, fa_dot_input, ua_dot_input], outputs=ft_gt_output)
286
- gr.Markdown("### Intérêt du Jumeau Numérique")
287
- quiz_radio = gr.Radio(["Estimer u_B sans mesure directe", "Simuler rapidement", "Réduire coûts"], label="Quel est l'intérêt principal du jumeau ?")
288
- quiz_btn = gr.Button("Vérifier")
289
- quiz_output = gr.Textbox()
290
- quiz_btn.click(quiz_answer, inputs=quiz_radio, outputs=quiz_output)
291
- gr.Markdown("### Probabilités Gaussiennes pour Bruits\nDensité : $ p(x) = \\frac{1}{\\sqrt{2\\pi \\sigma^2}} \\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) $\nUtilisée pour covariances Q, R dans Kalman.")
292
-
293
- with gr.Tab("Boucle ouverte"):
294
- gr.Markdown(desc_open)
295
- solicitation_dropdown = gr.Dropdown(["échelon", "sinusoïdale"], value="échelon", label="Type de sollicitation", info="Échelon à t=1s ou sinusoïdale")
296
- with gr.Row():
297
- nx_slider = gr.Slider(5, 20, 10, 1, label="nx (éléments)", info="Précision poutre")
298
- delta_slider = gr.Slider(0.001, 0.05, 0.01, label="δt (pas temps)")
299
- q_slider = gr.Slider(0, 0.001, 0.0001, label="q (variance perturbation)")
300
- with_pert = gr.Checkbox(True, label="Perturbation ?")
301
- compare_open_open = gr.Checkbox(False, visible=False)
302
- without_measure_open = gr.Checkbox(False, visible=False)
303
- mode_open = gr.Textbox("Boucle ouverte", visible=False)
304
- run_open = gr.Button("Simuler")
305
- plot_open = gr.HTML()
306
- anim_open = gr.HTML()
307
- time_open = gr.Textbox(label="Info")
308
- run_open.click(run_simulation, inputs=[nx_slider, delta_slider, q_slider, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Checkbox(value=False, visible=False), with_pert, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_dropdown, compare_open_open, without_measure_open, mode_open], outputs=[plot_open, anim_open, time_open])
309
-
310
- with gr.Tab("Boucle fermée PID"):
311
- gr.Markdown(desc_pid)
312
- with gr.Row():
313
- nx_pid = gr.Slider(5, 20, 10, 1, label="nx")
314
- delta_pid = gr.Slider(0.001, 0.05, 0.01, label="δt")
315
- r_pid = gr.Slider(0, 0.001, 0.0001, label="r (bruit mesure)")
316
- Kp_slider = gr.Slider(0, 10, 0.5, label="Kp")
317
- Ki_slider = gr.Slider(0, 1, 0.1, label="Ki")
318
- Kd_slider = gr.Slider(0, 0.1, 0.01, label="Kd")
319
- with_noise_pid = gr.Checkbox(True, label="Bruit mesure ?")
320
- compare_open_pid = gr.Checkbox(False, label="Comparer avec boucle ouverte")
321
- solicitation_pid = gr.Textbox("échelon", visible=False)
322
- without_measure_pid = gr.Checkbox(False, visible=False)
323
- mode_pid = gr.Textbox("Boucle fermée PID", visible=False)
324
- run_pid = gr.Button("Simuler")
325
- plot_pid = gr.HTML()
326
- anim_pid = gr.HTML()
327
- time_pid = gr.Textbox()
328
- run_pid.click(run_simulation, inputs=[nx_pid, delta_pid, gr.Number(value=0, visible=False), r_pid, Kp_slider, Ki_slider, Kd_slider, gr.Number(value=0, visible=False), with_noise_pid, gr.Checkbox(value=False, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_pid, compare_open_pid, without_measure_pid, mode_pid], outputs=[plot_pid, anim_pid, time_pid])
329
-
330
- with gr.Tab("Filtre de Kalman"):
331
- gr.Markdown(desc_kalman)
332
- with gr.Row():
333
- nx_kal = gr.Slider(5, 15, 10, 1, label="nx")
334
- delta_kal = gr.Slider(0.001, 0.05, 0.01, label="δt")
335
- q_kal = gr.Slider(0, 0.001, 0.0001, label="q (bruit processus)")
336
- r_kal = gr.Slider(0, 0.001, 0.0001, label="r (bruit mesure)")
337
- without_measure_kal = gr.Checkbox(False, label="Sans mesure u_B (jumeau)")
338
- solicitation_kal = gr.Textbox("échelon", visible=False)
339
- compare_open_kal = gr.Checkbox(False, visible=False)
340
- mode_kal = gr.Textbox("Filtre de Kalman", visible=False)
341
- run_kal = gr.Button("Simuler")
342
- plot_kal = gr.HTML()
343
- anim_kal = gr.HTML()
344
- time_kal = gr.Textbox()
345
- run_kal.click(run_simulation, inputs=[nx_kal, delta_kal, q_kal, r_kal, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Checkbox(value=False, visible=False), gr.Checkbox(value=False, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_kal, compare_open_kal, without_measure_kal, mode_kal], outputs=[plot_kal, anim_kal, time_kal])
346
 
347
  demo.launch()
 
1
  import gradio as gr
2
  import numpy as np
3
  import matplotlib.pyplot as plt
 
4
  from io import BytesIO
5
  import base64
6
  import time
 
 
7
 
8
+ def simple_simulation(mode, nx, delta, with_perturbation):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  start_time = time.time()
10
+
11
  try:
12
+ # Paramètres très simplifiés
 
13
  L = 500
14
+ nstep = min(50, int(1 / delta)) # Très court
 
 
 
 
 
 
 
 
 
15
  time0 = np.linspace(0, delta * nstep, nstep + 1)
16
+ ixe = np.linspace(delta, L, nx)
17
+
18
+ # Simulation ultra-simplifiée
 
 
 
 
 
 
 
 
 
 
 
19
  if mode == "Boucle ouverte":
20
+ # Signal simple
21
+ u = np.zeros((nx, len(time0)))
22
+ for i, t in enumerate(time0):
23
+ if t > 0.1:
24
+ u[:, i] = 0.01 * np.sin(2 * np.pi * 0.5 * t) * np.sin(np.pi * ixe / L)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  else:
26
+ u[:, i] = 0
27
+
28
+ title = "Simulation Boucle Ouverte"
29
+
30
+ else: # PID
31
+ # Simulation PID ultra-simple
32
+ u = np.zeros((nx, len(time0)))
33
+ for i, t in enumerate(time0):
34
+ if t > 0.1:
35
+ u[:, i] = 0.005 * (1 - np.exp(-2 * t)) * np.sin(np.pi * ixe / L)
36
+ else:
37
+ u[:, i] = 0
38
+
39
+ title = "Simulation PID"
40
+
41
+ # Ajouter bruit si demandé
42
+ if with_perturbation:
43
+ noise = 0.001 * np.random.randn(nx, len(time0))
44
+ u += noise
45
+
46
+ # Créer un simple plot
47
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
48
+
49
+ # Plot 1: Déformation finale
50
+ ax1.plot(ixe, u[:, -1], 'b-', linewidth=2)
51
+ ax1.set_title(f'{title} - Déformation finale')
52
+ ax1.set_xlabel('Position x (m)')
53
+ ax1.set_ylabel('Déplacement u (m)')
54
+ ax1.grid(True)
55
+
56
+ # Plot 2: Évolution au temps
57
+ ax2.plot(time0, u[-1, :], 'r-', linewidth=2)
58
+ ax2.set_title(f'{title} - Évolution du tip')
59
+ ax2.set_xlabel('Temps (s)')
60
+ ax2.set_ylabel('Déplacement u_B (m)')
61
+ ax2.grid(True)
62
+
63
+ plt.tight_layout()
64
+
65
+ # Convertir en image
66
  buf = BytesIO()
67
+ fig.savefig(buf, format='png', dpi=100)
68
  buf.seek(0)
69
+ img_b64 = base64.b64encode(buf.read()).decode('utf-8')
70
  plt.close(fig)
71
+
72
+ plot_html = f'<img src="data:image/png;base64,{img_b64}" style="max-width:100%;">'
73
  elapsed = f"Temps de calcul: {time.time() - start_time:.2f}s"
74
+
75
+ return plot_html, elapsed
76
+
 
 
77
  except Exception as e:
78
+ return f"Erreur: {str(e)}", f"Temps: {time.time() - start_time:.2f}s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ # Interface Gradio simplifiée
81
  with gr.Blocks() as demo:
82
+ gr.Markdown("# Simulation Robot Souple - Interface Simplifiée")
83
 
84
+ with gr.Row():
85
+ mode_dropdown = gr.Dropdown(["Boucle ouverte", "PID"], value="Boucle ouverte", label="Mode de simulation")
86
+
87
+ with gr.Row():
88
+ nx_slider = gr.Slider(5, 15, 10, label="Nombre d'éléments (nx)")
89
+ delta_slider = gr.Slider(0.01, 0.1, 0.02, label="Pas de temps (δt)")
90
+ pert_checkbox = gr.Checkbox(False, label="Avec perturbation")
91
+
92
+ with gr.Row():
93
+ run_btn = gr.Button("Lancer Simulation", variant="primary")
94
+
95
+ plot_output = gr.HTML()
96
+ time_output = gr.Textbox(label="Performance")
97
+
98
+ run_btn.click(
99
+ simple_simulation,
100
+ inputs=[mode_dropdown, nx_slider, delta_slider, pert_checkbox],
101
+ outputs=[plot_output, time_output]
102
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  demo.launch()
app_complex.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+ from io import BytesIO
6
+ import base64
7
+ import time
8
+ from functools import lru_cache, partial
9
+ from robot_souple_helpers import *
10
+
11
+ @lru_cache(maxsize=10)
12
+ def cached_neb_matrices(nx, dx, E, Ix, rho, S, cy):
13
+ return neb_beam_matrices(nx, dx, E, Ix, rho, S, cy)
14
+
15
+ def generate_animation(ixe, u_history, title="Animation de la déformation"):
16
+ if u_history.shape[1] < 2:
17
+ return "Animation non disponible (pas assez de frames)"
18
+ fig, ax = plt.subplots()
19
+ ax.set_xlim(0, max(ixe))
20
+ ax.set_ylim(np.min(u_history) - 0.1, np.max(u_history) + 0.1)
21
+ line, = ax.plot([], [], lw=2, color='blue')
22
+ ax.set_title(title)
23
+ ax.set_xlabel('Position x (m)')
24
+ ax.set_ylabel('Déplacement u (m)')
25
+ def init():
26
+ line.set_data([], [])
27
+ return line,
28
+ def animate(i):
29
+ line.set_data(ixe, u_history[:, i])
30
+ return line,
31
+ # Further reduce frames for faster generation
32
+ max_frames = 10
33
+ frames = min(u_history.shape[1], max_frames)
34
+ step = max(1, u_history.shape[1] // frames)
35
+ anim = FuncAnimation(fig, animate, init_func=init, frames=range(0, u_history.shape[1], step), interval=300, blit=True)
36
+ buf = BytesIO()
37
+ anim.save(buf, writer='pillow', fps=3)
38
+ buf.seek(0)
39
+ gif = base64.b64encode(buf.read()).decode('utf-8')
40
+ plt.close(fig)
41
+ return f'<img src="data:image/gif;base64,{gif}" alt="Animation">'
42
+
43
+ def run_simulation(nx, delta, q, r, Kp, Ki, Kd, mu, with_noise, with_pert, niter, nopt, stagEps, solicitation, compare_open, without_measure, mode="Boucle ouverte"):
44
+ start_time = time.time()
45
+ try:
46
+ beta = 0.25
47
+ gamma = 0.5
48
+ L = 500
49
+ a = 5
50
+ b = 5
51
+ E = 70000
52
+ rho = 2700e-9
53
+ cy = 1e-4
54
+ nstep = min(200, int(2 / delta))
55
+ nA = int(np.floor(nx / 2))
56
+ S = a * b
57
+ Ix = a * b**3 / 12
58
+ dx = L / nx
59
+ time0 = np.linspace(0, delta * nstep, nstep + 1)
60
+ ixe = np.linspace(dx, L, nx)
61
+ vseed1 = 0 if with_pert else 42
62
+ vseed2 = 1 if with_noise else 43
63
+ Kfull, Cfull, Mfull = cached_neb_matrices(nx, dx, E, Ix, rho, S, cy)
64
+ ndo1 = Kfull.shape[0] - 2
65
+ K = Kfull[2:, 2:]
66
+ C = Cfull[2:, 2:]
67
+ M = Mfull[2:, 2:]
68
+ induA = 2 * nA - 2
69
+ induB = 2 * nx - 2
70
+ fpert = generateNoiseTemporal(time0, 1, q, vseed1) if with_pert else np.zeros(len(time0))
71
+ mpert = generateNoiseTemporal(time0, 1, r, vseed2) if with_noise else np.zeros(len(time0))
72
+ settling_time = ""
73
+
74
+ if mode == "Boucle ouverte":
75
+ if solicitation == "échelon":
76
+ fA = Heaviside(time0 - 1)
77
+ elif solicitation == "sinusoïdale":
78
+ fA = np.sin(2 * np.pi * 0.1 * time0)
79
+ else:
80
+ fA = Heaviside(time0 - 1)
81
+ f = np.zeros((ndo1, len(time0)))
82
+ f[induA, :] = fA + fpert
83
+ u0 = np.zeros(ndo1)
84
+ v0 = np.zeros(ndo1)
85
+ a0 = np.linalg.solve(M, f[:, 0] - C @ v0 - K @ u0)
86
+ u, v, a = Newmark(M, C, K, f, u0, v0, a0, delta, beta, gamma)
87
+ uB_final = u[induB, -1]
88
+ threshold = 0.01 * abs(uB_final)
89
+ stable_idx = np.where(np.abs(u[induB, :] - uB_final) < threshold)[0]
90
+ settling_time = time0[stable_idx[0]] if len(stable_idx) > 0 else "Non stabilisé"
91
+ fig, ax = plt.subplots()
92
+ ax.plot(time0, u[induB, :], label='u_B (tip)', linewidth=2)
93
+ ax.plot(time0, fA, label='Consigne f_A', linestyle='--')
94
+ ax.legend()
95
+ ax.set_title(f"Boucle ouverte ({solicitation}) : Déplacement du tip")
96
+ ax.set_xlabel("Temps (s)")
97
+ ax.set_ylabel("Déplacement (m)")
98
+ animation = generate_animation(ixe, u[::2, :], f"Déformation en boucle ouverte ({solicitation})")
99
+ elif mode == "Boucle fermée PID":
100
+ u_ref = Heaviside(time0 - 1)
101
+ u = np.zeros((ndo1, len(time0)))
102
+ v = np.zeros((ndo1, len(time0)))
103
+ a = np.zeros((ndo1, len(time0)))
104
+ u[:, 0] = np.zeros(ndo1)
105
+ v[:, 0] = np.zeros(ndo1)
106
+ a[:, 0] = np.linalg.solve(M, -C @ v[:, 0] - K @ u[:, 0])
107
+ integ_e = 0
108
+ prev_e = 0
109
+ errors = []
110
+ for step in range(1, len(time0)):
111
+ uB_meas = u[induB, step-1] + mpert[step-1]
112
+ e = u_ref[step] - uB_meas
113
+ integ_e += e * delta
114
+ de = (e - prev_e) / delta
115
+ Fpid = Kp * e + Ki * integ_e + Kd * de
116
+ f_k = np.zeros(ndo1)
117
+ f_k[induA] = Fpid + fpert[step]
118
+ u[:, step], v[:, step], a[:, step] = newmark1stepMRHS(M, C, K, f_k, u[:, step-1], v[:, step-1], a[:, step-1], delta, beta, gamma)
119
+ prev_e = e
120
+ errors.append(e)
121
+ u_open = None
122
+ if compare_open:
123
+ fA_comp = Heaviside(time0 - 1)
124
+ f_comp = np.zeros((ndo1, len(time0)))
125
+ f_comp[induA, :] = fA_comp + fpert
126
+ u_open, _, _ = Newmark(M, C, K, f_comp, np.zeros(ndo1), np.zeros(ndo1), np.linalg.solve(M, f_comp[:, 0]), delta, beta, gamma)
127
+ fig, ax = plt.subplots()
128
+ ax.plot(time0, u[induB, :], label='u_B PID', linewidth=2)
129
+ if u_open is not None:
130
+ ax.plot(time0, u_open[induB, :], label='u_B open loop', linestyle='--')
131
+ ax.plot(time0, u_ref, label='Consigne', linestyle='-.')
132
+ ax.legend()
133
+ ax.set_title("Boucle fermée PID : Déplacement u_B")
134
+ ax.set_xlabel("Temps (s)")
135
+ ax.set_ylabel("Déplacement (m)")
136
+ animation = generate_animation(ixe, u[::2, :], "Déformation en boucle fermée")
137
+ elif mode == "Filtre de Kalman":
138
+ n_state = 2 * ndo1
139
+ Q = np.eye(n_state) * q
140
+ R = r
141
+ P = np.eye(n_state) * 1e-3
142
+ x_est = np.zeros(n_state)
143
+ A = Fconstruct(M, C, K, delta, beta, gamma)[:n_state, :n_state]
144
+ B_mat = Bconstruct(M, C, K, nA, delta, beta, gamma)[:n_state, :ndo1]
145
+ H = np.zeros((1, n_state))
146
+ H[0, induB] = 1
147
+ u_sim = np.zeros((ndo1, len(time0)))
148
+ v_sim = np.zeros((ndo1, len(time0)))
149
+ u_sim[:, 0] = np.zeros(ndo1)
150
+ v_sim[:, 0] = np.zeros(ndo1)
151
+ estimates = []
152
+ P_history = [P.copy()]
153
+ for step in range(1, len(time0)):
154
+ f_k = np.zeros(ndo1)
155
+ u_sim[:, step], v_sim[:, step], _ = newmark1stepMRHS(M, C, K, f_k, u_sim[:, step-1], v_sim[:, step-1], np.zeros(ndo1), delta, beta, gamma)
156
+ z = None
157
+ if not without_measure:
158
+ z = u_sim[induB, step] + mpert[step]
159
+ x_pred = A @ x_est
160
+ P_pred = A @ P @ A.T + Q
161
+ if not without_measure and z is not None:
162
+ K_kal = P_pred @ H.T @ np.linalg.inv(H @ P_pred @ H.T + R)
163
+ x_est = x_pred + K_kal @ (z - H @ x_pred)
164
+ P = (np.eye(n_state) - K_kal @ H) @ P_pred
165
+ else:
166
+ x_est = x_pred
167
+ P = P_pred
168
+ estimates.append(x_est[induB])
169
+ P_history.append(P.copy())
170
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
171
+ ax1.plot(time0[1:], estimates, label='Estimation Kalman', linewidth=2)
172
+ ax1.plot(time0, u_sim[induB, :], label='Vraie u_B', linestyle='--')
173
+ ax1.legend()
174
+ ax1.set_title("Estimation de u_B")
175
+ ax1.set_xlabel("Temps (s)")
176
+ ax1.set_ylabel("Déplacement (m)")
177
+ cov_uB = [P[induB, induB] for P in P_history]
178
+ ax2.plot(time0, cov_uB, label='Covariance u_B', linewidth=2)
179
+ ax2.legend()
180
+ ax2.set_title("Évolution de la covariance")
181
+ ax2.set_xlabel("Temps (s)")
182
+ ax2.set_ylabel("Variance")
183
+ animation = "Animation non disponible pour Kalman"
184
+ else:
185
+ return "Mode non implémenté", "", f"Temps: {time.time() - start_time:.2f}s"
186
+
187
+ buf = BytesIO()
188
+ fig.savefig(buf, format="png", dpi=100)
189
+ buf.seek(0)
190
+ plot_b64 = base64.b64encode(buf.read()).decode('utf-8')
191
+ plt.close(fig)
192
+ plot_html = f'<img src="data:image/png;base64,{plot_b64}" style="max-width:100%;">'
193
+ elapsed = f"Temps de calcul: {time.time() - start_time:.2f}s"
194
+ if mode == "Boucle ouverte":
195
+ info_str = f"{elapsed}, Stabilité: {settling_time}"
196
+ else:
197
+ info_str = elapsed
198
+ return plot_html, animation, info_str
199
+ except Exception as e:
200
+ return f"Erreur: {str(e)}", "", f"Temps: {time.time() - start_time:.2f}s"
201
+
202
+ def generate_schema():
203
+ fig, ax = plt.subplots(figsize=(10, 6))
204
+ ax.text(0.1, 0.8, 'i', fontsize=14, ha='center')
205
+ ax.arrow(0.15, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
206
+ ax.text(0.3, 0.8, 'Moteur', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue"))
207
+ ax.arrow(0.4, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
208
+ ax.text(0.55, 0.8, 'F_m', fontsize=14, ha='center')
209
+ ax.arrow(0.6, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
210
+ ax.text(0.75, 0.8, 'Transmission', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightgreen"))
211
+ ax.arrow(0.85, 0.8, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
212
+ ax.text(0.95, 0.8, 'F_t', fontsize=14, ha='center')
213
+ ax.arrow(0.15, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
214
+ ax.text(0.3, 0.5, 'C_m', fontsize=14, ha='center')
215
+ ax.arrow(0.4, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
216
+ ax.text(0.55, 0.5, 'θ', fontsize=14, ha='center')
217
+ ax.arrow(0.6, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
218
+ ax.text(0.75, 0.5, 'f_A', fontsize=14, ha='center')
219
+ ax.arrow(0.85, 0.5, 0.1, 0, head_width=0.03, head_length=0.03, fc='k', ec='k')
220
+ ax.text(0.95, 0.5, 'u_A', fontsize=14, ha='center')
221
+ ax.arrow(0.5, 0.3, 0, -0.1, head_width=0.03, head_length=0.03, fc='k', ec='k')
222
+ ax.text(0.5, 0.2, 'Poutre Flexible', fontsize=14, ha='center', bbox=dict(boxstyle="round,pad=0.3", facecolor="lightcoral"))
223
+ ax.arrow(0.5, 0.1, 0, -0.05, head_width=0.03, head_length=0.03, fc='k', ec='k')
224
+ ax.text(0.5, 0.05, 'u_B', fontsize=14, ha='center')
225
+ ax.set_xlim(0, 1)
226
+ ax.set_ylim(0, 1)
227
+ ax.axis('off')
228
+ buf = BytesIO()
229
+ fig.savefig(buf, format="png", dpi=100)
230
+ buf.seek(0)
231
+ img_b64 = base64.b64encode(buf.read()).decode('utf-8')
232
+ plt.close(fig)
233
+ return f'<img src="data:image/png;base64,{img_b64}" style="max-width:100%;">'
234
+
235
+ def compute_ft_gt(rendement, cm, theta_dot, fa_dot, ua_dot):
236
+ if rendement == 1:
237
+ ft = cm * theta_dot
238
+ gt = fa_dot * ua_dot
239
+ equal = "Oui" if np.isclose(ft, gt) else "Non"
240
+ return f"F_t = {ft:.3f}, G_t = {gt:.3f}, Égal: {equal}"
241
+ else:
242
+ return r"Pour rendement unitaire, F_t = C_m * \dot{\theta}, G_t = f_A * \dot{u_A}"
243
+
244
+ def quiz_answer(choice):
245
+ if choice == "Estimer u_B sans mesure directe":
246
+ return "Correct ! Le jumeau permet d'estimer états non mesurés."
247
+ else:
248
+ return "Incorrect. Réessayez."
249
+
250
+ desc_prep = r"""
251
+ ### Travail Préparatoire (Section 4.1 des rapports)
252
+ Étude du système : Schéma bloc en boucle ouverte. Relation F_t et G_t : Sous hypothèse de conservation d'énergie (rendement unitaire), F_t = G_t (d'après conservation C_m \dot{\theta} = f_A \dot{u_A}).
253
+ Intérêt du jumeau : Estimer u_B sans mesure directe pour contrôle en boucle fermée.
254
+ """
255
+ desc_open = """
256
+ ### Boucle Ouverte (Section 4.2.1)
257
+ Simulation sans rétroaction. Observez le déplacement u_B avec/sans perturbation. La position de B suit A avec retard (forme sinusoïdale ou échelon).
258
+ """
259
+ desc_pid = """
260
+ ### Boucle Fermée PID
261
+ Contrôle avec PID pour suivre la consigne. Observez l'erreur de suivi.
262
+ """
263
+ desc_kalman = """
264
+ ### Filtre de Kalman
265
+ Estimation d'état avec filtre Kalman pour filtrer le bruit sur la mesure u_B.
266
+ """
267
+
268
+ with gr.Blocks() as demo:
269
+ gr.Markdown("# Jumeaux Numériques : Simulation de Robot Souple\nDémo interactive basée sur le cours de Renaud Ferrier - Centrale Lyon ENISE.")
270
+
271
+ with gr.Tabs():
272
+ with gr.Tab("Préparatoire"):
273
+ gr.Markdown(desc_prep)
274
+ gr.Markdown("### Schéma Bloc du Système")
275
+ schema_img = gr.HTML(value=generate_schema())
276
+ gr.Markdown("### Conservation d'Énergie : F_t = G_t")
277
+ with gr.Row():
278
+ rendement_slider = gr.Slider(0.5, 1, 1, label="Rendement (η)", info="Hypothèse rendement unitaire pour conservation")
279
+ cm_input = gr.Number(1e-3, label="C_m (constante moteur)")
280
+ theta_dot_input = gr.Number(10, label="\\dot{\\theta} (vitesse angulaire)")
281
+ fa_dot_input = gr.Number(100, label="f_A (force en A)")
282
+ ua_dot_input = gr.Number(0.1, label="\\dot{u_A} (vitesse en A)")
283
+ compute_btn = gr.Button("Calculer F_t et G_t")
284
+ ft_gt_output = gr.Textbox(label="Résultat")
285
+ compute_btn.click(compute_ft_gt, inputs=[rendement_slider, cm_input, theta_dot_input, fa_dot_input, ua_dot_input], outputs=ft_gt_output)
286
+ gr.Markdown("### Intérêt du Jumeau Numérique")
287
+ quiz_radio = gr.Radio(["Estimer u_B sans mesure directe", "Simuler rapidement", "Réduire coûts"], label="Quel est l'intérêt principal du jumeau ?")
288
+ quiz_btn = gr.Button("Vérifier")
289
+ quiz_output = gr.Textbox()
290
+ quiz_btn.click(quiz_answer, inputs=quiz_radio, outputs=quiz_output)
291
+ gr.Markdown("### Probabilités Gaussiennes pour Bruits\nDensité : $ p(x) = \\frac{1}{\\sqrt{2\\pi \\sigma^2}} \\exp\\left(-\\frac{(x-\\mu)^2}{2\\sigma^2}\\right) $\nUtilisée pour covariances Q, R dans Kalman.")
292
+
293
+ with gr.Tab("Boucle ouverte"):
294
+ gr.Markdown(desc_open)
295
+ solicitation_dropdown = gr.Dropdown(["échelon", "sinusoïdale"], value="échelon", label="Type de sollicitation", info="Échelon à t=1s ou sinusoïdale")
296
+ with gr.Row():
297
+ nx_slider = gr.Slider(5, 20, 10, 1, label="nx (éléments)", info="Précision poutre")
298
+ delta_slider = gr.Slider(0.001, 0.05, 0.01, label="δt (pas temps)")
299
+ q_slider = gr.Slider(0, 0.001, 0.0001, label="q (variance perturbation)")
300
+ with_pert = gr.Checkbox(True, label="Perturbation ?")
301
+ compare_open_open = gr.Checkbox(False, visible=False)
302
+ without_measure_open = gr.Checkbox(False, visible=False)
303
+ mode_open = gr.Textbox("Boucle ouverte", visible=False)
304
+ run_open = gr.Button("Simuler")
305
+ plot_open = gr.HTML()
306
+ anim_open = gr.HTML()
307
+ time_open = gr.Textbox(label="Info")
308
+ run_open.click(run_simulation, inputs=[nx_slider, delta_slider, q_slider, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Checkbox(value=False, visible=False), with_pert, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_dropdown, compare_open_open, without_measure_open, mode_open], outputs=[plot_open, anim_open, time_open])
309
+
310
+ with gr.Tab("Boucle fermée PID"):
311
+ gr.Markdown(desc_pid)
312
+ with gr.Row():
313
+ nx_pid = gr.Slider(5, 20, 10, 1, label="nx")
314
+ delta_pid = gr.Slider(0.001, 0.05, 0.01, label="δt")
315
+ r_pid = gr.Slider(0, 0.001, 0.0001, label="r (bruit mesure)")
316
+ Kp_slider = gr.Slider(0, 10, 0.5, label="Kp")
317
+ Ki_slider = gr.Slider(0, 1, 0.1, label="Ki")
318
+ Kd_slider = gr.Slider(0, 0.1, 0.01, label="Kd")
319
+ with_noise_pid = gr.Checkbox(True, label="Bruit mesure ?")
320
+ compare_open_pid = gr.Checkbox(False, label="Comparer avec boucle ouverte")
321
+ solicitation_pid = gr.Textbox("échelon", visible=False)
322
+ without_measure_pid = gr.Checkbox(False, visible=False)
323
+ mode_pid = gr.Textbox("Boucle fermée PID", visible=False)
324
+ run_pid = gr.Button("Simuler")
325
+ plot_pid = gr.HTML()
326
+ anim_pid = gr.HTML()
327
+ time_pid = gr.Textbox()
328
+ run_pid.click(run_simulation, inputs=[nx_pid, delta_pid, gr.Number(value=0, visible=False), r_pid, Kp_slider, Ki_slider, Kd_slider, gr.Number(value=0, visible=False), with_noise_pid, gr.Checkbox(value=False, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_pid, compare_open_pid, without_measure_pid, mode_pid], outputs=[plot_pid, anim_pid, time_pid])
329
+
330
+ with gr.Tab("Filtre de Kalman"):
331
+ gr.Markdown(desc_kalman)
332
+ with gr.Row():
333
+ nx_kal = gr.Slider(5, 15, 10, 1, label="nx")
334
+ delta_kal = gr.Slider(0.001, 0.05, 0.01, label="δt")
335
+ q_kal = gr.Slider(0, 0.001, 0.0001, label="q (bruit processus)")
336
+ r_kal = gr.Slider(0, 0.001, 0.0001, label="r (bruit mesure)")
337
+ without_measure_kal = gr.Checkbox(False, label="Sans mesure u_B (jumeau)")
338
+ solicitation_kal = gr.Textbox("échelon", visible=False)
339
+ compare_open_kal = gr.Checkbox(False, visible=False)
340
+ mode_kal = gr.Textbox("Filtre de Kalman", visible=False)
341
+ run_kal = gr.Button("Simuler")
342
+ plot_kal = gr.HTML()
343
+ anim_kal = gr.HTML()
344
+ time_kal = gr.Textbox()
345
+ run_kal.click(run_simulation, inputs=[nx_kal, delta_kal, q_kal, r_kal, gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Checkbox(value=False, visible=False), gr.Checkbox(value=False, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), gr.Number(value=0, visible=False), solicitation_kal, compare_open_kal, without_measure_kal, mode_kal], outputs=[plot_kal, anim_kal, time_kal])
346
+
347
+ demo.launch()
requirements.txt CHANGED
@@ -1,6 +1,3 @@
1
  gradio==4.32.0
2
  numpy==1.24.3
3
- matplotlib==3.7.2
4
- scipy==1.10.1
5
- pillow==9.5.0
6
- huggingface_hub==0.19.4
 
1
  gradio==4.32.0
2
  numpy==1.24.3
3
+ matplotlib==3.7.2
 
 
 
requirements_complex.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio==4.32.0
2
+ numpy==1.24.3
3
+ matplotlib==3.7.2
4
+ scipy==1.10.1
5
+ pillow==9.5.0
6
+ huggingface_hub==0.19.4