Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,104 +3,112 @@ import numpy as np
|
|
| 3 |
import matplotlib.pyplot as plt
|
| 4 |
|
| 5 |
# --- 核心計算與繪圖函數 ---
|
| 6 |
-
def plot_seismic_exploration(v1, v2, h, x_max):
|
| 7 |
"""
|
| 8 |
-
|
| 9 |
-
*** 新增了反射波的模擬 ***
|
| 10 |
-
圖表內的標籤為英文。
|
| 11 |
"""
|
| 12 |
-
#
|
|
|
|
| 13 |
if v2 <= v1:
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
return
|
| 22 |
|
| 23 |
-
#
|
| 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 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 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 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
|
| 53 |
-
# 標示交越距離
|
| 54 |
if xc < x_max:
|
| 55 |
-
|
| 56 |
-
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
|
| 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 |
-
|
| 85 |
-
|
| 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
|
|
|
|
| 104 |
|
| 105 |
# --- Gradio 介面設定 ---
|
| 106 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
@@ -109,24 +117,27 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 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("### 📊
|
| 129 |
-
|
|
|
|
| 130 |
|
| 131 |
with gr.Row():
|
| 132 |
results_output = gr.Markdown("### 🔬 分析結果\n請設計你的地層模型並點擊「開始探勘!」以顯示計算結果。")
|
|
@@ -134,19 +145,23 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 134 |
# --- 事件監聽 ---
|
| 135 |
submit_btn.click(
|
| 136 |
fn=plot_seismic_exploration,
|
| 137 |
-
inputs=[v1_slider, v2_slider, h_slider, xmax_slider],
|
| 138 |
-
outputs=[
|
| 139 |
)
|
| 140 |
|
| 141 |
gr.Markdown(
|
| 142 |
"""
|
| 143 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
### 🚀 探索與發現 (Exploration & Discovery)
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
3. **厚度的秘密**: 如果你只增加地層厚度 `h`,反射波的雙曲線(紫色)和折射波的截時 `tᵢ` 會如何變化?它們之間有什麼數學關聯嗎?
|
| 149 |
-
4. **消失的折射波**: 在什麼條件下,折射波(綠色線)會完全消失,或是永遠比直達波(藍色線)還慢?
|
| 150 |
"""
|
| 151 |
)
|
| 152 |
|
|
|
|
| 3 |
import matplotlib.pyplot as plt
|
| 4 |
|
| 5 |
# --- 核心計算與繪圖函數 ---
|
| 6 |
+
def plot_seismic_exploration(v1, v2, h, x_max, num_receivers):
|
| 7 |
"""
|
| 8 |
+
根據輸入的地層參數,計算並繪製震測的走時曲線與震測剖面概念圖。
|
|
|
|
|
|
|
| 9 |
"""
|
| 10 |
+
# === PART 1: 物理計算 (與之前相同) ===
|
| 11 |
+
# 物理條件檢查
|
| 12 |
if v2 <= v1:
|
| 13 |
+
# 產生兩個空的錯誤圖表
|
| 14 |
+
fig1, ax1 = plt.subplots(figsize=(10, 6))
|
| 15 |
+
ax1.text(0.5, 0.5, 'Error: V2 must be greater than V1', ha='center', va='center', color='red')
|
| 16 |
+
ax1.set_title("Travel-Time Curve")
|
| 17 |
+
fig2, ax2 = plt.subplots(figsize=(10, 4))
|
| 18 |
+
ax2.text(0.5, 0.5, 'Please ensure V2 > V1 to generate a valid model.', ha='center', va='center', color='red')
|
| 19 |
+
ax2.set_title("Seismic Profile Concept")
|
| 20 |
+
return fig1, fig2, "### 參數錯誤\n請確保第二層速度 (V2) 大於第一層速度 (V1),否則無法產生臨界折射現象。"
|
| 21 |
|
| 22 |
+
# 計算關鍵物理量
|
|
|
|
| 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 |
t0 = (2 * h) / v1
|
| 28 |
|
| 29 |
+
# === PART 2: 繪製 T-X 走時曲線圖 (Plot 1) ===
|
| 30 |
+
x_continuous = np.linspace(0, x_max, 500)
|
| 31 |
+
t_direct = x_continuous / v1
|
| 32 |
+
t_refracted = (x_continuous / v2) + ti
|
| 33 |
+
t_reflected = np.sqrt(t0**2 + (x_continuous / v1)**2)
|
| 34 |
+
t_first_arrival_continuous = np.minimum(t_direct, t_refracted)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
fig1, ax1 = plt.subplots(figsize=(10, 6))
|
| 37 |
+
ax1.plot(x_continuous, t_direct, 'b--', label=f'Direct Wave (Slope 1/{v1:.0f})')
|
| 38 |
+
ax1.plot(x_continuous, t_refracted, 'g--', label=f'Refracted Wave (Slope 1/{v2:.0f})')
|
| 39 |
+
ax1.plot(x_continuous, t_reflected, 'm:', linewidth=2, label='Reflected Wave (Hyperbola)')
|
| 40 |
+
ax1.plot(x_continuous, t_first_arrival_continuous, 'r-', linewidth=3, label='First Arrival (Refraction)')
|
| 41 |
|
|
|
|
| 42 |
if xc < x_max:
|
| 43 |
+
ax1.axvline(x=xc, color='k', linestyle=':', label=f'Crossover = {xc:.1f} m')
|
| 44 |
+
ax1.plot(0, ti, 'go', markersize=8, label=f'Refraction Intercept = {ti*1000:.1f} ms')
|
| 45 |
+
ax1.plot(0, t0, 'mo', markersize=8, label=f'Reflection TWT = {t0*1000:.1f} ms')
|
| 46 |
|
| 47 |
+
ax1.set_title("1. Travel-Time (T-X) Curve", fontsize=16, loc='left')
|
| 48 |
+
ax1.set_xlabel("Distance (m)")
|
| 49 |
+
ax1.set_ylabel("Travel Time (s)")
|
| 50 |
+
ax1.legend()
|
| 51 |
+
ax1.grid(True)
|
| 52 |
+
ax1.set_xlim(0, x_max)
|
| 53 |
+
y_max = max(np.max(t_direct), np.max(t_reflected))
|
| 54 |
+
ax1.set_ylim(0, y_max * 1.1)
|
| 55 |
+
|
| 56 |
+
# === PART 3: 繪製震測剖面概念圖 (Plot 2) ===
|
| 57 |
+
fig2, ax2 = plt.subplots(figsize=(10, 4))
|
| 58 |
+
|
| 59 |
+
# 產生離散的測站位置
|
| 60 |
+
receiver_x = np.linspace(0, x_max, num_receivers)
|
| 61 |
+
|
| 62 |
+
# 計算每個測站的抵達時間
|
| 63 |
+
t_direct_rx = receiver_x / v1
|
| 64 |
+
t_refracted_rx = (receiver_x / v2) + ti
|
| 65 |
+
t_reflected_rx = np.sqrt(t0**2 + (receiver_x / v1)**2)
|
| 66 |
+
t_first_arrival_rx = np.minimum(t_direct_rx, t_refracted_rx)
|
| 67 |
+
|
| 68 |
+
# 繪製每一條震波線和標記
|
| 69 |
+
for i in range(num_receivers):
|
| 70 |
+
# 畫代���震波線的灰色虛線
|
| 71 |
+
ax2.plot([receiver_x[i], receiver_x[i]], [0, y_max * 1.1], 'k--', alpha=0.2)
|
| 72 |
+
# 標示各種波的抵達時間
|
| 73 |
+
ax2.plot(receiver_x[i], t_direct_rx[i], 'bo', markersize=5, alpha=0.6) # 直達波
|
| 74 |
+
ax2.plot(receiver_x[i], t_reflected_rx[i], 'mo', markersize=5, alpha=0.6) # 反射波
|
| 75 |
+
ax2.plot(receiver_x[i], t_first_arrival_rx[i], 'ro', markersize=8) # 初達波 (最重要)
|
| 76 |
+
|
| 77 |
+
# 繪製地表
|
| 78 |
+
ax2.axhline(0, color='brown', linewidth=2)
|
| 79 |
+
# 繪製震源
|
| 80 |
+
ax2.plot(0, 0, 'r*', markersize=20, label='Source (震源)')
|
| 81 |
+
# 繪製測站
|
| 82 |
+
ax2.plot(receiver_x, np.zeros_like(receiver_x), 'kv', markersize=8, label='Receivers (測站)')
|
| 83 |
|
| 84 |
+
ax2.set_title(f"2. Seismic Profile Concept ({num_receivers} Traces)", fontsize=16, loc='left')
|
| 85 |
+
ax2.set_xlabel("Distance (m)")
|
| 86 |
+
ax2.set_ylabel("Travel Time (s)")
|
| 87 |
+
ax2.set_xlim(-x_max * 0.05, x_max * 1.05)
|
| 88 |
+
ax2.set_ylim(y_max * 1.1, -y_max*0.05) # 時間軸向下為正
|
| 89 |
+
ax2.legend(loc='lower left')
|
| 90 |
|
| 91 |
+
# 清理圖表外觀
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
plt.tight_layout()
|
| 93 |
+
fig1.tight_layout()
|
| 94 |
|
| 95 |
+
# === PART 4: 準備輸出的說明文字 ===
|
| 96 |
results_md = f"""
|
| 97 |
### 🔬 分析結果
|
| 98 |
|
| 99 |
根據您設計的地層模型,我們計算出以下關鍵物理量:
|
| 100 |
|
| 101 |
#### 折射波 (Refracted Wave)
|
| 102 |
+
- **臨界角 (Critical Angle, θc)**: `arcsin({v1:.0f} / {v2:.0f})` = **{theta_c_deg:.2f}°**
|
| 103 |
+
- **截時 (Intercept Time, tᵢ)**: `(2 * {h:.0f} * cos({theta_c_deg:.2f}°)) / {v1:.0f}` = **{ti*1000:.1f} ms**
|
| 104 |
+
- **交越距離 (Crossover Distance, Xc)**: `2 * {h:.0f} * sqrt(...)` = **{xc:.1f} m**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
---
|
| 107 |
#### 反射波 (Reflected Wave)
|
| 108 |
+
- **雙程走時 (Two-Way Time, t₀)**: `2 * {h:.0f} / {v1:.0f}` = **{t0*1000:.1f} ms**
|
|
|
|
|
|
|
|
|
|
| 109 |
"""
|
| 110 |
+
return fig1, fig2, results_md
|
| 111 |
+
|
| 112 |
|
| 113 |
# --- Gradio 介面設定 ---
|
| 114 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
| 117 |
# 地心震波奇幻之旅:地球物理遊樂場 🌍
|
| 118 |
> 創意的發揮是一種學習,過程中,每個人同時是學生也是老師。
|
| 119 |
|
| 120 |
+
這個實驗室就是你的遊樂場。透過親手設計地層模型、佈放虛擬測站,你將不只是學習,更是在**創造和發現**地底下的物理法則。
|
|
|
|
| 121 |
"""
|
| 122 |
)
|
| 123 |
|
| 124 |
with gr.Row():
|
| 125 |
with gr.Column(scale=1):
|
| 126 |
+
gr.Markdown("### ⚙️ 1. 設計你的地層模型")
|
| 127 |
v1_slider = gr.Slider(label="V1: 第一層速度 (m/s)", minimum=300, maximum=3000, value=800, step=50)
|
| 128 |
v2_slider = gr.Slider(label="V2: 第二層速度 (m/s)", minimum=500, maximum=6000, value=2500, step=50)
|
| 129 |
h_slider = gr.Slider(label="h: 第一層厚度 (m)", minimum=5, maximum=100, value=20, step=1)
|
| 130 |
+
|
| 131 |
+
gr.Markdown("### ⚙️ 2. 佈放你的觀測儀器")
|
| 132 |
xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=500, value=250, step=10)
|
| 133 |
+
receivers_slider = gr.Slider(label="測站數量 (Number of Receivers)", minimum=5, maximum=100, value=25, step=1) # 新增滑桿
|
| 134 |
|
| 135 |
submit_btn = gr.Button("🚀 開始探勘!", variant="primary")
|
| 136 |
|
| 137 |
with gr.Column(scale=2):
|
| 138 |
+
gr.Markdown("### 📊 觀測結果")
|
| 139 |
+
plot_output1 = gr.Plot(label="走時-距離圖 (T-X Plot)")
|
| 140 |
+
plot_output2 = gr.Plot(label="震測剖面概念圖 (Seismic Profile Concept)") # 新增第二個圖表區
|
| 141 |
|
| 142 |
with gr.Row():
|
| 143 |
results_output = gr.Markdown("### 🔬 分析結果\n請設計你的地層模型並點擊「開始探勘!」以顯示計算結果。")
|
|
|
|
| 145 |
# --- 事件監聽 ---
|
| 146 |
submit_btn.click(
|
| 147 |
fn=plot_seismic_exploration,
|
| 148 |
+
inputs=[v1_slider, v2_slider, h_slider, xmax_slider, receivers_slider],
|
| 149 |
+
outputs=[plot_output1, plot_output2, results_output] # 更新輸出目標
|
| 150 |
)
|
| 151 |
|
| 152 |
gr.Markdown(
|
| 153 |
"""
|
| 154 |
---
|
| 155 |
+
### 📖 剖面圖是如何誕生的?
|
| 156 |
+
上方的 **T-X 圖** 是一個抽象的數學關係圖,展現了震波抵達時間與距離的完美曲線。
|
| 157 |
+
下方的 **剖面概念圖** 則模擬了真實的探勘情境:我們在地表放置了許多**測站 (黑色三角形)**,每個測站都會記錄到屬於自己的**震波線 (灰色虛線)**。圖中的彩色圓點,就是該測站記錄到的「到時」。
|
| 158 |
+
|
| 159 |
+
當我們把成千上萬條震波線並排陳列,就形成了一幅壯觀的**震測剖面圖**,它就像是地球的超音波照片!
|
| 160 |
+
|
| 161 |
### 🚀 探索與發現 (Exploration & Discovery)
|
| 162 |
+
1. **數據密度**: 試著將「測站數量」從 5 慢慢增加到 100,觀察下方的剖面概念圖。當測站越密集時,你是否能越清晰地「看見」上方 T-X 圖中的那些曲線?
|
| 163 |
+
2. **剖面盲區**: 在近距離時,反射波(紫色點)和直達波(藍色點)幾乎混在一起。這在真實的資料處理中稱作「Muting Zone」。你能透過調整 V1 和 h,讓這兩種波的抵達時間分得更開嗎?
|
| 164 |
+
3. **交越點的意義**: 在剖面概念圖上,找到初達波標記(紅色點)從跟隨藍色點轉為跟隨(不存在的)綠色點趨勢的位置。這個位置對應到上方 T-X 圖的什麼特徵?
|
|
|
|
|
|
|
| 165 |
"""
|
| 166 |
)
|
| 167 |
|