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>地球物理遊樂場 &copy; 2025 - 由 Gemini 根據課程文件與靈感生成</p>
        </footer>
    """)

if __name__ == "__main__":
    demo.launch()