File size: 9,887 Bytes
e971733
 
 
 
1ecc7cc
 
28681db
 
1ecc7cc
 
 
e971733
28681db
 
 
 
 
c4e778c
 
 
 
 
28681db
c4e778c
 
 
 
 
 
 
 
 
 
28681db
c4e778c
 
 
 
 
28681db
 
 
c4e778c
 
 
 
28681db
c4e778c
 
ea8c4dd
28681db
 
c4e778c
28681db
e971733
ea8c4dd
c4e778c
 
 
28681db
c4e778c
 
 
 
 
 
28681db
c4e778c
 
 
 
28681db
ea8c4dd
 
c4e778c
 
28681db
ea8c4dd
c4e778c
1ecc7cc
a00010c
c4e778c
 
28681db
1ecc7cc
c4e778c
1ecc7cc
ea8c4dd
a00010c
c4e778c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea8c4dd
c4e778c
576e90d
c4e778c
 
 
28681db
c4e778c
 
 
 
 
210db34
28681db
c4e778c
 
a00010c
c4e778c
 
 
 
 
 
 
a00010c
c4e778c
 
 
ea8c4dd
e971733
c4e778c
 
e971733
 
 
c4e778c
 
 
 
 
 
 
 
 
ea8c4dd
c4e778c
 
 
 
e971733
c4e778c
e971733
 
c4e778c
 
 
 
 
e971733
a00010c
c4e778c
e971733
 
c4e778c
 
 
 
 
 
 
28681db
e971733
9f4f09a
28681db
c4e778c
e971733
 
 
 
 
c4e778c
 
 
 
e971733
 
9f4f09a
 
 
 
c4e778c
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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)

