QSEIS2025:从模型到位移波形#

此教程使用仓库提供的 AK135 弹性参数、一个震源深度、一个接收深度和三个距离。默认串行计算,不需要 MPI,也不依赖本机 test/ 目录。

1. 运行位移示例#

先完成安装,进入仓库根目录并激活环境:

python examples/qseis2025.py --regional --output-dir examples/output/qseis2025-regional

Windows 非交互调用可使用:

conda run -n pygrnwang python examples/qseis2025.py --regional --output-dir examples/output/qseis2025-regional

脚本自动写出模型、预处理输入、写入 64 秒震源样本、运行 QSEIS2025、转换结果,并用 output_type="disp" 读取合成位移。示例使用 10 km 深度震源、地表接收、300/600/900 km 距离、4 秒采样间隔(Nyquist 频率 0.125 Hz),并启用平地球变换。底层格林函数库计算 4092 秒、1024 个采样点;合成后保存发震后 0–1020 秒(包含两端,共 256 点)的数组和绘图。求解器运行需要几分钟。模型取前 24 个数值行,常数 Qp=600、Qs=300 是教程设置,并不代表完整 AK135-F 衰减模型。

2. 查看结果#

输出目录中包含 disp.npz、disp.png、source_time_function.json、summary.json、模型文件与 library/。NPZ 保存 0–1020 秒的波形、时间、距离、分量标签及单位,位移数组形状为 (3, 3, 256);摘要记录环境、运行时间、输出形状和文件大小。library/ 保留完整的 1024 点原生计算结果。

QSEIS2025 小算例在 300、600、900 公里处的三分量位移波形。

10 km 深度震源在 300、600、900 km 处的合成位移,发震后 0–1020 秒;精确参数以共用脚本为准。#

此示例的 rotate=True 对应 东、北、上 三分量。图中位移单位为米,震源矩为脚本中指定的 \(10^{15}\) N m。图的横轴使用示例设定的时间零点;改变约化时间或平移选项后,应重新核对横轴含义。

震源采用 wavelet_type=0 和 1024 个自定义矩率样本:64 秒归一化 sin² 脉冲,预先补偿 QSEIS 的数值阻尼,使实际 STF 面积为 1、质心为 32 秒,与 SPGRN2020 一致。格林函数库保存速率核,读取程序在 output_type="disp" 时积分一次。

3. 计算应变与应力#

在另一个目录中启用完整的教程输出:

python examples/qseis2025.py --regional --observables all --output-dir examples/output/qseis2025-regional-tensors

新增 strain 和 stress 的数组与图,脚本直接向读取程序请求位移、应变和应力,均保存 0–1020 秒;应变与应力数组形状为 (3, 6, 256)。启用地理旋转时,对称张量排列为 [EE, EN, EU, NN, NU, UU],U 表示向上,也对应代码中的 Z。应变无量纲,应力单位为 Pa。不同后端未旋转张量的排列并不统一,详见科学约定。

4. 复用已完成的计算#

python examples/qseis2025.py --regional --output-dir examples/output/qseis2025-regional --reuse

复用运行会另写 summary-reuse.json,保留首次计算的摘要。仅对同一模型、网格和输出设置复用。改变计算参数时使用新目录,以免将旧库误认为新参数的结果。

模型取前 24 个数值行形成分层半空间,与完整球形地球是不同近似,不能只凭相同距离判断结果应完全相等。参见远距离教程和后端对比。

更短的入门计算#

不加 --regional 时,同一脚本计算 30/60/90 km、0.5 秒采样的轻量算例:原生库为 127.5 秒、256 点,保存 0–100 秒(201 点),几秒内完成:

python examples/qseis2025.py --output-dir examples/output/qseis2025

完整脚本#

