Spaces:
Sleeping
Sleeping
File size: 8,216 Bytes
e971733 1ecc7cc e971733 1ecc7cc e971733 1ecc7cc e971733 1ecc7cc e971733 ea8c4dd 1ecc7cc e971733 9f4f09a ea8c4dd e971733 ea8c4dd 1ecc7cc e971733 ea8c4dd 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd 1ecc7cc e971733 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd 576e90d e971733 ea8c4dd e971733 ea8c4dd 1ecc7cc ea8c4dd 9f4f09a e971733 1ecc7cc e971733 ea8c4dd e971733 ea8c4dd 1ecc7cc e971733 1ecc7cc e971733 9f4f09a e971733 ea8c4dd 1ecc7cc e971733 1ecc7cc e971733 9f4f09a 1ecc7cc e971733 1ecc7cc ea8c4dd 1ecc7cc ea8c4dd 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 |
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>地球物理遊樂場 © 2025 - 由 Gemini 根據課程文件與靈感生成</p>
</footer>
""")
if __name__ == "__main__":
demo.launch() |