Spaces:
Sleeping
Sleeping
File size: 9,371 Bytes
e971733 1ecc7cc e971733 1ecc7cc e971733 1ecc7cc e971733 1ecc7cc e971733 ea8c4dd a00010c 1ecc7cc a00010c 1ecc7cc e971733 9f4f09a ea8c4dd e971733 ea8c4dd 1ecc7cc e971733 ea8c4dd 1ecc7cc a00010c ea8c4dd 1ecc7cc ea8c4dd a00010c 1ecc7cc 210db34 1ecc7cc a00010c 1ecc7cc 210db34 1ecc7cc ea8c4dd 1ecc7cc e971733 210db34 ea8c4dd 1ecc7cc ea8c4dd 1ecc7cc 576e90d 210db34 e971733 ea8c4dd a00010c ea8c4dd 9f4f09a e971733 a00010c e971733 ea8c4dd e971733 ea8c4dd 1ecc7cc e971733 1ecc7cc 210db34 e971733 9f4f09a e971733 ea8c4dd 1ecc7cc e971733 a00010c e971733 9f4f09a a00010c e971733 a00010c 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd a00010c 1ecc7cc e971733 9f4f09a e971733 4a31c97 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
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')
ax1.set_title("Travel-Time Curve")
fig2, ax2 = plt.subplots(figsize=(10, 5))
ax2.text(0.5, 0.5, 'Please ensure V2 > V1 to generate a valid model.', ha='center', va='center', color='red')
ax2.set_title("Visualized Seismic Profile")
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, int(num_receivers))
# 計算每個測站的抵達時間
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(int(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=0.8)
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)
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 ({int(num_receivers)} Traces)", fontsize=14, 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)
# 【*** FIX 1: Move legend to a better position ***】
ax2.legend(loc='upper right', fontsize='small')
# 【*** FIX 2: Manually adjust layout to prevent overlaps ***】
fig1.tight_layout(pad=1.1)
fig2.subplots_adjust(left=0.1, right=0.98, top=0.9, bottom=0.15)
# === PART 4: 準備輸出的說明文字 ===
results_md = f"""
### 🔬 分析結果
根據您設計的地層模型,我們計算出以下關鍵物理量:
#### 折射波 (Refracted Wave)
- **臨界角 (Critical Angle, θc)**: `arcsin({v1:.0f} / {v2:.0f})` = **{theta_c_deg:.2f}°**
- **截時 (Intercept Time, tᵢ)**: `(2 * {h:.0f} * cos({theta_c_deg:.2f}°)) / {v1:.0f}` = **{ti*1000:.1f} ms**
- **交越距離 (Crossover Distance, Xc)**: `2 * {h:.0f} * sqrt(...)` = **{xc:.1f} m**
---
#### 反射波 (Reflected Wave)
- **雙程走時 (Two-Way Time, t₀)**: `2 * {h:.0f} / {v1:.0f}` = **{t0*1000:.1f} ms**
"""
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)
# 【*** FIX 3: Lower default gain for clarity ***】
gain_slider = gr.Slider(label="剖面增益 (Display Gain)", minimum=1, maximum=20, value=4, 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)")
with gr.Row():
results_output = gr.Markdown("### 🔬 分析結果\n請設計你的地層模型並點擊「開始探勘!」以顯示計算結果。")
# --- 事件監聽 ---
submit_btn.click(
fn=plot_seismic_exploration,
inputs=[v1_slider, v2_slider, h_slider, xmax_slider, receivers_slider, gain_slider],
outputs=[plot_output1, plot_output2, results_output]
)
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>地球物理遊樂場 © 2025 - 由 Gemini 根據課程文件與靈感生成</p>
</footer>
""")
if __name__ == "__main__":
demo.launch() |