中英文页面直接引用同一脚本:

  1"""Build QSEIS2025 introductory or regional traces; optionally include tensors."""
  2from pathlib import Path
  3
  4from common import (MECHANISM, MOMENT_NM, REGIONAL_SAMPLING_INTERVAL_S, REGIONAL_STF, finish, parser_for, prepare,
  5                    require_library_settings, save_waveforms)
  6from pygrnwang.create_qseis2025_bulk import (
  7    pre_process_qseis2025, create_grnlib_qseis2025_sequential)
  8from pygrnwang.read_qseis2025 import get_outfile_name_list, seek_qseis2025
  9from source_time_function import prepare_qseis_stf, validate_qseis_stf
 10from spectral_settings import qseis_spectral_settings
 11
 12
 13def main():
 14    parser = parser_for("qseis2025")
 15    parser.add_argument("--regional", action="store_true",
 16                        help="Use 300/600/900 km, a 64 s wavelet and Earth flattening")
 17    parser.add_argument("--point-source", action="store_true",
 18                        help="Regional control run: disable the default Gaussian spatial source smoothing")
 19    parser.set_defaults(output_dir=None)
 20    parser.add_argument("--observables", choices=("disp", "all"), default="disp",
 21                        help="all also computes strain and stress")
 22    args = parser.parse_args()
 23    if args.point_source and not args.regional:
 24        parser.error("--point-source requires --regional")
 25    source_radius_ratio = 0.0 if args.point_source else 0.05
 26    if args.output_dir is None:
 27        directory = "qseis2025-regional" if args.regional else "qseis2025"
 28        if args.point_source:
 29            directory += "-point-source"
 30        args.output_dir = Path(__file__).resolve().parent / "output" / directory
 31    output, library, model, report, started = prepare(args, "QSEIS2025")
 32    dt, window = (REGIONAL_SAMPLING_INTERVAL_S, 4092.0) if args.regional else (0.5, 127.5)
 33    output_end = 1020.0 if args.regional else 100.0
 34    output_samples = int(round(output_end / dt)) + 1  # Include the final sample.
 35    native_samples = int(round(window / dt)) + 1
 36    distances = [300.0, 600.0, 900.0] if args.regional else [30.0, 60.0, 90.0]
 37    wavelet_duration = 16 if args.regional else 4
 38    wavelet_type = 0 if args.regional else 2
 39    source_time_function = None
 40    if not args.reuse:
 41        pre_process_qseis2025(
 42            processes_num=1, path_green=library, event_depth_list=[10.0],
 43            receiver_depth_list=[0.0], dist_range=[distances[0], distances[-1]],
 44            delta_dist=distances[0],
 45            N_each_group=3, time_window=window, sampling_interval=dt, source_radius_ratio=source_radius_ratio,
 46            output_observables=([1, 0, 1, 1, 0] if args.observables == "all"
 47                                else [1, 0, 0, 0, 0]),
 48            wavelet_type=wavelet_type, wavelet_duration=wavelet_duration, time_reduction_velo=0,
 49            flat_earth_transform=args.regional, path_nd=model, earth_model_layer_num=24,
 50        )
 51        if args.regional:
 52            source_time_function = prepare_qseis_stf(
 53                library, duration_s=64.0, samples=1024)
 54        create_grnlib_qseis2025_sequential(library, remove_pd=False)
 55    require_library_settings(
 56        library, event_depth_list=[10.0], receiver_depth_list=[0.0],
 57        grn_dist_range=[distances[0], distances[-1]], grn_delta_dist=distances[0],
 58        sampling_interval=dt, time_window=window, sampling_num=native_samples,
 59        wavelet_type=wavelet_type, wavelet_duration=wavelet_duration, time_reduction_velo=0,
 60        flat_earth_transform=args.regional, earth_model_layer_num=24,
 61        slowness_window=None, wavenumber_sampling_rate=12, anti_alias=0.01,
 62        free_surface=0,
 63    )
 64    native_input = Path(library) / "10.00" / "0.00" / "0_0" / "grn.inp"
 65    lines = native_input.read_text(encoding="utf-8-sig").splitlines()
 66    headings = [i for i, line in enumerate(lines) if "WAVENUMBER INTEGRATION PARAMETERS" in line]
 67    if len(headings) != 1:
 68        raise ValueError("Expected one native wavenumber section")
 69    data = [line.split("#", 1)[0].strip() for line in lines[headings[0] + 1:]]
 70    data = [line for line in data if line]
 71    controls = [float(value) for value in data[1].split()]
 72    if controls != [1e-6, source_radius_ratio]:
 73        raise ValueError("Existing native spatial-source settings differ; use a fresh --output-dir")
 74    if args.regional and args.reuse:
 75        source_time_function = validate_qseis_stf(library)
 76    observables = ("disp", "strain", "stress") if args.observables == "all" else ("disp",)
 77    if args.reuse:
 78        # Backend metadata does not store observable flags. Check the requested
 79        # binary outputs before reading a displacement-only library as tensors.
 80        native_dir = Path(library) / "10.00" / "0.00" / "0_0"
 81        required = []
 82        for observable in observables:
 83            psv, sh = get_outfile_name_list(observable)
 84            required.extend(native_dir / ("grn_%s.bin" % name) for name in psv + sh)
 85        if any(not path.is_file() for path in required):
 86            raise ValueError("Requested outputs are absent. Recalculate in a fresh "
 87                             "--output-dir without --reuse using --observables all.")
 88    for observable in observables:
 89        # Regional type-0 kernels are rates; the reader integrates them before cropping.
 90        arrays = [MOMENT_NM * seek_qseis2025(
 91            path_green=library, event_depth_km=10.0, receiver_depth_km=0.0,
 92            az_deg=30.0, dist_km=distance, focal_mechanism=MECHANISM,
 93            srate=1 / dt, output_type=observable, rotate=True,
 94            before_p=None, shift=False, pad_zeros=False,
 95        )[:, :output_samples] for distance in distances]
 96        labels = ["E", "N", "U"] if observable == "disp" else ["EE", "EN", "EU", "NN", "NU", "UU"]
 97        unit = {"disp": "m", "strain": "1", "stress": "Pa"}[observable]
 98        save_waveforms(output, report, observable, arrays, distances, dt, labels, unit,
 99                       expected_samples=output_samples, time_limits=(0.0, output_end))
100    report.update(sampling_interval_s=dt, max_frequency_hz=0.5 / dt, time_window_s=window,
101                  output_time_range_s=[0.0, output_end], distances_km=distances,
102                  native_samples=native_samples, earth_model_numeric_rows=24,
103                  wavelet_type=wavelet_type, wavelet_duration_samples=wavelet_duration,
104                  wavelet_duration_s=wavelet_duration * dt,
105                  flat_earth_transform=args.regional, regional=args.regional, source_radius_ratio=source_radius_ratio, point_source=args.point_source)
106    if args.regional:
107        report["source_time_function"] = source_time_function
108        report["physical_source_time_function"] = dict(REGIONAL_STF)
109        report["spectral_settings"] = qseis_spectral_settings(library)
110    finish(output, report, started)
111
112
113if __name__ == "__main__":
114    main()

下一步可阅读 QSEIS2025 后端指南、读取与后处理及验证记录。小算例用于学习完整流程;正式研究还需检查空间采样、频率范围和数值收敛。