# --- 核心計算與繪圖函數 ---
# 【*** FIX 1: Add scenario_name_val as a function parameter ***】
def plot_seismic_exploration(scenario_name_val, v1, v2, v3, h1, h2, x_max, num_receivers, gain):
    """
    根據輸入的地層參數,計算並繪製所有探勘圖表。
    """
    # === PART 1: 物理計算 (升級至三層模型) ===
    valid_model = True
    error_msg = ""
    if v2 <= v1 or v3 <= v2:
        valid_model = False
        error_msg = "### ⚠️ 模型警告\n速度必須隨深度增加 (V3 > V2 > V1),折射波分析可能無效。"

    # 計算關鍵物理量 (第一層介面)
    t0_1 = (2 * h1) / v1
    # 計算關鍵物理量 (第二層介面)
    t0_2 = (2 * h1 / v1) + (2 * h2 / v2)
    
    # === PART 2: 繪製地質模型圖 (新圖表) ===
    fig0, ax0 = plt.subplots(figsize=(10, 2))
    ax0.set_xlim(0, x_max)
    ax0.set_ylim(-(h1 + h2) * 1.5, 5)
    ax0.axhline(0, color='saddlebrown', linewidth=3)
    ax0.axhline(-h1, color='gray', linestyle='--')
    ax0.axhline(-(h1+h2), color='darkgray', linestyle='--')
    ax0.fill_between([0, x_max], 0, -h1, color='sandybrown', alpha=0.6)
    ax0.fill_between([0, x_max], -h1, -(h1+h2), color='darkkhaki', alpha=0.6)
    ax0.fill_between([0, x_max], -(h1+h2), -(h1 + h2) * 1.5, color='dimgray', alpha=0.6)
    ax0.text(x_max/2, -h1/2, f'Layer 1\nV1 = {v1:.0f} m/s\nh1 = {h1:.0f} m', ha='center', va='center', fontsize=9, color='black')
    ax0.text(x_max/2, -h1-h2/2, f'Layer 2\nV2 = {v2:.0f} m/s\nh2 = {h2:.0f} m', ha='center', va='center', fontsize=9, color='black')
    ax0.text(x_max/2, -(h1+h2)*1.25, f'Layer 3 (Basement)\nV3 = {v3:.0f} m/s', ha='center', va='center', fontsize=9, color='white')
    ax0.set_title("Geological Model Cross-section")
    ax0.set_ylabel("Depth (m)")
    ax0.set_yticks([0, -h1, -(h1+h2)])
    ax0.set_xticks([])
    fig0.tight_layout(pad=1.1)

    # === PART 3: 繪製 T-X 走時曲線圖 ===
    x_continuous = np.linspace(0, x_max, 500)
    # 反射波 (使用RMS速度近似)
    v_rms_2 = np.sqrt((v1**2 * 2*h1/v1 + v2**2 * 2*h2/v2) / (2*h1/v1 + 2*h2/v2))
    t_refl_1 = np.sqrt(t0_1**2 + (x_continuous / v1)**2)
    t_refl_2 = np.sqrt(t0_2**2 + (x_continuous / v_rms_2)**2)
    
    fig1, ax1 = plt.subplots(figsize=(10, 6))
    ax1.plot(x_continuous, t_refl_1, 'm:', linewidth=2, label='Reflection 1 (from Layer 2)')
    ax1.plot(x_continuous, t_refl_2, 'c:', linewidth=2, label='Reflection 2 (from Layer 3)')
    
    # 折射波
    if valid_model:
        theta_c12 = np.arcsin(v1 / v2)
        ti_12 = (2 * h1 * np.cos(theta_c12)) / v1
        t_refr_12 = (x_continuous / v2) + ti_12
        ax1.plot(x_continuous, t_refr_12, 'g--', label='Refraction (from Layer 2)')
        
        ti_23 = 2 * h1 * np.sqrt(v3**2 - v1**2)/(v1*v3) + 2 * h2 * np.sqrt(v3**2 - v2**2)/(v2*v3)
        t_refr_23 = (x_continuous / v3) + ti_23
        ax1.plot(x_continuous, t_refr_23, 'y--', label='Refraction (from Layer 3)')

    ax1.set_title("1. Travel-Time (T-X) Curve")
    ax1.legend(fontsize='small')
    ax1.grid(True)
    ax1.set_xlim(0, x_max)
    y_max = np.max(t_refl_2) * 1.1
    ax1.set_ylim(0, y_max)
    fig1.tight_layout(pad=1.1)
    
    # === PART 4: 繪製視覺化震測剖面圖 ===
    fig2, ax2 = plt.subplots(figsize=(10, 5))
    receiver_x = np.linspace(0, x_max, int(num_receivers))
    # 反射波到時
    t_refl_1_rx = np.sqrt(t0_1**2 + (receiver_x / v1)**2)
    t_refl_2_rx = np.sqrt(t0_2**2 + (receiver_x / v_rms_2)**2)

    wavelet_duration = y_max / 10
    wavelet_t = np.linspace(0, wavelet_duration, 100)
    
    for i in range(int(num_receivers)):
        # 繪製第一層反射
        wavelet_amp_1 = ricker_wavelet(wavelet_t, f=40) * gain
        x_trace_1 = receiver_x[i] + wavelet_amp_1
        y_trace_1 = t_refl_1_rx[i] - wavelet_duration/2 + wavelet_t
        ax2.plot(x_trace_1, y_trace_1, 'k-', linewidth=0.8)
        ax2.fill_betweenx(y_trace_1, receiver_x[i], x_trace_1, where=(x_trace_1 > receiver_x[i]), color='black')

        # 繪製第二層反射
        wavelet_amp_2 = ricker_wavelet(wavelet_t, f=30) * gain * 0.8 # Deeper reflections are weaker
        x_trace_2 = receiver_x[i] + wavelet_amp_2
        y_trace_2 = t_refl_2_rx[i] - wavelet_duration/2 + wavelet_t
        ax2.plot(x_trace_2, y_trace_2, 'b-', linewidth=0.8)
        ax2.fill_betweenx(y_trace_2, receiver_x[i], x_trace_2, where=(x_trace_2 > receiver_x[i]), color='blue')
    
    ax2.set_title(f"2. Visualized Seismic Profile ({int(num_receivers)} Traces)")
    ax2.set_ylim(y_max, -y_max*0.05)
    ax2.set_xlim(-x_max * 0.05, x_max * 1.05)
    fig2.subplots_adjust(left=0.1, right=0.98, top=0.9, bottom=0.15)
    
    # === PART 5: 準備探勘日誌 ===
    log_md = f"""
    ### 📝 現場探勘日誌
    **任務目標**: {scenario_name_val}
    **儀器設定**: {int(num_receivers)} 個測站, 測線長度 {x_max} 公尺。

    **初步分析**:
    - **第一介面反射 (黑色震波)**: 雙程走時 (TWT) 約 **{t0_1*1000:.1f} ms**。
    - **第二介面反射 (藍色震波)**: 雙程走時 (TWT) 約 **{t0_2*1000:.1f} ms**。
    
    {error_msg}
    """
    return fig0, fig1, fig2, log_md

