engdarwish commited on
Commit
7e39fcd
·
verified ·
1 Parent(s): 9b104b0

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -0
app.py CHANGED
@@ -11,6 +11,7 @@ identically on a laptop, a phone browser, or a Hugging Face Space.
11
 
12
  from __future__ import annotations
13
 
 
14
  import math
15
 
16
  import gradio as gr
@@ -18,6 +19,8 @@ import matplotlib
18
  matplotlib.use("Agg")
19
  import matplotlib.pyplot as plt
20
  import numpy as np
 
 
21
 
22
  from pendulum_physics import (
23
  PLANETS,
@@ -53,6 +56,96 @@ except ImportError:
53
  # ---------------------------------------------------------------------------
54
 
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  def plot_motion(length_cm: float, amplitude_deg: float, gravity: float, damping: float, duration_s: float):
57
  L = length_cm / 100.0
58
  theta0 = math.radians(amplitude_deg)
@@ -169,6 +262,28 @@ with gr.Blocks(title="مختبر البندول التفاعلي | Interactive P
169
  "*exact* nonlinear pendulum equation (RK4) and compares it against the small-angle approximation."
170
  )
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  with gr.Tab("المحاكاة الحية | Live Simulation"):
173
  with gr.Row():
174
  with gr.Column(scale=1):
 
11
 
12
  from __future__ import annotations
13
 
14
+ import io
15
  import math
16
 
17
  import gradio as gr
 
19
  matplotlib.use("Agg")
20
  import matplotlib.pyplot as plt
21
  import numpy as np
22
+ from matplotlib.patches import Circle
23
+ from PIL import Image
24
 
25
  from pendulum_physics import (
26
  PLANETS,
 
56
  # ---------------------------------------------------------------------------
57
 
58
 
59
+ def plot_pendulum_animation(length_cm: float, amplitude_deg: float, gravity: float, damping: float):
60
+ """Render pendulum visualization with camera view, theta(t), and phase space."""
61
+ L = length_cm / 100.0
62
+ theta0 = math.radians(amplitude_deg)
63
+ res = simulate(theta0, gravity, L, damping=damping, t_max=3.0, dt=0.01)
64
+
65
+ fig, axes = plt.subplots(1, 3, figsize=(16, 5))
66
+
67
+ # --- Subplot 1: Pendulum Camera View ---
68
+ ax1 = axes[0]
69
+ ax1.set_xlim(-0.5, 0.5)
70
+ ax1.set_ylim(-0.4, 0.1)
71
+ ax1.set_aspect('equal')
72
+ ax1.invert_yaxis()
73
+ ax1.set_facecolor('#e6d5cc')
74
+ ax1.set_title('Camera Feed (Vision Tracker)', fontsize=12, fontweight='bold')
75
+
76
+ # Draw pivot
77
+ ax1.plot(0, 0, 'ro', markersize=12, label='Pivot')
78
+
79
+ # Draw string and bob at current position
80
+ theta_current = res["theta"][-1]
81
+ x = L * np.sin(theta_current)
82
+ y = L * np.cos(theta_current)
83
+ ax1.plot([0, x], [0, y], 'b-', linewidth=2)
84
+
85
+ # Draw bob
86
+ bob = Circle((x, y), 0.03, color='white', ec='blue', linewidth=2)
87
+ ax1.add_patch(bob)
88
+
89
+ # Draw reference line
90
+ ax1.axvline(0, color='red', linestyle='--', alpha=0.5, linewidth=1)
91
+
92
+ # Add angle annotation
93
+ if abs(theta_current) > 0.01:
94
+ angle_arc = np.linspace(0, theta_current, 30)
95
+ arc_r = 0.1
96
+ ax1.plot(arc_r * np.sin(angle_arc), arc_r * np.cos(angle_arc), 'g--', linewidth=1)
97
+ ax1.text(0.15, -0.05, f'θ = {np.degrees(theta_current):.1f}°', fontsize=10, color='green')
98
+
99
+ ax1.legend(loc='upper right', fontsize=9)
100
+ ax1.set_xlabel('X (m)', fontsize=10)
101
+ ax1.set_ylabel('Y (m)', fontsize=10)
102
+ ax1.grid(True, alpha=0.3)
103
+
104
+ # --- Subplot 2: θ(t) vs Time ---
105
+ ax2 = axes[1]
106
+ ax2.plot(res["t"], np.degrees(res["theta"]), 'b-', linewidth=2, label='Exact (RK4)')
107
+ ax2.plot(res["t"], np.degrees(res["theta_small_angle"]), '--', color='#dc2626', linewidth=1.5, label='Small-angle (SHM)')
108
+ ax2.set_xlabel('Time (s)', fontsize=10)
109
+ ax2.set_ylabel('Angle θ (degrees)', fontsize=10)
110
+ ax2.set_title('Angular Position', fontsize=12, fontweight='bold')
111
+ ax2.grid(True, alpha=0.3)
112
+ ax2.legend(fontsize=9)
113
+
114
+ # --- Subplot 3: Phase Space (θ vs ω) ---
115
+ ax3 = axes[2]
116
+ ax3.plot(np.degrees(res["theta"]), res["omega"], 'r-', linewidth=2, alpha=0.7)
117
+ ax3.set_xlabel('Angle θ (degrees)', fontsize=10)
118
+ ax3.set_ylabel('Angular Velocity ω (rad/s)', fontsize=10)
119
+ ax3.set_title('Phase Space', fontsize=12, fontweight='bold')
120
+ ax3.grid(True, alpha=0.3)
121
+
122
+ plt.tight_layout()
123
+
124
+ # Convert to image
125
+ buf = io.BytesIO()
126
+ plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
127
+ buf.seek(0)
128
+ img = Image.open(buf)
129
+ plt.close()
130
+
131
+ # Compute summary
132
+ T0 = period_small_angle(gravity, L)
133
+ Tex = period_exact(theta0, gravity, L)
134
+ diff_pct = (Tex / T0 - 1.0) * 100
135
+
136
+ summary = (
137
+ f"**T (small-angle) = {T0:.5f} s** — **T (exact) = {Tex:.5f} s** "
138
+ f"— الفرق / difference: **{diff_pct:+.3f}%**\n\n"
139
+ f"عند سعة {amplitude_deg:.1f}°، خطأ تقريب الزاوية الصغيرة "
140
+ f"(SHM) هو {diff_pct:+.3f}% — هذا بالضبط سبب تحديد تجربة الكاميرا الحقيقية "
141
+ f"(`vision_lab/`) لزاوية إطلاق لا تتجاوز 15°.\n\n"
142
+ f"At {amplitude_deg:.1f}° amplitude, the small-angle (SHM) approximation error is "
143
+ f"{diff_pct:+.3f}% — exactly why the real camera experiment (`vision_lab/`) "
144
+ f"restricts the release angle to <= 15°."
145
+ )
146
+ return img, summary
147
+
148
+
149
  def plot_motion(length_cm: float, amplitude_deg: float, gravity: float, damping: float, duration_s: float):
150
  L = length_cm / 100.0
151
  theta0 = math.radians(amplitude_deg)
 
262
  "*exact* nonlinear pendulum equation (RK4) and compares it against the small-angle approximation."
263
  )
264
 
265
+ with gr.Tab("رسم البندول | Pendulum Animation"):
266
+ gr.Markdown(
267
+ "شاهد رسماً بصرياً للبندول مع مسار حركته وفضاء الطور.\n\n"
268
+ "Watch a visual rendering of the pendulum with its trajectory and phase portrait."
269
+ )
270
+ with gr.Row():
271
+ with gr.Column(scale=1):
272
+ length_cm_anim = gr.Slider(5, 150, value=30, step=1, label="طول الخيط L (cm) | String length")
273
+ amplitude_deg_anim = gr.Slider(1, 170, value=15, step=1, label="زاوية الإطلاق θ₀ (deg) | Release angle")
274
+ planet_anim = gr.Dropdown(list(PLANETS.keys()), value="Earth / الأرض", label="الجاذبية | Gravity (planet)")
275
+ damping_anim = gr.Slider(0.0, 3.0, value=0.0, step=0.05, label="التخامد b | Damping coefficient")
276
+ run_btn_anim = gr.Button("شغّل | Run", variant="primary")
277
+ with gr.Column(scale=2):
278
+ plot_out_anim = gr.Image(label="Camera Feed & Analysis")
279
+ text_out_anim = gr.Markdown()
280
+
281
+ def _run_anim(l, a, p, d):
282
+ return plot_pendulum_animation(l, a, PLANETS[p], d)
283
+
284
+ run_btn_anim.click(_run_anim, [length_cm_anim, amplitude_deg_anim, planet_anim, damping_anim], [plot_out_anim, text_out_anim])
285
+ demo.load(_run_anim, [length_cm_anim, amplitude_deg_anim, planet_anim, damping_anim], [plot_out_anim, text_out_anim])
286
+
287
  with gr.Tab("المحاكاة الحية | Live Simulation"):
288
  with gr.Row():
289
  with gr.Column(scale=1):