Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,10 +3,11 @@ import numpy as np
|
|
| 3 |
import matplotlib.pyplot as plt
|
| 4 |
|
| 5 |
# --- 核心計算與繪圖函數 ---
|
| 6 |
-
def
|
| 7 |
"""
|
| 8 |
-
|
| 9 |
-
|
|
|
|
| 10 |
"""
|
| 11 |
# 物理條件檢查:折射必須 V2 > V1
|
| 12 |
if v2 <= v1:
|
|
@@ -17,100 +18,122 @@ def plot_seismic_refraction(v1, v2, h, x_max):
|
|
| 17 |
ax.set_ylabel("Travel Time (s)")
|
| 18 |
ax.grid(True)
|
| 19 |
ax.set_title("Travel-Time Curve")
|
| 20 |
-
return fig, "### 參數錯誤\n請確保第二層速度 (V2) 大於第一層速度 (V1)
|
| 21 |
|
| 22 |
# 1. 計算關鍵物理量
|
|
|
|
| 23 |
theta_c_rad = np.arcsin(v1 / v2)
|
| 24 |
theta_c_deg = np.rad2deg(theta_c_rad)
|
| 25 |
ti = (2 * h * np.cos(theta_c_rad)) / v1
|
| 26 |
xc = 2 * h * np.sqrt((v2 + v1) / (v2 - v1))
|
| 27 |
|
|
|
|
|
|
|
|
|
|
| 28 |
# 2. 準備繪圖數據
|
| 29 |
x = np.linspace(0, x_max, 500)
|
|
|
|
| 30 |
t_direct = x / v1
|
|
|
|
| 31 |
t_refracted = (x / v2) + ti
|
|
|
|
|
|
|
|
|
|
| 32 |
t_first_arrival = np.minimum(t_direct, t_refracted)
|
| 33 |
|
| 34 |
-
# 3. 使用 Matplotlib 繪圖
|
| 35 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 36 |
|
| 37 |
-
#
|
| 38 |
ax.plot(x, t_direct, 'b--', label=f'Direct Wave (Slope 1/{v1:.0f})')
|
| 39 |
ax.plot(x, t_refracted, 'g--', label=f'Refracted Wave (Slope 1/{v2:.0f})')
|
| 40 |
-
ax.plot(x,
|
|
|
|
| 41 |
|
|
|
|
| 42 |
if xc < x_max:
|
| 43 |
ax.axvline(x=xc, color='k', linestyle=':', label=f'Crossover Distance = {xc:.1f} m')
|
| 44 |
ax.plot(xc, xc/v1, 'ko')
|
| 45 |
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
#
|
| 49 |
-
ax.set_title("Interactive Seismic
|
| 50 |
ax.set_xlabel("Distance (m)", fontsize=12)
|
| 51 |
ax.set_ylabel("Travel Time (s)", fontsize=12)
|
| 52 |
|
| 53 |
ax.legend()
|
| 54 |
ax.grid(True)
|
| 55 |
ax.set_xlim(0, x_max)
|
| 56 |
-
|
|
|
|
|
|
|
| 57 |
plt.tight_layout()
|
| 58 |
|
| 59 |
-
# 4. 準備輸出的說明文字
|
| 60 |
results_md = f"""
|
| 61 |
### 🔬 分析結果
|
| 62 |
|
| 63 |
-
|
| 64 |
|
|
|
|
| 65 |
- **臨界角 (Critical Angle, θc)**:
|
| 66 |
- 公式: `θc = arcsin(V1 / V2)`
|
| 67 |
- 計算: `arcsin({v1:.0f} / {v2:.0f})` = **{theta_c_deg:.2f}°**
|
| 68 |
- *這是產生「抄捷徑」折射波的關鍵入射角度。*
|
| 69 |
-
|
| 70 |
- **截時 (Intercept Time, tᵢ)**:
|
| 71 |
- 公式: `tᵢ = (2 * h * cos(θc)) / V1`
|
| 72 |
- 計算: `(2 * {h:.0f} * cos({theta_c_deg:.2f}°)) / {v1:.0f}` = **{ti*1000:.1f} ms**
|
| 73 |
-
-
|
| 74 |
-
|
| 75 |
- **交越距離 (Crossover Distance, Xc)**:
|
| 76 |
- 公式: `Xc = 2 * h * sqrt((V2 + V1) / (V2 - V1))`
|
| 77 |
- 計算: `2 * {h:.0f} * sqrt(({v2:.0f} + {v1:.0f}) / ({v2:.0f} - {v1:.0f}))` = **{xc:.1f} m**
|
| 78 |
-
-
|
| 79 |
-
"""
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
return fig, results_md
|
| 82 |
|
| 83 |
-
# --- Gradio 介面設定
|
| 84 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 85 |
-
gr.Markdown("# 地心震波奇幻之旅:互動折射震測實驗室 🌍")
|
| 86 |
gr.Markdown(
|
| 87 |
"""
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
"""
|
| 92 |
)
|
| 93 |
|
| 94 |
with gr.Row():
|
| 95 |
with gr.Column(scale=1):
|
| 96 |
-
gr.Markdown("### ⚙️
|
| 97 |
v1_slider = gr.Slider(label="V1: 第一層速度 (m/s)", minimum=300, maximum=3000, value=800, step=50)
|
| 98 |
v2_slider = gr.Slider(label="V2: 第二層速度 (m/s)", minimum=500, maximum=6000, value=2500, step=50)
|
| 99 |
h_slider = gr.Slider(label="h: 第一層厚度 (m)", minimum=5, maximum=100, value=20, step=1)
|
| 100 |
xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=500, value=250, step=10)
|
| 101 |
|
| 102 |
-
submit_btn = gr.Button("
|
| 103 |
|
| 104 |
with gr.Column(scale=2):
|
| 105 |
-
gr.Markdown("### 📊
|
| 106 |
plot_output = gr.Plot()
|
| 107 |
|
| 108 |
with gr.Row():
|
| 109 |
-
results_output = gr.Markdown("### 🔬 分析結果\n
|
| 110 |
|
| 111 |
# --- 事件監聽 ---
|
| 112 |
submit_btn.click(
|
| 113 |
-
fn=
|
| 114 |
inputs=[v1_slider, v2_slider, h_slider, xmax_slider],
|
| 115 |
outputs=[plot_output, results_output]
|
| 116 |
)
|
|
@@ -118,17 +141,21 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 118 |
gr.Markdown(
|
| 119 |
"""
|
| 120 |
---
|
| 121 |
-
###
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
<footer>
|
| 128 |
-
<p style="text-align:center; color:grey;">地球物理小教室 © 2025 - 由 Gemini 根據課程文件生成</p>
|
| 129 |
-
</footer>
|
| 130 |
"""
|
| 131 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
if __name__ == "__main__":
|
| 134 |
demo.launch()
|
|
|
|
| 3 |
import matplotlib.pyplot as plt
|
| 4 |
|
| 5 |
# --- 核心計算與繪圖函數 ---
|
| 6 |
+
def plot_seismic_exploration(v1, v2, h, x_max):
|
| 7 |
"""
|
| 8 |
+
根據輸入的地層參數,計算並繪製震測的走時曲線。
|
| 9 |
+
*** 新增了反射波的模擬 ***
|
| 10 |
+
圖表內的標籤為英文。
|
| 11 |
"""
|
| 12 |
# 物理條件檢查:折射必須 V2 > V1
|
| 13 |
if v2 <= v1:
|
|
|
|
| 18 |
ax.set_ylabel("Travel Time (s)")
|
| 19 |
ax.grid(True)
|
| 20 |
ax.set_title("Travel-Time Curve")
|
| 21 |
+
return fig, "### 參數錯誤\n請確保第二層速度 (V2) 大於第一層速度 (V1),否則無法產生臨界折射現象。"
|
| 22 |
|
| 23 |
# 1. 計算關鍵物理量
|
| 24 |
+
# 折射波相關
|
| 25 |
theta_c_rad = np.arcsin(v1 / v2)
|
| 26 |
theta_c_deg = np.rad2deg(theta_c_rad)
|
| 27 |
ti = (2 * h * np.cos(theta_c_rad)) / v1
|
| 28 |
xc = 2 * h * np.sqrt((v2 + v1) / (v2 - v1))
|
| 29 |
|
| 30 |
+
# 反射波相關
|
| 31 |
+
t0 = (2 * h) / v1
|
| 32 |
+
|
| 33 |
# 2. 準備繪圖數據
|
| 34 |
x = np.linspace(0, x_max, 500)
|
| 35 |
+
# 直達波
|
| 36 |
t_direct = x / v1
|
| 37 |
+
# 折射波
|
| 38 |
t_refracted = (x / v2) + ti
|
| 39 |
+
# 反射波 (新增)
|
| 40 |
+
t_reflected = np.sqrt(t0**2 + (x / v1)**2)
|
| 41 |
+
# 初達波
|
| 42 |
t_first_arrival = np.minimum(t_direct, t_refracted)
|
| 43 |
|
| 44 |
+
# 3. 使用 Matplotlib 繪圖
|
| 45 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 46 |
|
| 47 |
+
# 繪製各波線
|
| 48 |
ax.plot(x, t_direct, 'b--', label=f'Direct Wave (Slope 1/{v1:.0f})')
|
| 49 |
ax.plot(x, t_refracted, 'g--', label=f'Refracted Wave (Slope 1/{v2:.0f})')
|
| 50 |
+
ax.plot(x, t_reflected, 'm:', linewidth=2, label='Reflected Wave (Hyperbola)') # 新增反射波
|
| 51 |
+
ax.plot(x, t_first_arrival, 'r-', linewidth=3, label='First Arrival (Refraction)')
|
| 52 |
|
| 53 |
+
# 標示交越距離
|
| 54 |
if xc < x_max:
|
| 55 |
ax.axvline(x=xc, color='k', linestyle=':', label=f'Crossover Distance = {xc:.1f} m')
|
| 56 |
ax.plot(xc, xc/v1, 'ko')
|
| 57 |
|
| 58 |
+
# 標示截時
|
| 59 |
+
ax.plot(0, ti, 'go', markersize=8, label=f'Refraction Intercept = {ti*1000:.1f} ms')
|
| 60 |
+
# 標示雙程走時 (新增)
|
| 61 |
+
ax.plot(0, t0, 'mo', markersize=8, label=f'Reflection Two-Way Time = {t0*1000:.1f} ms')
|
| 62 |
|
| 63 |
+
# 設定圖表樣式
|
| 64 |
+
ax.set_title("Interactive Seismic Exploration T-X Plot", fontsize=16)
|
| 65 |
ax.set_xlabel("Distance (m)", fontsize=12)
|
| 66 |
ax.set_ylabel("Travel Time (s)", fontsize=12)
|
| 67 |
|
| 68 |
ax.legend()
|
| 69 |
ax.grid(True)
|
| 70 |
ax.set_xlim(0, x_max)
|
| 71 |
+
# 自動調整 Y 軸,確保能看到所有曲線
|
| 72 |
+
y_max = max(np.max(t_direct), np.max(t_reflected))
|
| 73 |
+
ax.set_ylim(0, y_max * 1.1)
|
| 74 |
plt.tight_layout()
|
| 75 |
|
| 76 |
+
# 4. 準備輸出的說明文字
|
| 77 |
results_md = f"""
|
| 78 |
### 🔬 分析結果
|
| 79 |
|
| 80 |
+
根據您設計的地層模型,我們計算出以下關鍵物理量:
|
| 81 |
|
| 82 |
+
#### 折射波 (Refracted Wave)
|
| 83 |
- **臨界角 (Critical Angle, θc)**:
|
| 84 |
- 公式: `θc = arcsin(V1 / V2)`
|
| 85 |
- 計算: `arcsin({v1:.0f} / {v2:.0f})` = **{theta_c_deg:.2f}°**
|
| 86 |
- *這是產生「抄捷徑」折射波的關鍵入射角度。*
|
|
|
|
| 87 |
- **截時 (Intercept Time, tᵢ)**:
|
| 88 |
- 公式: `tᵢ = (2 * h * cos(θc)) / V1`
|
| 89 |
- 計算: `(2 * {h:.0f} * cos({theta_c_deg:.2f}°)) / {v1:.0f}` = **{ti*1000:.1f} ms**
|
| 90 |
+
- *折射波走時線在時間軸上的截距,隱含了第一層的厚度資訊。*
|
|
|
|
| 91 |
- **交越距離 (Crossover Distance, Xc)**:
|
| 92 |
- 公式: `Xc = 2 * h * sqrt((V2 + V1) / (V2 - V1))`
|
| 93 |
- 計算: `2 * {h:.0f} * sqrt(({v2:.0f} + {v1:.0f}) / ({v2:.0f} - {v1:.0f}))` = **{xc:.1f} m**
|
| 94 |
+
- *在此距離之後,折射波會比直達波先抵達。*
|
|
|
|
| 95 |
|
| 96 |
+
---
|
| 97 |
+
#### 反射波 (Reflected Wave)
|
| 98 |
+
- **雙程走時 (Two-Way Time, t₀)**:
|
| 99 |
+
- 公式: `t₀ = 2 * h / V1`
|
| 100 |
+
- 計算: `2 * {h:.0f} / {v1:.0f}` = **{t0*1000:.1f} ms**
|
| 101 |
+
- *這是震波垂直向下傳播並返回地表所需的時間,也是反射波雙曲線的頂點。*
|
| 102 |
+
"""
|
| 103 |
return fig, results_md
|
| 104 |
|
| 105 |
+
# --- Gradio 介面設定 ---
|
| 106 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
| 107 |
gr.Markdown(
|
| 108 |
"""
|
| 109 |
+
# 地心震波奇幻之旅:地球物理遊樂場 🌍
|
| 110 |
+
> 創意的發揮是一種學習,過程中,每個人同時是學生也是老師。
|
| 111 |
+
|
| 112 |
+
這個實驗室就是你的遊樂場。透過親手調整地層的參數,你將不只是在學習地球物理,更是在**創造和發現**地底下的物理法則。
|
| 113 |
+
你既是設計模型的老師,也是觀察結果的學生。動動手,看看你能發現什麼秘密!
|
| 114 |
"""
|
| 115 |
)
|
| 116 |
|
| 117 |
with gr.Row():
|
| 118 |
with gr.Column(scale=1):
|
| 119 |
+
gr.Markdown("### ⚙️ 設計你的地層模型")
|
| 120 |
v1_slider = gr.Slider(label="V1: 第一層速度 (m/s)", minimum=300, maximum=3000, value=800, step=50)
|
| 121 |
v2_slider = gr.Slider(label="V2: 第二層速度 (m/s)", minimum=500, maximum=6000, value=2500, step=50)
|
| 122 |
h_slider = gr.Slider(label="h: 第一層厚度 (m)", minimum=5, maximum=100, value=20, step=1)
|
| 123 |
xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=500, value=250, step=10)
|
| 124 |
|
| 125 |
+
submit_btn = gr.Button("🚀 開始探勘!", variant="primary")
|
| 126 |
|
| 127 |
with gr.Column(scale=2):
|
| 128 |
+
gr.Markdown("### 📊 觀測走時-距離圖 (T-X Plot)")
|
| 129 |
plot_output = gr.Plot()
|
| 130 |
|
| 131 |
with gr.Row():
|
| 132 |
+
results_output = gr.Markdown("### 🔬 分析結果\n請設計你的地層模型並點擊「開始探勘!」以顯示計算結果。")
|
| 133 |
|
| 134 |
# --- 事件監聽 ---
|
| 135 |
submit_btn.click(
|
| 136 |
+
fn=plot_seismic_exploration,
|
| 137 |
inputs=[v1_slider, v2_slider, h_slider, xmax_slider],
|
| 138 |
outputs=[plot_output, results_output]
|
| 139 |
)
|
|
|
|
| 141 |
gr.Markdown(
|
| 142 |
"""
|
| 143 |
---
|
| 144 |
+
### 🚀 探索與發現 (Exploration & Discovery)
|
| 145 |
+
試著回答下面的問題,你就是自己的老師!
|
| 146 |
+
1. **折射 vs. 反射**: 在近距離 (X 接近 0) 時,哪一種波(直達波、反射波)最先抵達?為什麼?
|
| 147 |
+
2. **速度的影響**: 如果把 V1 和 V2 的速度差距拉得更大,折射波的走時線(綠色)會變得更陡還是更平緩?這對交越距離又有什麼影響?
|
| 148 |
+
3. **厚度的秘密**: 如果你只增加地層厚度 `h`,反射波的雙曲線(紫色)和折射波的截時 `tᵢ` 會如何變化?它們之間有什麼數學關聯嗎?
|
| 149 |
+
4. **消失的折射波**: 在什麼條件下,折射波(綠色線)會完全消失,或是永遠比直達波(藍色線)還慢?
|
|
|
|
|
|
|
|
|
|
| 150 |
"""
|
| 151 |
)
|
| 152 |
+
|
| 153 |
+
gr.HTML("""
|
| 154 |
+
<footer style="text-align:center; margin-top: 30px; color:grey;">
|
| 155 |
+
<p>「創意的發揮是一種學習,過程中,每個人同時是學生也是老師。」</p>
|
| 156 |
+
<p>地球物理遊樂場 © 2025 - 由 Gemini 根據課程文件與靈感生成</p>
|
| 157 |
+
</footer>
|
| 158 |
+
""")
|
| 159 |
|
| 160 |
if __name__ == "__main__":
|
| 161 |
demo.launch()
|