# --- Gradio 介面與任務設定 ---
scenarios = {
    "自訂模式 (Custom Mode)": {"v1": 800, "v2": 2500, "v3": 4500, "h1": 20, "h2": 50},
    "尋找淺層地下水 (Find Groundwater)": {"v1": 500, "v2": 2200, "v3": 3500, "h1": 15, "h2": 40},
    "桃園台地工程鑽探 (Taoyuan Engineering)": {"v1": 600, "v2": 1800, "v3": 3000, "h1": 10, "h2": 30},
    "油氣田探勘 (Oil & Gas Prospecting)": {"v1": 1500, "v2": 2800, "v3": 4200, "h1": 100, "h2": 250},
}

def update_sliders(scenario_key):
    params = scenarios[scenario_key]
    return params['v1'], params['v2'], params['v3'], params['h1'], params['h2']

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    scenario_name = gr.State("自訂模式 (Custom Mode)")
    gr.Markdown("# 地球物理探勘總部 🛰️")
    
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("### 🎯 1. 選擇探勘任務")
            scenario_dropdown = gr.Dropdown(list(scenarios.keys()), label="Select Mission", value="自訂模式 (Custom Mode)")
            
            gr.Markdown("### ⚙️ 2. 微調地層參數")
            v1_slider = gr.Slider(label="V1 (m/s)", minimum=300, maximum=5000, value=800, step=50)
            h1_slider = gr.Slider(label="h1 (m)", minimum=5, maximum=500, value=20, step=5)
            v2_slider = gr.Slider(label="V2 (m/s)", minimum=500, maximum=6000, value=2500, step=50)
            h2_slider = gr.Slider(label="h2 (m)", minimum=10, maximum=1000, value=50, step=10)
            v3_slider = gr.Slider(label="V3 (m/s)", minimum=1000, maximum=8000, value=4500, step=50)
            
            gr.Markdown("### 📡 3. 設定儀器")
            xmax_slider = gr.Slider(label="最大觀測距離 (m)", minimum=100, maximum=2000, value=500, step=50)
            receivers_slider = gr.Slider(label="測站數量", minimum=10, maximum=200, value=50, step=5)
            gain_slider = gr.Slider(label="剖面增益", minimum=1, maximum=20, value=4, step=1)
            
            submit_btn = gr.Button("🚀 發射震波!", variant="primary")
            
        with gr.Column(scale=2):
            gr.Markdown("### 🗺️ 地質模型")
            plot_output0 = gr.Plot(label="Geological Model")
            gr.Markdown("### 📊 探勘數據")
            plot_output1 = gr.Plot(label="走時-距離圖")
            plot_output2 = gr.Plot(label="視覺化震測剖面圖")
            
    with gr.Row():
        log_output = gr.Markdown("### 📝 現場探勘日誌\n請選擇任務或調整參數,然後點擊「發射震波!」")

    # --- 事件監聽 ---
    scenario_dropdown.change(
        fn=update_sliders,
        inputs=scenario_dropdown,
        outputs=[v1_slider, v2_slider, v3_slider, h1_slider, h2_slider]
    )
    scenario_dropdown.change(lambda x: x, inputs=scenario_dropdown, outputs=scenario_name)
    
    # 【*** FIX 2: Add scenario_name to the inputs list ***】
    submit_btn.click(
        fn=plot_seismic_exploration,
        inputs=[scenario_name, v1_slider, v2_slider, v3_slider, h1_slider, h2_slider, xmax_slider, receivers_slider, gain_slider],
        outputs=[plot_output0, plot_output1, plot_output2, log_output] 
    )
    
    gr.Markdown(
        """
        ---
        ### 🧠 總工程師的挑戰
        1.  **看見儲油構造**: 在「油氣田探勘」任務中,來自第二介面(藍色震波)的反射同相軸呈現一個向上彎曲的「背斜」形狀,這正是油氣最喜歡聚集的地方!你能透過微調 `h1` 和 `h2` 讓這個構造更明顯嗎?
        2.  **折射的極限**: 試著在自訂模式中,將 `V2` 調得比 `V1` 慢,看看走時圖和日誌會出現什麼警告?這在真實地質中稱為「低速帶」,是折射法的一大挑戰。
        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()