Spaces:
Sleeping
Sleeping
| 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() |