Quickstart: static Coulomb stress#
This calculation uses one 1 km × 1 km source patch with 1 m slip and receivers at 30, 60 and 90 km epicentral distance. The patch is 10 km deep; receivers are 5 km deep. Both mechanisms are strike 30°, dip 45°, rake 90°. Friction is 0.4 and B_pore=0.
The script copies examples/wenchuan/input/model.nd and uses its first 24 numeric rows for the static backend. This illustrative geometry is not a Wenchuan reproduction. The coarse library checks the workflow; it is not a convergence study.
1. Run the calculation#
Follow installation, then run from the DynCFS repository root:
python docs/examples/quickstart.py
On Windows with Conda:
conda run -n cfs python docs/examples/quickstart.py
Files are written below docs/_build/quickstart/. The script requires a new or empty output directory. To repeat:
python docs/examples/quickstart.py --output-dir docs/_build/quickstart-repeat
2. Inspect the inputs and library#
The script writes source/receiver CSV files and an INI with absolute paths, then calls create_static_lib and compute_static_cfs.
The library has source depths 10 and 11 km, receiver depth 5 km, and distances 1–121 km at 10 km spacing. EDGRN needs at least two source depths. Queries stay inside the library range.
To prepare inputs and run the equivalent CLI steps:
python docs/examples/quickstart.py --prepare-only --output-dir docs/_build/prepared
python -m dyncfs.main --config docs/_build/prepared/quickstart.ini --create-static-lib --compute-static-cfs
The CLI writes numerical results; plotting and the summary are steps in the complete Python script.
3. Read the results#
File below the output directory |
Meaning |
|---|---|
|
Configuration with resolved paths |
|
Source, receivers and copied Earth model |
|
Static library and metadata |
|
Shape |
|
Three rows, one column, Pa |
|
Normal, shear and Coulomb stress in kPa |
|
Environment, timing, shapes and checks |
Local quickstart output. CSV values remain in pascals; this figure displays kilopascals.#
The script checks dimensions, finite nonzero values and
CFS = shear_stress + 0.4 * normal_stress.
See the validation record for what was run.
from pathlib import Path
import numpy as np
result = Path("docs/_build/quickstart/results/static")
stress = np.load(result / "stress_tensor_plane1.npy")
cfs = np.loadtxt(result / "cfs_static_plane1.csv", delimiter=",", ndmin=1)
print(stress.shape) # (3, 6)
print(cfs / 1e6) # MPa
4. Continue to dynamic stress#
Follow the dynamic workflow and provide a physically appropriate STF. Dynamic synthesis scales the STF to moment; static synthesis uses area and slip. Verify their consistency before comparing them.
The introductory INI includes time fields required by the parser, but its dynamic settings are not a validated dynamic tutorial.
Complete script and configuration#
1"""Small static DynCFS calculation; run from a source checkout."""
2import argparse
3import configparser
4import json
5import os
6from pathlib import Path
7import platform
8import shutil
9import sys
10import time
11
12ROOT = Path(__file__).resolve().parents[2]
13sys.path.insert(0, str(ROOT))
14os.environ.setdefault("MPLBACKEND", "Agg")
15
16import matplotlib.pyplot as plt
17import numpy as np
18import pygrnwang
19
20from dyncfs import __version__
21from dyncfs.configuration import CfsConfig
22from dyncfs.cfs_static import create_static_lib, compute_static_cfs
23from pygrnwang.geo import d2km
24
25
26def main():
27 parser = argparse.ArgumentParser(description=__doc__)
28 parser.add_argument("--output-dir", type=Path, default=ROOT / "docs/_build/quickstart")
29 parser.add_argument("--prepare-only", action="store_true", help="Write inputs without running solvers")
30 args = parser.parse_args()
31 output = args.output_dir.resolve()
32 if output.exists() and any(output.iterdir()):
33 parser.error("Use a new or empty output directory to keep calculations separate.")
34 started = time.perf_counter()
35 input_dir = output / "input"
36 input_dir.mkdir(parents=True, exist_ok=True)
37 shutil.copyfile(ROOT / "examples/wenchuan/input/model.nd", input_dir / "model.nd")
38 distances = np.array([30.0, 60.0, 90.0])
39 # Patch center; static synthesis uses area and slip. STF is included for the file schema.
40 source = [[0, 0, 10, 30, 45, 90, 1, 1, 1, 3.112616e16, 0, 0.5, 1, 0.5, 0]]
41 receivers = np.array([[0, d / d2km, 5, 30, 45, 90] for d in distances])
42 np.savetxt(input_dir / "source_plane1.csv", source, delimiter=",")
43 np.savetxt(input_dir / "obs_plane1.csv", receivers, delimiter=",")
44 ini = configparser.ConfigParser()
45 ini.read(ROOT / "docs/examples/quickstart.ini", encoding="utf-8")
46 ini["path"]["path_input"] = input_dir.as_posix()
47 ini["path"]["path_output"] = output.as_posix()
48 config_path = output / "quickstart.ini"
49 with config_path.open("w", encoding="utf-8") as stream:
50 ini.write(stream)
51 print(f"Configuration: {config_path}")
52 if args.prepare_only:
53 return
54 config = CfsConfig()
55 config.read_config(str(config_path))
56 create_static_lib(config)
57 compute_static_cfs(config)
58 result_dir = Path(config.path_output_results_static)
59 stress = np.load(result_dir / "stress_tensor_plane1.npy")
60 names = ["normal_stress_static", "shear_stress_static", "cfs_static"]
61 values = np.column_stack([
62 np.loadtxt(result_dir / f"{name}_plane1.csv", delimiter=",", ndmin=1)
63 for name in names
64 ])
65 if stress.shape != (3, 6) or values.shape != (3, 3):
66 raise AssertionError(f"Unexpected output shapes: {stress.shape}, {values.shape}")
67 if not np.isfinite(stress).all() or not np.isfinite(values).all() or not np.any(values):
68 raise AssertionError("Results must be finite and nonzero")
69 np.testing.assert_allclose(values[:, 2], values[:, 1] + config.mu_f * values[:, 0],
70 rtol=1e-12, atol=1e-10)
71 fig, ax = plt.subplots(figsize=(8, 4.5), constrained_layout=True)
72 for index, label in enumerate(["Normal stress", "Shear stress", "Coulomb stress"]):
73 ax.plot(distances, values[:, index] / 1000, "o-", label=label)
74 ax.axhline(0, color="0.5", lw=0.8)
75 ax.set(xlabel="Epicentral distance (km)", ylabel="Stress change (kPa)",
76 title="DynCFS · a 1 km² patch with 1 m slip")
77 ax.grid(alpha=0.2)
78 ax.legend()
79 fig.savefig(output / "static_cfs.png", dpi=160)
80 plt.close(fig)
81 report = {
82 "dyncfs": __version__, "pygrnwang_source_version": pygrnwang.__version__,
83 "pygrnwang_source": str(Path(pygrnwang.__file__).resolve()),
84 "python": platform.python_version(), "platform": platform.platform(),
85 "backend": "EDGRN2 + EDCMP2", "elapsed_seconds": time.perf_counter() - started,
86 "stress_shape": list(stress.shape), "resolved_stress_shape": list(values.shape),
87 "distance_km": distances.tolist(), "stress_unit": "Pa",
88 "normal_shear_cfs_pa": values.tolist(),
89 "checks": ["finite nonzero outputs", "expected array shapes", "CFS = shear + 0.4 * normal"],
90 }
91 (output / "summary.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
92 print(json.dumps(report, indent=2))
93
94
95if __name__ == "__main__":
96 main()
[path]
# The preparation script replaces these with absolute paths.
path_input = docs/_build/quickstart/input
path_output = docs/_build/quickstart
[input_addition]
optimal_type = 0
tectonic_stress_type = 1
tectonic_stress = [-4, 0, 0, -6, 0, -2]
mu_f = 0.4
B_pore = 0
source_inds = [1]
source_shapes = [[1, 1]]
source_ref = [0, 0]
obs_inds = [1]
obs_shapes = [[3, 1]]
obs_ref = [0, 0]
earth_model_layer_num = 24
use_spherical = False
slip_thresh = 0
cut_stf = 0
correct_zero_freq = False
[fixed_obs_depth]
fixed_obs_depth = 0
obs_lat_range = [0, 0]
obs_lon_range = [0.3, 0.8]
obs_delta_lat = 0.1
obs_delta_lon = 0.1
receiver_mechanism = None
[grn_region]
# EDGRN needs at least two source depths.
grn_source_depth_range = [10, 11]
grn_delta_source_depth = 1
grn_obs_depth_range = [5, 5]
grn_delta_obs_depth = 1
grn_dist_unit = km
grn_dist_range = [1, 121]
grn_delta_dist = 10
[time_window]
sampling_interval_stf = 0.5
sampling_interval_cfs = 0.5
sampling_num = 256
max_frequency = 1
[parallel]
processes_num = 1
check_finished = False
[default_config]
default_config = True