ship-roll / app.py
dddddd1ff's picture
Update app.py
3a44936 verified
Raw History Blame Contribute Delete
8.11 kB
# --------------------------------------------------
# 1. 필수 라이브러리
# (Hugging Face는 requirements.txt를 읽음)
# --------------------------------------------------
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import os
import uuid
print("Gradio 및 필수 라이브러리 로드 완료.")
# --------------------------------------------------
# 2. 시뮬레이션 엔진 (v6와 동일)
# --------------------------------------------------
I, c, k = 1.0, 0.2, 1.0
A_wave_1, omega_wave_1 = 0.2, 2.0 # type1
A_wave_2, omega_wave_2 = 0.5, 0.8 # type2
A_wave_3a, omega_wave_3a = 0.2, 1.0 # type3
A_wave_3b, omega_wave_3b = 0.1, 3.0
dt = 0.01
T_end = 20.0
t_array = np.arange(0, T_end, dt)
def wave_torque_generator(wave_type, t_array):
if wave_type == "type1":
return A_wave_1 * np.sin(omega_wave_1 * t_array)
elif wave_type == "type2":
return A_wave_2 * np.sin(omega_wave_2 * t_array)
elif wave_type == "type3":
torque_a = A_wave_3a * np.sin(omega_wave_3a * t_array)
torque_b = A_wave_3b * np.sin(omega_wave_3b * t_array)
return torque_a + torque_b
elif wave_type == "type4_random":
raw_noise = np.random.randn(len(t_array)) * 0.5
window = np.ones(50) / 50
smooth_noise = np.convolve(raw_noise, window, mode='same')
return smooth_noise
else:
return np.zeros_like(t_array)
class PIDController:
def __init__(self, Kp, Ki, Kd):
self.Kp, self.Ki, self.Kd = Kp, Ki, Kd
self.integral, self.prev_error = 0.0, 0.0
def reset(self):
self.integral, self.prev_error = 0.0, 0.0
def update(self, r, r_dot, dt):
error = 0.0 - r
self.integral += error * dt
derivative = (error - self.prev_error) / dt
self.prev_error = error
return self.Kp * error + self.Ki * self.integral + self.Kd * derivative
class FuzzyController:
def __init__(self):
self.Kp = 2.0
self.Kd = 2.5
def reset(self):
pass
def update(self, r, r_dot, dt):
angle = r
angle_rate = r_dot
u_p = -self.Kp * angle
u_d = -self.Kd * angle_rate
u = u_p + u_d
return u
class FuzzyPIDController:
def __init__(self):
self.pid_controller = PIDController(Kp=0, Ki=0, Kd=0)
def reset(self):
self.pid_controller.reset()
def update(self, r, r_dot, dt):
error = 0.0 - r
if abs(error) > 0.1:
self.pid_controller.Kp = 4.0
self.pid_controller.Ki = 3.0
self.pid_controller.Kd = 1.5
else:
self.pid_controller.Kp = 2.0
self.pid_controller.Ki = 4.0
self.pid_controller.Kd = 2.5
return self.pid_controller.update(r, r_dot, dt)
def simulate_roll_data(control_mode, wave_type):
M_w_array = wave_torque_generator(wave_type, t_array)
r, r_dot = np.deg2rad(10.0), 0.0
r_list = []
pid = PIDController(Kp=4.0, Ki=3.0, Kd=1.5)
fuzzy = FuzzyController()
fuzzy_pid = FuzzyPIDController()
pid.reset()
fuzzy.reset()
fuzzy_pid.reset()
for i, t in enumerate(t_array):
M_w = M_w_array[i]
if control_mode == "none": M_c = 0.0
elif control_mode == "pid": M_c = pid.update(r, r_dot, dt)
elif control_mode == "fuzzy": M_c = fuzzy.update(r, r_dot, dt)
elif control_mode == "fuzzy_pid": M_c = fuzzy_pid.update(r, r_dot, dt)
else: M_c = 0.0
r_ddot = (M_w + M_c - c * r_dot - k * r) / I
r_dot = r_dot + r_ddot * dt
r = r + r_dot * dt
r_list.append(r)
return t_array, np.array(r_list)
# --------------------------------------------------
# 3. Gradio 메인 함수 (GIF 생성 + 분석 데이터)
# --------------------------------------------------
def run_simulation_and_analyze(control_mode, wave_type):
print(f"[{control_mode} / {wave_type}] 조건으로 {T_end}초 시뮬레이션 및 GIF 생성 시작...")
t, r_array = simulate_roll_data(control_mode, wave_type)
fig = plt.figure(figsize=(8, 6))
gs = fig.add_gridspec(2, 1, height_ratios=[1, 1])
ax_ship = fig.add_subplot(gs[0])
ax_ship.set_xlim(-2, 2); ax_ship.set_ylim(-1, 1); ax_ship.set_aspect('equal')
ax_ship.set_title(f"Ship Roll Animation ({control_mode} / {wave_type})")
ship_width = 1.5; ship_height = 0.3
ship_line, = ax_ship.plot([], [], lw=3, color='blue')
ax_ship.axhline(0, linestyle="--", color='cyan')
ax_rt = fig.add_subplot(gs[1])
ax_rt.set_xlim(t[0], t[-1])
r_abs_max = np.max(np.abs(r_array))
r_lim = max(r_abs_max * 1.2, 0.2)
ax_rt.set_ylim(-r_lim, r_lim)
ax_rt.set_xlabel("Time [s]"); ax_rt.set_ylabel(f"Roll angle r(t) [rad] (Total: {T_end}s)"); ax_rt.grid(True)
rt_line, = ax_rt.plot([], [], lw=2, color='red')
def init():
ship_line.set_data([], []); rt_line.set_data([], [])
return ship_line, rt_line
def update(frame):
r_now = r_array[frame]
x_corners = np.array([-ship_width/2, ship_width/2, ship_width/2, -ship_width/2, -ship_width/2])
y_corners = np.array([-ship_height/2, -ship_height/2, ship_height/2, ship_height/2, -ship_height/2])
cos_r, sin_r = np.cos(r_now), np.sin(r_now)
x_rot = x_corners * cos_r - y_corners * sin_r
y_rot = x_corners * sin_r + y_corners * cos_r
ship_line.set_data(x_rot, y_rot + 0.1)
rt_line.set_data(t[:frame+1], r_array[:frame+1])
return ship_line, rt_line
ani = FuncAnimation(fig, update, frames=range(0, len(t), 20), init_func=init, blit=False)
filename = f"sim_animation_{uuid.uuid4().hex}.gif"
print(f"애니메이션 생성 중... (총 {T_end}초 분량) -> {filename}")
ani.save(filename, writer='pillow', fps=15)
plt.close(fig)
print(f"GIF 생성 완료!")
max_roll_rad = np.max(np.abs(r_array))
max_roll_deg = np.rad2deg(max_roll_rad)
idx_8s = int(8.0 / dt)
iae_after_8s = np.sum(np.abs(r_array[idx_8s:])) * dt
# ⬇️ [핵심 수정] (v6.1 -> v6.2)
# 여러 줄로 나눴던 f-string을 f"""...""" (따옴표 3개)로 감싸서
# 문법 오류(SyntaxError)가 나지 않도록 수정
stats_text = f"""--- 📊 정량 분석 결과 ({T_end}초) ---
1. 최대 롤각 (전체 시간): {max_roll_deg:.2f} (degree)
(배가 최대로 기울어진 각도 - 오버슈트 포함)
2. 안정화 성능 (IAE @ 8s-{T_end}s): {iae_after_8s:.3f}
(8초 이후의 총 흔들림 지수. 작을수록 파도를 잘 이겨내고 멈춘 것.)
"""
return filename, stats_text
# --------------------------------------------------
# 4. Gradio 인터페이스 생성 및 실행
# --------------------------------------------------
inputs = [
gr.Dropdown(
label="1. 제어 모드 선택",
choices=[("제어 없음", "none"), ("PID", "pid"), ("Fuzzy", "fuzzy"), ("Fuzzy PID", "fuzzy_pid")],
value="pid"
),
gr.Dropdown(
label="2. 파도 타입 선택",
choices=[
("유형 1 (작고 빠름)", "type1"),
("유형 2 (크고 느림)", "type2"),
("유형 3 (복잡한 규칙)", "type3"),
("유형 4 (매번 다름 - 불규칙)", "type4_random")
],
value="type2"
)
]
outputs = [
gr.Image(label="시뮬레이션 애니메이션 결과 (GIF)"),
gr.Text(label=f"성능 분석 결과 (v6.2)")
]
iface = gr.Interface(
fn=run_simulation_and_analyze,
inputs=inputs,
outputs=outputs,
title=f"🚢 선박 롤링 제어 시뮬레이션 (v6.2 - 20s + 4 Modes)",
description="제어 모드와 파도 타입을 선택하고 'Submit' 버튼을 누르세요. (20초 GIF 생성)",
allow_flagging="never",
examples=[["pid", "type2"], ["fuzzy_pid", "type2"], ["none", "type2"],]
)
# ⬇️ [수정] (v6.1 -> v6.2)
print(f"Gradio 인터페이스(v6.2)를 실행합니다...")
iface.launch() # Hugging Face에서는 share=True 없이 실행