Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| # --- 核心計算與繪圖函數 (已加入臨界距離) --- | |
| def plot_travel_times(v1, v2, h, x_max): | |
| """ | |
| 根據輸入的地層參數,計算並繪製直達波、反射波與折射波的走時曲線。 | |
| 圖表內的標籤為英文。 | |
| """ | |
| # 物理條件檢查:折射必須 V2 > V1 | |
| if v2 <= v1: | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| # 即使沒有折射,我們仍然可以顯示直達波和反射波 | |
| x = np.linspace(0, x_max, 500) | |
| t_direct = x / v1 | |
| t_reflected = np.sqrt(x**2 + (2 * h)**2) / v1 | |
| ax.plot(x, t_direct, 'b--', label='Direct Wave') | |
| ax.plot(x, t_reflected, 'm-.', label='Reflected Wave') | |
| ax.text(0.5, 0.5, 'V2 <= V1: Critical refraction does not occur.\nOnly Direct and Reflected waves are shown.', | |
| ha='center', va='center', fontsize=12, color='orange') | |
| ax.set_xlabel("Distance (m)") | |
| ax.set_ylabel("Travel Time (s)") | |
| ax.grid(True) | |
| ax.legend() | |
| ax.set_title("Travel-Time Curve (No Refraction)") | |
| results_md = "### 參數錯誤\n請確保第二層速度 (V2) 大於第一層速度 (V1) 才能產生臨界折射現象。" | |
| return fig, results_md | |
| # 1. 計算關鍵物理量 | |
| 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)) | |
| xcrit = 2 * h * np.tan(theta_c_rad) # <<< 新增:臨界距離計算 | |
| # 2. 準備繪圖數據 | |
| x = np.linspace(0, x_max, 500) | |
| t_direct = x / v1 | |
| t_refracted = (x / v2) + ti | |
| t_reflected = np.sqrt(x**2 + (2 * h)**2) / v1 | |
| t_first_arrival = np.minimum(t_direct, t_refracted) | |
| # 3. 使用 Matplotlib 繪圖 | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| ax.plot(x, t_direct, 'b--', label=f'Direct Wave (Slope 1/{v1:.0f})') | |
| ax.plot(x, t_refracted, 'g--', label=f'Refracted Wave (Slope 1/{v2:.0f})') | |
| ax.plot(x, t_reflected, 'm-.', label='Reflected Wave') | |
| ax.plot(x, t_first_arrival, 'r-', linewidth=3, label='First Arrival') | |
| # 標示交越距離 | |
| if xc < x_max: | |
| ax.axvline(x=xc, color='k', linestyle=':', label=f'Crossover Distance = {xc:.1f} m') | |
| ax.plot(xc, xc/v1, 'ko') | |
| # <<< 新增:標示臨界距離 >>> | |
| if xcrit < x_max: | |
| tcrit = (xcrit / v2) + ti # 計算臨界點的時間 | |
| ax.axvline(x=xcrit, color='c', linestyle=':', label=f'Critical Distance = {xcrit:.1f} m') | |
| ax.plot(xcrit, tcrit, 'co', markersize=8) # 在臨界點加上青色圓點 | |
| ax.plot(0, ti, 'mo', markersize=8, label=f'Intercept Time = {ti*1000:.1f} ms') | |
| ax.set_title("Interactive Seismic T-X Plot (Reflection + Refraction)", fontsize=16) | |
| ax.set_xlabel("Distance (m)", fontsize=12) | |
| ax.set_ylabel("Travel Time (s)", fontsize=12) | |
| ax.legend() | |
| ax.grid(True) | |
| ax.set_xlim(0, x_max) | |
| max_time = np.max([t_direct[-1], t_refracted[-1], t_reflected[-1]]) | |
| ax.set_ylim(0, max_time * 1.1) | |
| plt.tight_layout() | |
| # 4. 準備輸出的說明文字 | |
| results_md = f""" | |
| ### 🔬 分析結果 | |
| 根據您設定的參數,我們計算出以下關鍵物理量: | |
| - **直達波 (Direct Wave)**: | |
| - 公式: `t = x / V1` | |
| - *震波直接沿著地表傳遞,走時圖為一條過原點的直線。* | |
| - **反射波 (Reflected Wave)**: | |
| - 公式: `t = sqrt(x² + (2h)²) / V1` | |
| - *震波向下傳至界面後反射回地表。走時圖為一條雙曲線。* | |
| - **折射波 (Refracted Wave)**: | |
| - **臨界角 (Critical Angle, θc)**: | |
| - 公式: `θc = arcsin(V1 / V2)` | |
| - 計算: `arcsin({v1:.0f} / {v2:.0f})` = **{theta_c_deg:.2f}°** | |
| - **臨界距離 (Critical Distance, Xcrit)**: | |
| - 公式: `Xcrit = 2 * h * tan(θc)` | |
| - 計算: `2 * {h:.0f} * tan({theta_c_deg:.2f}°) ` = **{xcrit:.1f} m** | |
| - *這是能夠在地表接收到第一道臨界折射波的「最短水平距離」。* | |
| - **截時 (Intercept Time, tᵢ)**: | |
| - 公式: `tᵢ = (2 * h * cos(θc)) / V1` | |
| - 計算: `(2 * {h:.0f} * cos({theta_c_deg:.2f}°)) / {v1:.0f}` = **{ti*1000:.1f} ms** | |
| - *折射波走時線在時間軸上的截距,隱含了第一層的厚度資訊。* | |
| - **交越距離 (Crossover Distance, Xc)**: | |
| - 公式: `Xc = 2 * h * sqrt((V2 + V1) / (V2 - V1))` | |
| - 計算: `2 * {h:.0f} * sqrt(({v2:.0f} + {v1:.0f}) / ({v2:.0f} - {v1:.0f}))` = **{xc:.1f} m** | |
| - *在此距離上,直達波和折射波同時抵達。超過此距離後,折射波會先到。* | |
| """ | |
| return fig, results_md | |
| # --- Gradio 介面設定 (已更新標題與說明) --- | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 地心震波奇幻之旅:互動震測走時曲線實驗室 🌍") | |
| gr.Markdown( | |
| """ | |
| 歡迎來到地球物理小教室!這個互動工具可以模擬一個簡單的雙層地層模型中的震波傳遞。 | |
| 您可以透過下方的滑桿來調整第一層速度 `V1`、第二層速度 `V2` 以及第一層的厚度 `h`。 | |
| 觀察右側的「走時-距離圖」如何即時變化,並從中學習**直達波 (Direct)**、**反射波 (Reflected)** 與**折射波 (Refracted)** 的核心原理! | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### ⚙️ 調整地層參數") | |
| 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) | |
| xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=500, value=250, step=10) | |
| submit_btn = gr.Button("產生圖表", variant="primary") | |
| with gr.Column(scale=2): | |
| gr.Markdown("### 📊 走時-距離圖 (T-X Plot)") | |
| plot_output = gr.Plot() | |
| with gr.Row(): | |
| results_output = gr.Markdown("### 🔬 分析結果\n請調整參數並點擊按鈕以顯示計算結果。") | |
| # --- 事件監聽 --- | |
| submit_btn.click( | |
| fn=plot_travel_times, | |
| inputs=[v1_slider, v2_slider, h_slider, xmax_slider], | |
| outputs=[plot_output, results_output] | |
| ) | |
| gr.Markdown( | |
| """ | |
| --- | |
| ### 學習重點 | |
| 1. **觀察斜率**: 直達波的斜率是 `1/V1`,折射波的斜率是 `1/V2`。 | |
| 2. **觀察截時 (Intercept Time)**: 增加厚度 `h`,觀察折射波在 Y 軸上的截距如何變大。 | |
| 3. **觀察交越距離 (Crossover Distance)**: 增加 `h` 或減小 `V2` 與 `V1` 的速度差,觀察交叉點如何向右移動。 | |
| 4. **觀察反射波**: 注意反射波(洋紅色點虛線)是一條雙曲線,其曲率與 `h` 和 `V1` 有關。 | |
| 5. **尋找臨界點**: 觀察**臨界距離**(青色虛線)的位置。這是折射波物理上「誕生」的地方。注意看,它正好落在反射波的曲線上! | |
| 6. **必要條件**: 將 `V2` 調整到比 `V1` 小,臨界折射現象會消失,只剩下直達波與反射波! | |
| <footer> | |
| <p style="text-align:center; color:grey;">地球物理小教室 © 2025 - 由 Gemini 根據課程文件生成</p> | |
| </footer> | |
| """ | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |