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

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