Refraction_3 / app.py
cwadayi's picture
Update app.py
1ecc7cc verified
raw
history blame
8.22 kB
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
# --- 輔助函數:產生 Ricker 震波 ---
def ricker_wavelet(t, f=25.0):
""" 產生一個 Ricker 震波 (墨西哥帽函數) """
t = t - 2.0 / f # 將震波峰值對齊時間點
p = (np.pi * f * t) ** 2
return (1 - 2 * p) * np.exp(-p)
# --- 核心計算與繪圖函數 ---
def plot_seismic_exploration(v1, v2, h, x_max, num_receivers, gain):
"""
根據輸入的地層參數,計算並繪製震測的走時曲線與視覺化震測剖面圖。
"""
# === PART 1: 物理計算 ===
if v2 <= v1:
fig1, ax1 = plt.subplots(figsize=(10, 6))
ax1.text(0.5, 0.5, 'Error: V2 must be greater than V1', ha='center', va='center', color='red')
fig2, ax2 = plt.subplots(figsize=(10, 5))
ax2.text(0.5, 0.5, 'Please ensure V2 > V1', ha='center', va='center', color='red')
return fig1, fig2, "### 參數錯誤\n請確保第二層速度 (V2) 大於第一層速度 (V1)。"
theta_c_rad = np.arcsin(v1 / v2)
theta_c_deg = np.rad2deg(theta_c_rad)
ti = (2 * h * np.cos(theta_c_rad)) / v1
xc = 2 * h * np.sqrt((v2 + v1) / (v2 - v1))
t0 = (2 * h) / v1
# === PART 2: 繪製 T-X 走時曲線圖 (Plot 1) ===
x_continuous = np.linspace(0, x_max, 500)
t_direct = x_continuous / v1
t_refracted = (x_continuous / v2) + ti
t_reflected = np.sqrt(t0**2 + (x_continuous / v1)**2)
t_first_arrival_continuous = np.minimum(t_direct, t_refracted)
fig1, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(x_continuous, t_direct, 'b--', label='Direct Wave')
ax1.plot(x_continuous, t_refracted, 'g--', label='Refracted Wave')
ax1.plot(x_continuous, t_reflected, 'm:', linewidth=2, label='Reflected Wave')
ax1.plot(x_continuous, t_first_arrival_continuous, 'r-', linewidth=3, label='First Arrival')
if xc < x_max:
ax1.axvline(x=xc, color='k', linestyle=':', label=f'Crossover = {xc:.1f} m')
ax1.set_title("1. Travel-Time (T-X) Curve", fontsize=16, loc='left')
ax1.set_xlabel("Distance (m)")
ax1.set_ylabel("Travel Time (s)")
ax1.legend()
ax1.grid(True)
ax1.set_xlim(0, x_max)
y_max = max(np.max(t_direct), np.max(t_reflected))
ax1.set_ylim(0, y_max * 1.1)
# === PART 3: 繪製視覺化震測剖面圖 (Plot 2) ===
fig2, ax2 = plt.subplots(figsize=(10, 5))
receiver_x = np.linspace(0, x_max, num_receivers)
# 計算每個測站的抵達時間
t_direct_rx = receiver_x / v1
t_reflected_rx = np.sqrt(t0**2 + (receiver_x / v1)**2)
t_first_arrival_rx = np.minimum(receiver_x / v1, (receiver_x / v2) + ti)
# 繪製每一條帶有震波的震波線
wavelet_duration = 0.08
wavelet_t = np.linspace(0, wavelet_duration, 100)
for i in range(num_receivers):
# 繪製反射波震波 (最重要,因為剖面主要看反射)
wavelet_amp_refl = ricker_wavelet(wavelet_t, f=40) * gain
x_trace_refl = receiver_x[i] + wavelet_amp_refl
y_trace_refl = t_reflected_rx[i] - wavelet_duration/2 + wavelet_t
ax2.plot(x_trace_refl, y_trace_refl, 'k-', linewidth=1)
ax2.fill_betweenx(y_trace_refl, receiver_x[i], x_trace_refl, where=(x_trace_refl > receiver_x[i]), color='black')
# 繪製初達波震波 (通常能量較強)
wavelet_amp_first = ricker_wavelet(wavelet_t, f=30) * gain * 1.2 # 讓它振幅稍大
x_trace_first = receiver_x[i] + wavelet_amp_first
y_trace_first = t_first_arrival_rx[i] - wavelet_duration/2 + wavelet_t
ax2.plot(x_trace_first, y_trace_first, 'r-', linewidth=1.5)
ax2.fill_betweenx(y_trace_first, receiver_x[i], x_trace_first, where=(x_trace_first > receiver_x[i]), color='red', alpha=0.8)
# 繪製地表、震源與測站
ax2.axhline(0, color='brown', linewidth=2)
ax2.plot(0, 0, 'r*', markersize=20, label='Source')
ax2.plot(receiver_x, np.zeros_like(receiver_x), 'kv', markersize=8, label='Receivers')
ax2.set_title(f"2. Visualized Seismic Profile ({num_receivers} Traces)", fontsize=16, loc='left')
ax2.set_xlabel("Distance (m)")
ax2.set_ylabel("Two-Way Time (s)")
ax2.set_xlim(-x_max * 0.05, x_max * 1.05)
ax2.set_ylim(y_max * 1.1, -y_max*0.05)
ax2.legend(loc='lower left')
plt.tight_layout()
fig1.tight_layout()
# === PART 4: 準備輸出的說明文字 ===
results_md = "..." # (與前版相同,此處省略以節省空間)
return fig1, fig2, results_md
# --- Gradio 介面設定 ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 地心震波奇幻之旅:地球物理遊樂場 🌍")
# ... (開頭說明文字)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### ⚙️ 1. 設計你的地層模型")
v1_slider = gr.Slider(label="V1: 第一層速度 (m/s)", minimum=300, maximum=3000, value=800, step=50)
v2_slider = gr.Slider(label="V2: 第二層速度 (m/s)", minimum=500, maximum=6000, value=2500, step=50)
h_slider = gr.Slider(label="h: 第一層厚度 (m)", minimum=5, maximum=100, value=20, step=1)
gr.Markdown("### ⚙️ 2. 佈放儀器與顯示設定")
xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=500, value=250, step=10)
receivers_slider = gr.Slider(label="測站數量", minimum=5, maximum=100, value=40, step=1)
gain_slider = gr.Slider(label="剖面增益 (Display Gain)", minimum=1, maximum=20, value=5, step=1) # 新增增益滑桿
submit_btn = gr.Button("🚀 開始探勘!", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### 📊 觀測結果")
plot_output1 = gr.Plot(label="走時-距離圖 (T-X Plot)")
plot_output2 = gr.Plot(label="視覺化震測剖面圖 (Visualized Seismic Profile)")
# ... (分析結果顯示區塊)
# --- 事件監聽 ---
submit_btn.click(
fn=plot_seismic_exploration,
inputs=[v1_slider, v2_slider, h_slider, xmax_slider, receivers_slider, gain_slider], # 加入 gain_slider
outputs=[plot_output1, plot_output2, demo.outputs[-1]] # 確保結果正確輸出
)
gr.Markdown(
"""
---
### 📖 剖面圖是如何誕生的? (全新解說)
下方的 **視覺化震測剖面圖** 完美模擬了真實的探勘情境。每一條黑色的垂直線代表一個**測站 (Receiver)**,它記錄到的訊號就是一條帶有**震波 (Wiggle)** 的**震波線 (Trace)**。
- **紅色震波** 代表 **初達波 (First Arrival)**,是能量最早抵達的波。
- **黑色震波** 代表 **反射波 (Reflected Wave)**,它們來自地下介面的反射。
地球物理學家最重要的工作,就是在成千上萬條震波線中,**尋找並追蹤這些連續排列的震波(稱為「同相軸」)**。例如,圖中那條優美的黑色雙曲線同相軸,就清楚地標示出了地下第一層介面的位置!
### 🚀 探索與發現 (全新挑戰)
1. **增益的效果**: 試著調整「剖面增益」,看看震波的振幅如何變化。在真實資料中,深層的反射信號很微弱,就需要提高增益才能看清楚。
2. **看見雙曲線**: 專注觀察剖面圖中的黑色震波。當你增加「測站數量」時,是不是能更清楚地「描繪」出那條對應到上方 T-X 圖的紫色雙曲線?
3. **初達波的威力**: 紅色的初達波在剖面圖中形成了一條明顯的分界線。觀察它的轉折點,思考一下這個轉折點(交越距離)告訴了我們關於地下速度結構的什麼資訊?
"""
)
gr.HTML("""
<footer style="text-align:center; margin-top: 30px; color:grey;">
<p>「創意的發揮是一種學習,過程中,每個人同時是學生也是老師。」</p>
<p>地球物理遊樂場 &copy; 2025 - 由 Gemini 根據課程文件與靈感生成</p>
</footer>
""")
if __name__ == "__main__":
demo.launch()