Spaces:
Sleeping
Sleeping
feature(initial): Initial commit
Browse files- README.md +5 -0
- app.py +305 -5
- surfdisp2k25/README.md +113 -0
- surfdisp2k25/README_dispsurf2k25.md +129 -0
- surfdisp2k25/__init__.py +37 -0
- surfdisp2k25/dispsurf2k25.py +477 -0
- surfdisp2k25/dltar.py +454 -0
- surfdisp2k25/dnka.py +119 -0
- surfdisp2k25/getsol.py +205 -0
- surfdisp2k25/getsolh.py +75 -0
- surfdisp2k25/half.py +84 -0
- surfdisp2k25/nevill.py +248 -0
- surfdisp2k25/normc.py +70 -0
- surfdisp2k25/sphere.py +147 -0
- surfdisp2k25/var.py +133 -0
README.md
CHANGED
|
@@ -12,3 +12,8 @@ short_description: Surfdisp2k25 simulator for dispersion curve generation
|
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 15 |
+
|
| 16 |
+
## Running locally
|
| 17 |
+
|
| 18 |
+
- Install dependencies with `pip install -r requirements.txt`.
|
| 19 |
+
- Launch the interface via `python app.py` and open the local URL printed by Gradio.
|
app.py
CHANGED
|
@@ -1,7 +1,307 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
-
|
| 4 |
-
return "Hello " + name + "!!"
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
|
| 3 |
+
from typing import Sequence
|
|
|
|
| 4 |
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
import gradio as gr
|
| 11 |
+
except ModuleNotFoundError: # pragma: no cover - optional dependency for tests
|
| 12 |
+
gr = None # type: ignore[assignment]
|
| 13 |
+
|
| 14 |
+
from surfdisp2k25 import dispsurf2k25_simulator
|
| 15 |
+
|
| 16 |
+
MODEL_SIZE = 60
|
| 17 |
+
CUSTOM_MODEL_LABEL = "Custom (enter values below)"
|
| 18 |
+
|
| 19 |
+
# Preset shear-wave velocity profiles (km/s) sampled over 60 layers.
|
| 20 |
+
PRESET_MODELS: dict[str, np.ndarray] = {
|
| 21 |
+
"Soft Sedimentary Basin": np.concatenate(
|
| 22 |
+
[
|
| 23 |
+
np.linspace(0.8, 1.6, 20, dtype=np.float32),
|
| 24 |
+
np.linspace(1.6, 2.4, 20, dtype=np.float32),
|
| 25 |
+
np.linspace(2.4, 3.2, 20, dtype=np.float32),
|
| 26 |
+
]
|
| 27 |
+
),
|
| 28 |
+
"Continental Crust": np.concatenate(
|
| 29 |
+
[
|
| 30 |
+
np.linspace(2.6, 3.2, 15, dtype=np.float32),
|
| 31 |
+
np.linspace(3.2, 3.8, 25, dtype=np.float32),
|
| 32 |
+
np.linspace(3.8, 4.5, 20, dtype=np.float32),
|
| 33 |
+
]
|
| 34 |
+
),
|
| 35 |
+
"Oceanic Lithosphere": np.concatenate(
|
| 36 |
+
[
|
| 37 |
+
np.linspace(1.8, 2.4, 15, dtype=np.float32),
|
| 38 |
+
np.linspace(2.4, 3.4, 25, dtype=np.float32),
|
| 39 |
+
np.linspace(3.4, 4.2, 20, dtype=np.float32),
|
| 40 |
+
]
|
| 41 |
+
),
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
DEFAULT_CUSTOM_PROFILE = PRESET_MODELS["Continental Crust"]
|
| 45 |
+
DEFAULT_TABLE = [[float(v)] for v in DEFAULT_CUSTOM_PROFILE]
|
| 46 |
+
|
| 47 |
+
EARTH_MODEL_OPTIONS = {
|
| 48 |
+
"Flat Earth (iflsph=0)": 0,
|
| 49 |
+
"Spherical Earth (iflsph=1)": 1,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
WAVE_TYPE_OPTIONS = {
|
| 53 |
+
"Rayleigh waves (iwave=2)": 2,
|
| 54 |
+
"Love waves (iwave=1)": 1,
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
GROUP_VELOCITY_OPTIONS = {
|
| 58 |
+
"Phase velocity only (igr=0)": 0,
|
| 59 |
+
"Phase & group velocity (igr=1)": 1,
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class ValidationError(ValueError):
|
| 64 |
+
"""Raised when user input is invalid."""
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _fail(message: str) -> None:
|
| 68 |
+
if gr is not None:
|
| 69 |
+
raise gr.Error(message)
|
| 70 |
+
raise ValidationError(message)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _extract_model_values(
|
| 74 |
+
model_label: str, table_values: Sequence[Sequence[float]]
|
| 75 |
+
) -> np.ndarray:
|
| 76 |
+
if model_label != CUSTOM_MODEL_LABEL:
|
| 77 |
+
return PRESET_MODELS[model_label]
|
| 78 |
+
|
| 79 |
+
flattened: list[float] = []
|
| 80 |
+
for row in table_values:
|
| 81 |
+
if isinstance(row, (list, tuple)):
|
| 82 |
+
value = row[0] if row else None
|
| 83 |
+
else:
|
| 84 |
+
value = row
|
| 85 |
+
|
| 86 |
+
if value in (None, ""):
|
| 87 |
+
_fail("All 60 custom model rows must contain a value.")
|
| 88 |
+
try:
|
| 89 |
+
flattened.append(float(value))
|
| 90 |
+
except (TypeError, ValueError) as exc:
|
| 91 |
+
_fail("Custom model values must be numeric.")
|
| 92 |
+
|
| 93 |
+
values = np.asarray(flattened, dtype=np.float32)
|
| 94 |
+
if values.size != MODEL_SIZE:
|
| 95 |
+
_fail(
|
| 96 |
+
f"Custom model must contain exactly {MODEL_SIZE} values "
|
| 97 |
+
f"(received {values.size})."
|
| 98 |
+
)
|
| 99 |
+
if np.any((values < 0.0) | (values > 8.0)):
|
| 100 |
+
_fail("Custom model values must stay between 0 and 8 km/s.")
|
| 101 |
+
return values
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _build_theta(
|
| 105 |
+
vs_values: np.ndarray, vp_vs_ratio: float, min_depth: float, max_depth: float
|
| 106 |
+
) -> torch.Tensor:
|
| 107 |
+
if vp_vs_ratio <= 1.0:
|
| 108 |
+
_fail("Vp/Vs ratio must be greater than 1.0.")
|
| 109 |
+
if max_depth <= min_depth:
|
| 110 |
+
_fail("Max depth must be greater than min depth.")
|
| 111 |
+
|
| 112 |
+
n_layers = vs_values.size
|
| 113 |
+
if n_layers == 0:
|
| 114 |
+
_fail("Model must contain at least one layer.")
|
| 115 |
+
if n_layers > 100:
|
| 116 |
+
_fail("Models cannot exceed 100 layers.")
|
| 117 |
+
|
| 118 |
+
if n_layers == 1:
|
| 119 |
+
layer_thickness = 0.0
|
| 120 |
+
else:
|
| 121 |
+
layer_thickness = (max_depth - min_depth) / (n_layers - 1)
|
| 122 |
+
|
| 123 |
+
thickness = np.full(n_layers, layer_thickness, dtype=np.float32)
|
| 124 |
+
thickness[-1] = 0.0 # Half-space
|
| 125 |
+
|
| 126 |
+
theta = np.concatenate(
|
| 127 |
+
[
|
| 128 |
+
np.array([float(n_layers), float(vp_vs_ratio)], dtype=np.float32),
|
| 129 |
+
thickness,
|
| 130 |
+
vs_values.astype(np.float32),
|
| 131 |
+
]
|
| 132 |
+
)
|
| 133 |
+
return torch.from_numpy(theta).unsqueeze(0)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _make_plot(periods: np.ndarray, velocities: np.ndarray):
|
| 137 |
+
fig, ax = plt.subplots(figsize=(7, 4))
|
| 138 |
+
ax.plot(periods, velocities, marker="o", markersize=3, linewidth=1.5)
|
| 139 |
+
ax.set_xlabel("Period (s)")
|
| 140 |
+
ax.set_ylabel("Phase velocity (km/s)")
|
| 141 |
+
ax.set_title("SurfDisp2k25 Dispersion Curve")
|
| 142 |
+
ax.grid(True, linestyle="--", linewidth=0.6, alpha=0.5)
|
| 143 |
+
fig.tight_layout()
|
| 144 |
+
return fig
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def run_simulation(
|
| 148 |
+
model_label: str,
|
| 149 |
+
custom_values: Sequence[Sequence[float]],
|
| 150 |
+
min_depth: float,
|
| 151 |
+
max_depth: float,
|
| 152 |
+
vp_vs_ratio: float,
|
| 153 |
+
p_min: float,
|
| 154 |
+
p_max: float,
|
| 155 |
+
earth_model_label: str,
|
| 156 |
+
wave_type_label: str,
|
| 157 |
+
mode: int,
|
| 158 |
+
group_label: str,
|
| 159 |
+
):
|
| 160 |
+
vs_values = _extract_model_values(model_label, custom_values)
|
| 161 |
+
|
| 162 |
+
if p_min <= 0.0 or p_max <= 0.0:
|
| 163 |
+
_fail("Periods must be positive numbers.")
|
| 164 |
+
if p_max <= p_min:
|
| 165 |
+
_fail("Maximum period must be greater than minimum period.")
|
| 166 |
+
if mode < 1:
|
| 167 |
+
_fail("Mode number must be at least 1.")
|
| 168 |
+
|
| 169 |
+
theta = _build_theta(vs_values, vp_vs_ratio, min_depth, max_depth)
|
| 170 |
+
iflsph = EARTH_MODEL_OPTIONS[earth_model_label]
|
| 171 |
+
iwave = WAVE_TYPE_OPTIONS[wave_type_label]
|
| 172 |
+
igr = GROUP_VELOCITY_OPTIONS[group_label]
|
| 173 |
+
|
| 174 |
+
try:
|
| 175 |
+
disp = dispsurf2k25_simulator(
|
| 176 |
+
theta=theta,
|
| 177 |
+
p_min=float(p_min),
|
| 178 |
+
p_max=float(p_max),
|
| 179 |
+
kmax=MODEL_SIZE,
|
| 180 |
+
iflsph=iflsph,
|
| 181 |
+
iwave=iwave,
|
| 182 |
+
mode=int(mode),
|
| 183 |
+
igr=igr,
|
| 184 |
+
dtype=torch.float32,
|
| 185 |
+
)
|
| 186 |
+
except RuntimeError as exc:
|
| 187 |
+
_fail(f"Simulation failed: {exc}")
|
| 188 |
+
|
| 189 |
+
velocities = disp.squeeze(0).detach().cpu().numpy()
|
| 190 |
+
periods = np.linspace(p_min, p_max, velocities.size)
|
| 191 |
+
plot = _make_plot(periods, velocities)
|
| 192 |
+
|
| 193 |
+
table = [[float(p), float(v)] for p, v in zip(periods, velocities)]
|
| 194 |
+
|
| 195 |
+
layer_thickness = 0.0 if vs_values.size <= 1 else (max_depth - min_depth) / (
|
| 196 |
+
vs_values.size - 1
|
| 197 |
+
)
|
| 198 |
+
summary = "\n".join(
|
| 199 |
+
[
|
| 200 |
+
f"**Model**: {model_label}",
|
| 201 |
+
f"**Layers**: {vs_values.size} (Δz ≈ {layer_thickness:.2f} km)",
|
| 202 |
+
f"**Vs range**: {vs_values.min():.2f} – {vs_values.max():.2f} km/s",
|
| 203 |
+
f"**Periods**: {p_min:.2f} – {p_max:.2f} s ({velocities.size} samples)",
|
| 204 |
+
f"**Wave type**: {wave_type_label}; mode = {int(mode)}",
|
| 205 |
+
f"**Phase velocity range**: {velocities.min():.2f} – {velocities.max():.2f} km/s",
|
| 206 |
+
]
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
return plot, table, summary
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
if gr is not None:
|
| 213 |
+
with gr.Blocks(title="SurfDisp2k25 Simulator") as demo:
|
| 214 |
+
gr.Markdown(
|
| 215 |
+
"### SurfDisp2k25 dispersion simulator\n"
|
| 216 |
+
"Select a preset velocity profile or switch to custom mode to edit the "
|
| 217 |
+
"60 Vs values (km/s) sampled between the minimum and maximum depth."
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
with gr.Row():
|
| 221 |
+
with gr.Column():
|
| 222 |
+
model_selector = gr.Dropdown(
|
| 223 |
+
choices=[*PRESET_MODELS.keys(), CUSTOM_MODEL_LABEL],
|
| 224 |
+
value="Continental Crust",
|
| 225 |
+
label="Shear-wave velocity model",
|
| 226 |
+
)
|
| 227 |
+
custom_model = gr.Dataframe(
|
| 228 |
+
headers=["Vs (km/s)"],
|
| 229 |
+
value=DEFAULT_TABLE,
|
| 230 |
+
row_count=(MODEL_SIZE, "fixed"),
|
| 231 |
+
col_count=(1, "fixed"),
|
| 232 |
+
datatype="float",
|
| 233 |
+
label="Custom Vs profile (used when Custom is selected)",
|
| 234 |
+
)
|
| 235 |
+
min_depth_input = gr.Number(value=0.0, label="Minimum depth (km)")
|
| 236 |
+
max_depth_input = gr.Number(value=30.0, label="Maximum depth (km)")
|
| 237 |
+
vpvs_input = gr.Slider(
|
| 238 |
+
minimum=1.5,
|
| 239 |
+
maximum=2.2,
|
| 240 |
+
value=1.75,
|
| 241 |
+
step=0.01,
|
| 242 |
+
label="Vp/Vs ratio",
|
| 243 |
+
)
|
| 244 |
+
with gr.Column():
|
| 245 |
+
p_min_input = gr.Number(value=1.0, label="Minimum period (s)")
|
| 246 |
+
p_max_input = gr.Number(value=30.0, label="Maximum period (s)")
|
| 247 |
+
earth_model_input = gr.Radio(
|
| 248 |
+
choices=list(EARTH_MODEL_OPTIONS.keys()),
|
| 249 |
+
value="Flat Earth (iflsph=0)",
|
| 250 |
+
label="Earth model",
|
| 251 |
+
)
|
| 252 |
+
wave_type_input = gr.Radio(
|
| 253 |
+
choices=list(WAVE_TYPE_OPTIONS.keys()),
|
| 254 |
+
value="Rayleigh waves (iwave=2)",
|
| 255 |
+
label="Wave type",
|
| 256 |
+
)
|
| 257 |
+
mode_input = gr.Slider(
|
| 258 |
+
minimum=1,
|
| 259 |
+
maximum=5,
|
| 260 |
+
value=1,
|
| 261 |
+
step=1,
|
| 262 |
+
label="Mode number",
|
| 263 |
+
)
|
| 264 |
+
group_mode_input = gr.Radio(
|
| 265 |
+
choices=list(GROUP_VELOCITY_OPTIONS.keys()),
|
| 266 |
+
value="Phase velocity only (igr=0)",
|
| 267 |
+
label="Velocity output",
|
| 268 |
+
)
|
| 269 |
+
run_button = gr.Button("Run simulation", variant="primary")
|
| 270 |
+
|
| 271 |
+
with gr.Row():
|
| 272 |
+
plot_output = gr.Plot(label="Dispersion curve")
|
| 273 |
+
table_output = gr.Dataframe(
|
| 274 |
+
headers=["Period (s)", "Phase velocity (km/s)"],
|
| 275 |
+
datatype="float",
|
| 276 |
+
col_count=(2, "fixed"),
|
| 277 |
+
row_count=(MODEL_SIZE, "dynamic"),
|
| 278 |
+
label="Sampled dispersion values",
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
summary_output = gr.Markdown()
|
| 282 |
+
|
| 283 |
+
run_button.click(
|
| 284 |
+
fn=run_simulation,
|
| 285 |
+
inputs=[
|
| 286 |
+
model_selector,
|
| 287 |
+
custom_model,
|
| 288 |
+
min_depth_input,
|
| 289 |
+
max_depth_input,
|
| 290 |
+
vpvs_input,
|
| 291 |
+
p_min_input,
|
| 292 |
+
p_max_input,
|
| 293 |
+
earth_model_input,
|
| 294 |
+
wave_type_input,
|
| 295 |
+
mode_input,
|
| 296 |
+
group_mode_input,
|
| 297 |
+
],
|
| 298 |
+
outputs=[plot_output, table_output, summary_output],
|
| 299 |
+
)
|
| 300 |
+
else: # pragma: no cover - allows importing without gradio installed
|
| 301 |
+
demo = None
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
if __name__ == "__main__":
|
| 305 |
+
if demo is None:
|
| 306 |
+
raise ImportError("gradio must be installed to launch the interface.")
|
| 307 |
+
demo.launch()
|
surfdisp2k25/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# surfdisp2k25 Extension
|
| 2 |
+
|
| 3 |
+
This package provides a Python implementation of the surfdisp96 Fortran code, which is used for calculating dispersion curves for surface waves in layered media.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
The `surfdisp2k25` package includes Python implementations of several key functions from the original Fortran code:
|
| 8 |
+
|
| 9 |
+
- `dltar`: Computes the period equation for Love or Rayleigh waves
|
| 10 |
+
- `dltar1`: Computes the period equation for Love waves
|
| 11 |
+
- `dltar4`: Computes the period equation for Rayleigh waves
|
| 12 |
+
- `nevill`: Hybrid method for refining a root once it has been bracketed
|
| 13 |
+
- `half`: Interval halving method for refining a root
|
| 14 |
+
- `getsol`: Brackets a dispersion curve and then refines it
|
| 15 |
+
|
| 16 |
+
## Using the `getsol` Function
|
| 17 |
+
|
| 18 |
+
The `getsol` function is used to find phase velocities for surface waves at a given period. It first brackets the solution by finding values with opposite signs of the period equation, then refines the solution using the `nevill` function.
|
| 19 |
+
|
| 20 |
+
### Function Signature
|
| 21 |
+
|
| 22 |
+
```python
|
| 23 |
+
def getsol(t1, c1, clow, dc, cm, betmx, ifunc, ifirst, d, a, b, rho):
|
| 24 |
+
"""
|
| 25 |
+
Bracket dispersion curve and then refine it.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
t1 : float
|
| 30 |
+
Period in seconds.
|
| 31 |
+
c1 : float
|
| 32 |
+
Initial phase velocity estimate in km/s.
|
| 33 |
+
clow : float
|
| 34 |
+
Lower bound for phase velocity in km/s.
|
| 35 |
+
dc : float
|
| 36 |
+
Phase velocity increment for search in km/s.
|
| 37 |
+
cm : float
|
| 38 |
+
Minimum phase velocity to consider in km/s.
|
| 39 |
+
betmx : float
|
| 40 |
+
Maximum phase velocity to consider in km/s.
|
| 41 |
+
ifunc : int
|
| 42 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 43 |
+
ifirst : int
|
| 44 |
+
First call flag: 1 for first call, 0 otherwise.
|
| 45 |
+
d : array_like
|
| 46 |
+
Layer thicknesses in km.
|
| 47 |
+
a : array_like
|
| 48 |
+
P-wave velocities in km/s.
|
| 49 |
+
b : array_like
|
| 50 |
+
S-wave velocities in km/s.
|
| 51 |
+
rho : array_like
|
| 52 |
+
Densities in g/cm^3.
|
| 53 |
+
|
| 54 |
+
Returns
|
| 55 |
+
-------
|
| 56 |
+
tuple
|
| 57 |
+
A tuple containing:
|
| 58 |
+
- c1: float, the refined phase velocity in km/s.
|
| 59 |
+
- iret: int, return code (1 for success, -1 for failure).
|
| 60 |
+
"""
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Example Usage
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
import numpy as np
|
| 67 |
+
from migrate.extensions.surfdisp2k25 import getsol
|
| 68 |
+
|
| 69 |
+
# Define a three-layer model
|
| 70 |
+
d = np.array([2.0, 5.0, 10.0]) # Layer thicknesses in km
|
| 71 |
+
a = np.array([5.8, 6.8, 8.0]) # P-wave velocities in km/s
|
| 72 |
+
b = np.array([3.2, 3.9, 4.5]) # S-wave velocities in km/s
|
| 73 |
+
rho = np.array([2.7, 3.0, 3.3]) # Densities in g/cm^3
|
| 74 |
+
|
| 75 |
+
# Parameters for getsol
|
| 76 |
+
t1 = 10.0 # Period in seconds
|
| 77 |
+
c1 = 3.0 # Initial phase velocity estimate in km/s
|
| 78 |
+
clow = 2.0 # Lower bound for phase velocity
|
| 79 |
+
dc = 0.01 # Phase velocity increment
|
| 80 |
+
cm = 2.0 # Minimum phase velocity
|
| 81 |
+
betmx = 5.0 # Maximum phase velocity
|
| 82 |
+
ifunc = 2 # 2 for Rayleigh waves, 1 for Love waves
|
| 83 |
+
ifirst = 1 # 1 for first call, 0 otherwise
|
| 84 |
+
|
| 85 |
+
# Call getsol to find the phase velocity
|
| 86 |
+
c_refined, iret = getsol(t1, c1, clow, dc, cm, betmx, ifunc, ifirst, d, a, b, rho)
|
| 87 |
+
|
| 88 |
+
if iret == 1:
|
| 89 |
+
print(f"Found phase velocity: {c_refined} km/s")
|
| 90 |
+
else:
|
| 91 |
+
print("Failed to find a solution")
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
## Differences from the Original Fortran Code
|
| 95 |
+
|
| 96 |
+
The Python implementation differs from the original Fortran code in several ways:
|
| 97 |
+
|
| 98 |
+
1. **Return Values**: Instead of using output arguments, the Python functions return values directly. For example, `getsol` returns a tuple containing the refined phase velocity and a return code.
|
| 99 |
+
|
| 100 |
+
2. **Error Handling**: The Python implementation includes proper error handling with descriptive error messages.
|
| 101 |
+
|
| 102 |
+
3. **Array Handling**: The Python implementation uses NumPy arrays instead of Fortran arrays, which provides more flexibility and better performance.
|
| 103 |
+
|
| 104 |
+
4. **Static Variables**: The Fortran code uses static variables to save state between calls. The Python implementation simulates this using function attributes.
|
| 105 |
+
|
| 106 |
+
5. **Control Flow**: The Python implementation replaces goto statements with more structured control flow constructs like loops and conditional statements.
|
| 107 |
+
|
| 108 |
+
## Implementation Notes
|
| 109 |
+
|
| 110 |
+
- The `dltar`, `dltar1`, and `dltar4` functions are different methods for calculating the period equation for different wave types.
|
| 111 |
+
- The `nevill` function uses a combination of Neville's algorithm and interval halving to refine a root.
|
| 112 |
+
- The `half` function implements a simple interval halving method for root finding.
|
| 113 |
+
- The `getsol` function combines these methods to find phase velocities for dispersion curves.
|
surfdisp2k25/README_dispsurf2k25.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# dispsurf2k25 Function
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The `dispsurf2k25` function is a Python implementation of the Fortran subroutine `surfdisp96` from the `surfdisp96.f90` file. It calculates surface wave dispersion curves for either Love waves or Rayleigh waves.
|
| 6 |
+
|
| 7 |
+
## Implementation Details
|
| 8 |
+
|
| 9 |
+
The implementation follows these key principles:
|
| 10 |
+
|
| 11 |
+
1. **Pure Python Implementation**: The function is implemented in pure Python using NumPy for array operations.
|
| 12 |
+
2. **Return Values Instead of Output Arguments**: Unlike the Fortran subroutine which uses output arguments, the Python implementation returns values as a tuple.
|
| 13 |
+
3. **Python Implementations of Dependencies**: The function uses the Python implementations of `sphere`, `getsol`, and `getsolh` from the `surfdisp2k25` module.
|
| 14 |
+
4. **Input Validation**: The function includes validation of input parameters to ensure they are valid.
|
| 15 |
+
|
| 16 |
+
## Function Signature
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
def dispsurf2k25(thkm, vpm, vsm, rhom, iflsph, iwave, mode, igr, kmax, t):
|
| 20 |
+
"""
|
| 21 |
+
Calculate surface wave dispersion curves.
|
| 22 |
+
|
| 23 |
+
Parameters
|
| 24 |
+
----------
|
| 25 |
+
thkm : array_like
|
| 26 |
+
Layer thicknesses in km.
|
| 27 |
+
vpm : array_like
|
| 28 |
+
P-wave velocities in km/s.
|
| 29 |
+
vsm : array_like
|
| 30 |
+
S-wave velocities in km/s.
|
| 31 |
+
rhom : array_like
|
| 32 |
+
Densities in g/cm^3.
|
| 33 |
+
iflsph : int
|
| 34 |
+
Flag for spherical earth model: 0 for flat earth, 1 for spherical earth.
|
| 35 |
+
iwave : int
|
| 36 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 37 |
+
mode : int
|
| 38 |
+
Mode number to calculate.
|
| 39 |
+
igr : int
|
| 40 |
+
Flag for group velocity calculation: 0 for phase velocity only, 1 for phase and group velocity.
|
| 41 |
+
kmax : int
|
| 42 |
+
Number of periods to calculate.
|
| 43 |
+
t : array_like
|
| 44 |
+
Periods in seconds.
|
| 45 |
+
|
| 46 |
+
Returns
|
| 47 |
+
-------
|
| 48 |
+
tuple
|
| 49 |
+
A tuple containing:
|
| 50 |
+
- cg: array_like, calculated phase or group velocities in km/s.
|
| 51 |
+
- err: int, error code (0 for success, 1 for error).
|
| 52 |
+
"""
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
## Usage Examples
|
| 56 |
+
|
| 57 |
+
### Basic Usage with Love Waves
|
| 58 |
+
|
| 59 |
+
```python
|
| 60 |
+
import numpy as np
|
| 61 |
+
from migrate.extensions.surfdisp2k25 import dispsurf2k25
|
| 62 |
+
|
| 63 |
+
# Create a simple model
|
| 64 |
+
# 3 layers: 2 layers + half-space
|
| 65 |
+
thkm = np.array([10.0, 20.0, 0.0]) # Layer thicknesses in km
|
| 66 |
+
vpm = np.array([5.0, 6.0, 7.0]) # P-wave velocities in km/s
|
| 67 |
+
vsm = np.array([3.0, 3.5, 4.0]) # S-wave velocities in km/s
|
| 68 |
+
rhom = np.array([2.7, 2.9, 3.1]) # Densities in g/cm^3
|
| 69 |
+
|
| 70 |
+
# Parameters for dispsurf2k25
|
| 71 |
+
iflsph = 0 # Flat earth model
|
| 72 |
+
iwave = 1 # Love waves
|
| 73 |
+
mode = 1 # Fundamental mode
|
| 74 |
+
igr = 0 # Phase velocity only
|
| 75 |
+
kmax = 5 # Number of periods
|
| 76 |
+
t = np.array([5.0, 10.0, 15.0, 20.0, 25.0]) # Periods in seconds
|
| 77 |
+
|
| 78 |
+
# Call the function
|
| 79 |
+
cg, err = dispsurf2k25(thkm, vpm, vsm, rhom, iflsph, iwave, mode, igr, kmax, t)
|
| 80 |
+
print(f"Phase velocities: {cg}")
|
| 81 |
+
print(f"Error code: {err}")
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Usage with Rayleigh Waves
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
# Same model as above, but with Rayleigh waves
|
| 88 |
+
iwave = 2 # Rayleigh waves
|
| 89 |
+
|
| 90 |
+
# Call the function
|
| 91 |
+
cg, err = dispsurf2k25(thkm, vpm, vsm, rhom, iflsph, iwave, mode, igr, kmax, t)
|
| 92 |
+
print(f"Phase velocities: {cg}")
|
| 93 |
+
print(f"Error code: {err}")
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Usage with Group Velocity Calculation
|
| 97 |
+
|
| 98 |
+
```python
|
| 99 |
+
# Same model as above, but with group velocity calculation
|
| 100 |
+
iwave = 1 # Love waves
|
| 101 |
+
igr = 1 # Group velocity calculation
|
| 102 |
+
|
| 103 |
+
# Call the function
|
| 104 |
+
cg, err = dispsurf2k25(thkm, vpm, vsm, rhom, iflsph, iwave, mode, igr, kmax, t)
|
| 105 |
+
print(f"Group velocities: {cg}")
|
| 106 |
+
print(f"Error code: {err}")
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### Usage with Spherical Earth Model
|
| 110 |
+
|
| 111 |
+
```python
|
| 112 |
+
# Same model as above, but with spherical earth model
|
| 113 |
+
iflsph = 1 # Spherical earth model
|
| 114 |
+
iwave = 1 # Love waves
|
| 115 |
+
igr = 0 # Phase velocity only
|
| 116 |
+
|
| 117 |
+
# Call the function
|
| 118 |
+
cg, err = dispsurf2k25(thkm, vpm, vsm, rhom, iflsph, iwave, mode, igr, kmax, t)
|
| 119 |
+
print(f"Phase velocities: {cg}")
|
| 120 |
+
print(f"Error code: {err}")
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
## Notes
|
| 124 |
+
|
| 125 |
+
- The function returns an error code of 1 if it encounters an error during the calculation, such as not finding a root for a particular period.
|
| 126 |
+
- For periods where a root is not found, the corresponding velocity in the output array is set to 0.0.
|
| 127 |
+
- The function handles both Love waves (iwave=1) and Rayleigh waves (iwave=2).
|
| 128 |
+
- The function can calculate either phase velocities only (igr=0) or both phase and group velocities (igr=1).
|
| 129 |
+
- The function can handle both flat earth models (iflsph=0) and spherical earth models (iflsph=1).
|
surfdisp2k25/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python implementation of the surfdisp96 extension.
|
| 3 |
+
|
| 4 |
+
This module provides functions for calculating dispersion curves for surface waves
|
| 5 |
+
in layered media. It is a pure Python implementation of the surfdisp96 Fortran code.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
# Import functions from their respective modules
|
| 11 |
+
from .getsol import getsol
|
| 12 |
+
from .getsolh import getsolh
|
| 13 |
+
from .dispsurf2k25 import dispsurf2k25, dispsurf2k25_simulator
|
| 14 |
+
from .normc import normc
|
| 15 |
+
from .var import var
|
| 16 |
+
from .dnka import dnka
|
| 17 |
+
from .dltar import dltar, dltar1, dltar4
|
| 18 |
+
from .half import half
|
| 19 |
+
from .nevill import nevill
|
| 20 |
+
from .sphere import sphere
|
| 21 |
+
|
| 22 |
+
# Define the public API
|
| 23 |
+
__all__ = [
|
| 24 |
+
'getsol',
|
| 25 |
+
'getsolh',
|
| 26 |
+
'dispsurf2k25_simulator',
|
| 27 |
+
'dispsurf2k25',
|
| 28 |
+
'normc',
|
| 29 |
+
'var',
|
| 30 |
+
'dnka',
|
| 31 |
+
'dltar',
|
| 32 |
+
'dltar1',
|
| 33 |
+
'dltar4',
|
| 34 |
+
'half',
|
| 35 |
+
'nevill',
|
| 36 |
+
'sphere'
|
| 37 |
+
]
|
surfdisp2k25/dispsurf2k25.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python implementation of the surfdisp96 subroutine from surfdisp96.f90.
|
| 3 |
+
|
| 4 |
+
This module provides a Python implementation of the surfdisp96 subroutine
|
| 5 |
+
from the surfdisp96 Fortran code, which is responsible for calculating
|
| 6 |
+
surface wave dispersion curves.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from typing import Tuple, List, Union, Optional, Any
|
| 12 |
+
from types import SimpleNamespace
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
import migrate.extensions.surfdisp2k25 as sd2k25 # type: ignore
|
| 16 |
+
except ModuleNotFoundError: # pragma: no cover - fallback for pure Python envs
|
| 17 |
+
from .sphere import sphere as _sphere
|
| 18 |
+
from .getsol import getsol as _getsol
|
| 19 |
+
from .getsolh import getsolh as _getsolh
|
| 20 |
+
from .dltar import dltar as _dltar
|
| 21 |
+
from .nevill import nevill as _nevill
|
| 22 |
+
|
| 23 |
+
sd2k25 = SimpleNamespace(
|
| 24 |
+
sphere=_sphere,
|
| 25 |
+
getsol=_getsol,
|
| 26 |
+
getsolh=_getsolh,
|
| 27 |
+
dltar=_dltar,
|
| 28 |
+
nevill=_nevill,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def dispsurf2k25_simulator(
|
| 33 |
+
theta: torch.Tensor,
|
| 34 |
+
p_min: float,
|
| 35 |
+
p_max: float,
|
| 36 |
+
kmax: int,
|
| 37 |
+
iflsph: int = 0,
|
| 38 |
+
iwave: int = 2,
|
| 39 |
+
mode: int = 1,
|
| 40 |
+
igr: int = 1,
|
| 41 |
+
dtype: torch.dtype = torch.float32,
|
| 42 |
+
) -> torch.Tensor:
|
| 43 |
+
"""
|
| 44 |
+
surfdisp2k25_simulator
|
| 45 |
+
"""
|
| 46 |
+
bs = theta.shape[0]
|
| 47 |
+
|
| 48 |
+
# To numpy
|
| 49 |
+
theta = theta.numpy()
|
| 50 |
+
|
| 51 |
+
# Output dispersion curves
|
| 52 |
+
output_disp = np.zeros((bs, kmax))
|
| 53 |
+
|
| 54 |
+
# Prepare periods
|
| 55 |
+
t = np.linspace(p_min, p_max, kmax)
|
| 56 |
+
|
| 57 |
+
for b_i in range(bs):
|
| 58 |
+
# Prepare inputs
|
| 59 |
+
thkm = np.zeros(100)
|
| 60 |
+
vpm = np.zeros(100)
|
| 61 |
+
vsm = np.zeros(100)
|
| 62 |
+
rhom = np.zeros(100)
|
| 63 |
+
|
| 64 |
+
# Compute max. layer
|
| 65 |
+
max_layer = (theta.shape[1] - 2) // 2
|
| 66 |
+
|
| 67 |
+
# Number of layers
|
| 68 |
+
n = int(theta[b_i, 0])
|
| 69 |
+
vpvs = theta[b_i, 1]
|
| 70 |
+
|
| 71 |
+
# Get parameters
|
| 72 |
+
h = theta[b_i, 2:max_layer+2]
|
| 73 |
+
vs = theta[b_i, max_layer+2:]
|
| 74 |
+
|
| 75 |
+
# Get Vp
|
| 76 |
+
vp = vs * vpvs
|
| 77 |
+
|
| 78 |
+
# Get rho (simple linear law)
|
| 79 |
+
rho = 0.32 + 0.77 * vp
|
| 80 |
+
|
| 81 |
+
# Prepare inputs
|
| 82 |
+
thkm[:n] = h[:n]
|
| 83 |
+
vpm[:n] = vp[:n]
|
| 84 |
+
vsm[:n] = vs[:n]
|
| 85 |
+
rhom[:n] = rho[:n]
|
| 86 |
+
|
| 87 |
+
# Run simulation
|
| 88 |
+
disp_y, err = dispsurf2k25(
|
| 89 |
+
thkm=thkm,
|
| 90 |
+
vsm=vsm,
|
| 91 |
+
vpm=vpm,
|
| 92 |
+
rhom=rhom,
|
| 93 |
+
nlayer=n,
|
| 94 |
+
iflsph=iflsph,
|
| 95 |
+
iwave=iwave,
|
| 96 |
+
mode=mode,
|
| 97 |
+
igr=igr,
|
| 98 |
+
kmax=60,
|
| 99 |
+
t=t,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
if err != 0:
|
| 103 |
+
raise RuntimeError(f"Simulation {b_i} failed with error: {err}")
|
| 104 |
+
# end if
|
| 105 |
+
|
| 106 |
+
output_disp[b_i, :] = disp_y
|
| 107 |
+
# end for
|
| 108 |
+
|
| 109 |
+
return torch.from_numpy(output_disp).to(dtype)
|
| 110 |
+
# end dispsurf2k25_simulator
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def dispsurf2k25(
|
| 114 |
+
thkm: np.ndarray,
|
| 115 |
+
vpm: np.ndarray,
|
| 116 |
+
vsm: np.ndarray,
|
| 117 |
+
rhom: np.ndarray,
|
| 118 |
+
nlayer: int,
|
| 119 |
+
iflsph: int,
|
| 120 |
+
iwave: int,
|
| 121 |
+
mode: int,
|
| 122 |
+
igr: int,
|
| 123 |
+
kmax: int,
|
| 124 |
+
t: np.ndarray
|
| 125 |
+
) -> Tuple[np.ndarray, int]:
|
| 126 |
+
"""
|
| 127 |
+
Calculate surface wave dispersion curves.
|
| 128 |
+
|
| 129 |
+
This is a pure Python implementation of the surfdisp96 Fortran subroutine.
|
| 130 |
+
|
| 131 |
+
Parameters
|
| 132 |
+
----------
|
| 133 |
+
thkm : array_like
|
| 134 |
+
Layer thicknesses in km.
|
| 135 |
+
vpm : array_like
|
| 136 |
+
P-wave velocities in km/s.
|
| 137 |
+
vsm : array_like
|
| 138 |
+
S-wave velocities in km/s.
|
| 139 |
+
rhom : array_like
|
| 140 |
+
Densities in g/cm^3.
|
| 141 |
+
iflsph : int
|
| 142 |
+
Flag for spherical earth model: 0 for flat earth, 1 for spherical earth.
|
| 143 |
+
iwave : int
|
| 144 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 145 |
+
mode : int
|
| 146 |
+
Mode number to calculate.
|
| 147 |
+
igr : int
|
| 148 |
+
Flag for group velocity calculation: 0 for phase velocity only, 1 for phase and group velocity.
|
| 149 |
+
kmax : int
|
| 150 |
+
Number of periods to calculate.
|
| 151 |
+
t : array_like
|
| 152 |
+
Periods in seconds.
|
| 153 |
+
|
| 154 |
+
Returns
|
| 155 |
+
-------
|
| 156 |
+
tuple
|
| 157 |
+
A tuple containing:
|
| 158 |
+
- cg: array_like, calculated phase or group velocities in km/s.
|
| 159 |
+
- err: int, error code (0 for success, 1 for error).
|
| 160 |
+
"""
|
| 161 |
+
# Parameters
|
| 162 |
+
LER = 0
|
| 163 |
+
LIN = 5
|
| 164 |
+
LOT = 6
|
| 165 |
+
NL = 100
|
| 166 |
+
NLAY = 100
|
| 167 |
+
NL2 = NL + NL
|
| 168 |
+
NP = 60
|
| 169 |
+
|
| 170 |
+
# Check sizes
|
| 171 |
+
assert thkm.ndim == 1 and thkm.shape[0] == NLAY, f"thkm must be 1dim (but is {thkm.ndim}), and shape {NLAY} (but is {thkm.shape})."
|
| 172 |
+
assert vpm.ndim == 1 and vpm.shape[0] == NLAY, f"vpm must be 1dim (but is {vpm.ndim}), and shape {NLAY} (but is {vpm.shape})."
|
| 173 |
+
assert vsm.ndim == 1 and vsm.shape[0] == NLAY, f"vsm must be 1dim (but is {vsm.ndim}), and shape {NLAY} (but is {vsm.shape})."
|
| 174 |
+
assert rhom.ndim == 1 and rhom.shape[0] == NLAY, f"rhom must be 1dim (but is {rhom.ndim}), and shape {NLAY} (but is {rhom.shape})."
|
| 175 |
+
assert t.ndim == 1 and t.shape[0] == NP, f"t must be 1dim (but is {t.ndim}), and shape {NP} (but is {t.shape})."
|
| 176 |
+
|
| 177 |
+
# Arguments
|
| 178 |
+
cg = np.zeros(NP, dtype=np.float64)
|
| 179 |
+
|
| 180 |
+
# Local variables
|
| 181 |
+
c = np.zeros(NP, dtype=np.float64)
|
| 182 |
+
cb = np.zeros(NP, dtype=np.float64)
|
| 183 |
+
d = np.zeros(NL, dtype=np.float64)
|
| 184 |
+
a = np.zeros(NL, dtype=np.float64)
|
| 185 |
+
b = np.zeros(NL, dtype=np.float64)
|
| 186 |
+
rho = np.zeros(NL, dtype=np.float64)
|
| 187 |
+
rtp = np.zeros(NL, dtype=np.float64)
|
| 188 |
+
dtp = np.zeros(NL, dtype=np.float64)
|
| 189 |
+
btp = np.zeros(NL, dtype=np.float64)
|
| 190 |
+
iverb = np.zeros(3, dtype=np.int32)
|
| 191 |
+
|
| 192 |
+
mmax = nlayer
|
| 193 |
+
nsph = iflsph
|
| 194 |
+
err = 0
|
| 195 |
+
|
| 196 |
+
# Save current values
|
| 197 |
+
b[:mmax] = vsm[:mmax]
|
| 198 |
+
a[:mmax] = vpm[:mmax]
|
| 199 |
+
d[:mmax] = thkm[:mmax]
|
| 200 |
+
rho[:mmax] = rhom[:mmax]
|
| 201 |
+
|
| 202 |
+
# Check if iwave is valid
|
| 203 |
+
if iwave not in [1, 2]:
|
| 204 |
+
raise ValueError(
|
| 205 |
+
"iwave must be 1 (Love waves) or 2 (Rayleigh waves)"
|
| 206 |
+
)
|
| 207 |
+
# end if
|
| 208 |
+
|
| 209 |
+
# Set up wave type
|
| 210 |
+
if iwave == 1:
|
| 211 |
+
idispl = kmax
|
| 212 |
+
idispr = 0
|
| 213 |
+
elif iwave == 2:
|
| 214 |
+
idispl = 0
|
| 215 |
+
idispr = kmax
|
| 216 |
+
else:
|
| 217 |
+
raise ValueError("iwave must be 1 or 2")
|
| 218 |
+
# end if
|
| 219 |
+
|
| 220 |
+
iverb[1] = 0
|
| 221 |
+
iverb[2] = 0
|
| 222 |
+
|
| 223 |
+
# Constants
|
| 224 |
+
sone0 = 1.500
|
| 225 |
+
ddc0 = 0.005
|
| 226 |
+
h0 = 0.005
|
| 227 |
+
|
| 228 |
+
# Check for water layer
|
| 229 |
+
llw = 1
|
| 230 |
+
if b[0] <= 0.0:
|
| 231 |
+
llw = 2
|
| 232 |
+
# end if
|
| 233 |
+
twopi = 2.0 * np.pi
|
| 234 |
+
one = 1.0e-2
|
| 235 |
+
# Apply spherical earth transformation if needed
|
| 236 |
+
if nsph == 1:
|
| 237 |
+
d, a, b, rho, rtp, dtp, btp = sd2k25.sphere(
|
| 238 |
+
ifunc=0,
|
| 239 |
+
iflag=0,
|
| 240 |
+
d=d,
|
| 241 |
+
a=a,
|
| 242 |
+
b=b,
|
| 243 |
+
rho=rho,
|
| 244 |
+
rtp=rtp,
|
| 245 |
+
dtp=dtp,
|
| 246 |
+
btp=btp,
|
| 247 |
+
mmax=mmax,
|
| 248 |
+
llw=llw,
|
| 249 |
+
twopi=twopi
|
| 250 |
+
)
|
| 251 |
+
# end if
|
| 252 |
+
|
| 253 |
+
JMN = 0
|
| 254 |
+
betmx = -1.0e20
|
| 255 |
+
betmn = 1.0e20
|
| 256 |
+
# Find the extremal velocities to assist in starting search
|
| 257 |
+
for i in range(nlayer):
|
| 258 |
+
if 0.01 < b[i] < betmn:
|
| 259 |
+
betmn = b[i]
|
| 260 |
+
JMN = i
|
| 261 |
+
jsol = 1
|
| 262 |
+
elif b[i] <= 0.01 and a[i] < betmn:
|
| 263 |
+
betmn = a[i]
|
| 264 |
+
JMN = i
|
| 265 |
+
jsol = 0
|
| 266 |
+
# end if
|
| 267 |
+
|
| 268 |
+
if b[i] > betmx:
|
| 269 |
+
betmx = b[i]
|
| 270 |
+
# end if
|
| 271 |
+
# end for
|
| 272 |
+
# Loop over wave types
|
| 273 |
+
for ifunc in [1, 2]:
|
| 274 |
+
if ifunc == 1 and idispl <= 0:
|
| 275 |
+
continue
|
| 276 |
+
# end if
|
| 277 |
+
|
| 278 |
+
if ifunc == 2 and idispr <= 0:
|
| 279 |
+
continue
|
| 280 |
+
# end if
|
| 281 |
+
|
| 282 |
+
# Apply spherical earth transformation for current wave type
|
| 283 |
+
if nsph == 1:
|
| 284 |
+
d, a, b, rho, rtp, dtp, btp = sd2k25.sphere(
|
| 285 |
+
ifunc=ifunc,
|
| 286 |
+
iflag=1,
|
| 287 |
+
d=d,
|
| 288 |
+
a=a,
|
| 289 |
+
b=b,
|
| 290 |
+
rho=rho,
|
| 291 |
+
rtp=rtp,
|
| 292 |
+
dtp=dtp,
|
| 293 |
+
btp=btp,
|
| 294 |
+
mmax=mmax,
|
| 295 |
+
llw=llw,
|
| 296 |
+
twopi=twopi
|
| 297 |
+
)
|
| 298 |
+
# end if
|
| 299 |
+
ddc = ddc0
|
| 300 |
+
sone = sone0
|
| 301 |
+
h = h0
|
| 302 |
+
|
| 303 |
+
if sone < 0.01:
|
| 304 |
+
sone = 2.0
|
| 305 |
+
# end if
|
| 306 |
+
|
| 307 |
+
onea = sone
|
| 308 |
+
|
| 309 |
+
# Get starting value for phase velocity
|
| 310 |
+
if jsol == 0:
|
| 311 |
+
# Water layer
|
| 312 |
+
cc1 = betmn
|
| 313 |
+
else:
|
| 314 |
+
# Solid layer solves halfspace period equation
|
| 315 |
+
cc1 = sd2k25.getsolh(
|
| 316 |
+
a=float(a[JMN]),
|
| 317 |
+
b=float(b[JMN])
|
| 318 |
+
)
|
| 319 |
+
# end if
|
| 320 |
+
# Back off a bit to get a starting value at a lower phase velocity
|
| 321 |
+
cc1 = 0.95 * cc1
|
| 322 |
+
cc1 = 0.90 * cc1
|
| 323 |
+
cc = cc1
|
| 324 |
+
dc = ddc
|
| 325 |
+
dc = abs(dc)
|
| 326 |
+
c1 = cc
|
| 327 |
+
cm = cc
|
| 328 |
+
|
| 329 |
+
# Initialize arrays
|
| 330 |
+
cb[:kmax] = 0.0
|
| 331 |
+
c[:kmax] = 0.0
|
| 332 |
+
|
| 333 |
+
ift = 999
|
| 334 |
+
# Loop over modes
|
| 335 |
+
# Warning: iq is from [1...mode]
|
| 336 |
+
for iq in range(1, mode + 1):
|
| 337 |
+
is_ = 0
|
| 338 |
+
ie = kmax
|
| 339 |
+
itst = ifunc
|
| 340 |
+
|
| 341 |
+
# Loop over periods
|
| 342 |
+
for k in range(is_, ie):
|
| 343 |
+
if k >= ift:
|
| 344 |
+
break
|
| 345 |
+
# end if
|
| 346 |
+
|
| 347 |
+
# Get the period
|
| 348 |
+
t1 = t[k]
|
| 349 |
+
|
| 350 |
+
if igr > 0:
|
| 351 |
+
t1a = t1 / (1.0 + h)
|
| 352 |
+
t1b = t1 / (1.0 - h)
|
| 353 |
+
t1 = t1a
|
| 354 |
+
else:
|
| 355 |
+
t1a = t1
|
| 356 |
+
# end if igr > 0
|
| 357 |
+
|
| 358 |
+
# Get initial phase velocity estimate to begin search
|
| 359 |
+
if k == is_ and iq == 1:
|
| 360 |
+
c1 = cc
|
| 361 |
+
clow = cc
|
| 362 |
+
ifirst = 1
|
| 363 |
+
elif k == is_ and iq > 1:
|
| 364 |
+
c1 = c[is_] + one * dc
|
| 365 |
+
clow = c1
|
| 366 |
+
ifirst = 1
|
| 367 |
+
elif k > is_ and iq > 1:
|
| 368 |
+
ifirst = 0
|
| 369 |
+
clow = c[k] + one * dc
|
| 370 |
+
c1 = c[k-1]
|
| 371 |
+
if c1 < clow:
|
| 372 |
+
c1 = clow
|
| 373 |
+
# end if
|
| 374 |
+
elif k > is_ and iq == 1:
|
| 375 |
+
ifirst = 0
|
| 376 |
+
c1 = c[k-1] - onea * dc
|
| 377 |
+
clow = cm
|
| 378 |
+
else:
|
| 379 |
+
raise ValueError(f"Impossible to get initial phase velocity")
|
| 380 |
+
# end if
|
| 381 |
+
|
| 382 |
+
# Bracket root and refine it
|
| 383 |
+
c1, iret = sd2k25.getsol(
|
| 384 |
+
t1=t1,
|
| 385 |
+
c1=c1,
|
| 386 |
+
clow=clow,
|
| 387 |
+
dc=dc,
|
| 388 |
+
cm=cm,
|
| 389 |
+
betmx=betmx,
|
| 390 |
+
ifunc=ifunc,
|
| 391 |
+
ifirst=ifirst,
|
| 392 |
+
d=d,
|
| 393 |
+
a=a,
|
| 394 |
+
b=b,
|
| 395 |
+
rho=rho,
|
| 396 |
+
rtp=rtp,
|
| 397 |
+
dtp=dtp,
|
| 398 |
+
btp=btp,
|
| 399 |
+
mmax=mmax,
|
| 400 |
+
llw=llw
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
if iret == -1:
|
| 404 |
+
break
|
| 405 |
+
# end if
|
| 406 |
+
|
| 407 |
+
c[k] = c1
|
| 408 |
+
|
| 409 |
+
# For group velocities compute near above solution
|
| 410 |
+
if igr > 0:
|
| 411 |
+
t1 = t1b
|
| 412 |
+
ifirst = 0
|
| 413 |
+
clow = cb[k] + one * dc
|
| 414 |
+
c1 = c1 - onea * dc
|
| 415 |
+
c1, iret = sd2k25.getsol(
|
| 416 |
+
t1=t1,
|
| 417 |
+
c1=c1,
|
| 418 |
+
clow=clow,
|
| 419 |
+
dc=dc,
|
| 420 |
+
cm=cm,
|
| 421 |
+
betmx=betmx,
|
| 422 |
+
ifunc=ifunc,
|
| 423 |
+
ifirst=ifirst,
|
| 424 |
+
d=d,
|
| 425 |
+
a=a,
|
| 426 |
+
b=b,
|
| 427 |
+
rho=rho,
|
| 428 |
+
rtp=rtp,
|
| 429 |
+
dtp=dtp,
|
| 430 |
+
btp=btp,
|
| 431 |
+
mmax=mmax,
|
| 432 |
+
llw=llw
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
# Test if root not found at slightly larger period
|
| 436 |
+
if iret == -1:
|
| 437 |
+
c1 = c[k]
|
| 438 |
+
# end if
|
| 439 |
+
|
| 440 |
+
cb[k] = c1
|
| 441 |
+
else:
|
| 442 |
+
c1 = 0.0
|
| 443 |
+
# end if igr
|
| 444 |
+
|
| 445 |
+
cc0 = c[k]
|
| 446 |
+
cc1 = c1
|
| 447 |
+
|
| 448 |
+
if igr == 0:
|
| 449 |
+
# Output only phase velocity
|
| 450 |
+
cg[k] = cc0
|
| 451 |
+
else:
|
| 452 |
+
# Calculate group velocity and output phase and group velocities
|
| 453 |
+
gvel = (1.0 / t1a - 1.0 / t1b) / (1.0 / (t1a*cc0) - 1.0 / (t1b*cc1))
|
| 454 |
+
cg[k] = gvel
|
| 455 |
+
# end if igr
|
| 456 |
+
# end for k is_..ie
|
| 457 |
+
|
| 458 |
+
# If we broke out of the loop early
|
| 459 |
+
if iverb[ifunc] == 0 and iq <= 1:
|
| 460 |
+
# raise RuntimeError(f"Error, loop finished to early")
|
| 461 |
+
pass
|
| 462 |
+
# end if
|
| 463 |
+
|
| 464 |
+
ift = k
|
| 465 |
+
itst = 0
|
| 466 |
+
|
| 467 |
+
# Set the remaining values to 0
|
| 468 |
+
# for i in range(k, ie):
|
| 469 |
+
# t1a = t[i]
|
| 470 |
+
# cg[i] = 0.0
|
| 471 |
+
# # end for k...ie
|
| 472 |
+
|
| 473 |
+
# end for iq 1...mode
|
| 474 |
+
# end for ifunc
|
| 475 |
+
|
| 476 |
+
return cg, err
|
| 477 |
+
# end def dispsurf2k25
|
surfdisp2k25/dltar.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Compute the period equation for Love and Rayleigh waves.
|
| 3 |
+
|
| 4 |
+
This module contains the dltar, dltar1, and dltar4 functions, which are pure Python
|
| 5 |
+
implementations of the dltar_, dltar1_, and dltar4_ Fortran functions.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# Imports
|
| 9 |
+
import numpy as np
|
| 10 |
+
from typing import List, Union, Tuple
|
| 11 |
+
from .var import var
|
| 12 |
+
from .dnka import dnka
|
| 13 |
+
from .normc import normc
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def dltar1(
|
| 17 |
+
wvno: float,
|
| 18 |
+
omega: float,
|
| 19 |
+
d: np.ndarray,
|
| 20 |
+
a: np.ndarray,
|
| 21 |
+
b: np.ndarray,
|
| 22 |
+
rho: np.ndarray,
|
| 23 |
+
rtp: np.ndarray,
|
| 24 |
+
dtp: np.ndarray,
|
| 25 |
+
btp: np.ndarray,
|
| 26 |
+
mmax: int,
|
| 27 |
+
llw: int,
|
| 28 |
+
twopi: int
|
| 29 |
+
) -> float:
|
| 30 |
+
"""
|
| 31 |
+
Compute the period equation for Love waves.
|
| 32 |
+
|
| 33 |
+
This is a pure Python implementation of the dltar1_ Fortran function.
|
| 34 |
+
|
| 35 |
+
Parameters
|
| 36 |
+
----------
|
| 37 |
+
wvno : float
|
| 38 |
+
Wave number in rad/km.
|
| 39 |
+
omega : float
|
| 40 |
+
Angular frequency in rad/s.
|
| 41 |
+
d : array_like
|
| 42 |
+
Layer thicknesses in km.
|
| 43 |
+
a : array_like
|
| 44 |
+
P-wave velocities in km/s.
|
| 45 |
+
b : array_like
|
| 46 |
+
S-wave velocities in km/s.
|
| 47 |
+
rho : array_like
|
| 48 |
+
Densities in g/cm^3.
|
| 49 |
+
|
| 50 |
+
Returns
|
| 51 |
+
-------
|
| 52 |
+
float
|
| 53 |
+
Value of the period equation for Love waves.
|
| 54 |
+
"""
|
| 55 |
+
# print(">>>>>>>>>>>>>>>>>>>>>>")
|
| 56 |
+
# print("Calling dltar1 ...")
|
| 57 |
+
# print(f"wvno={wvno}")
|
| 58 |
+
# print(f"omega={omega}")
|
| 59 |
+
# print(f"d={d}")
|
| 60 |
+
# print(f"a={a}")
|
| 61 |
+
# print(f"b={b}")
|
| 62 |
+
# print(f"rho={rho}")
|
| 63 |
+
# print(f"rtp={rtp}")
|
| 64 |
+
# print(f"dtp={dtp}")
|
| 65 |
+
# print(f"btp={btp}")
|
| 66 |
+
# print(f"mmax={mmax}")
|
| 67 |
+
# print(f"llw={llw}")
|
| 68 |
+
# print(f"twopi={twopi}")
|
| 69 |
+
# Determine if there's a water layer
|
| 70 |
+
llw = 1
|
| 71 |
+
if b[0] <= 0.0:
|
| 72 |
+
llw = 2
|
| 73 |
+
# end if
|
| 74 |
+
|
| 75 |
+
# Haskell-Thompson love wave formulation from halfspace to surface
|
| 76 |
+
# Using the exact same variable names and calculations as the Fortran code
|
| 77 |
+
beta1 = float(b[mmax-1])
|
| 78 |
+
rho1 = float(rho[mmax-1])
|
| 79 |
+
xkb = omega / beta1
|
| 80 |
+
wvnop = wvno + xkb
|
| 81 |
+
wvnom = abs(wvno - xkb)
|
| 82 |
+
rb = np.sqrt(wvnop * wvnom)
|
| 83 |
+
e1 = rho1 * rb
|
| 84 |
+
e2 = 1.0 / (beta1 * beta1)
|
| 85 |
+
mmm1 = mmax - 1
|
| 86 |
+
|
| 87 |
+
# Loop from bottom layer to top (or water layer)
|
| 88 |
+
for m in range(mmm1, llw-1, -1):
|
| 89 |
+
beta1 = float(b[m])
|
| 90 |
+
rho1 = float(rho[m])
|
| 91 |
+
xmu = rho1 * beta1 * beta1
|
| 92 |
+
xkb = omega / beta1
|
| 93 |
+
wvnop = wvno + xkb
|
| 94 |
+
wvnom = abs(wvno - xkb)
|
| 95 |
+
rb = np.sqrt(wvnop * wvnom)
|
| 96 |
+
q = float(d[m]) * rb
|
| 97 |
+
|
| 98 |
+
# Calculate sinq, y, z, cosq based on the relationship between wvno and xkb
|
| 99 |
+
if wvno < xkb:
|
| 100 |
+
# Propagating case
|
| 101 |
+
sinq = np.sin(q)
|
| 102 |
+
y = sinq / rb
|
| 103 |
+
z = -rb * sinq
|
| 104 |
+
cosq = np.cos(q)
|
| 105 |
+
elif wvno == xkb:
|
| 106 |
+
# Special case: wvno equals xkb
|
| 107 |
+
cosq = 1.0
|
| 108 |
+
y = float(d[m])
|
| 109 |
+
z = 0.0
|
| 110 |
+
else:
|
| 111 |
+
# Evanescent case
|
| 112 |
+
fac = 0.0
|
| 113 |
+
if q < 16:
|
| 114 |
+
fac = np.exp(-2.0 * q)
|
| 115 |
+
cosq = (1.0 + fac) * 0.5
|
| 116 |
+
sinq = (1.0 - fac) * 0.5
|
| 117 |
+
y = sinq / rb
|
| 118 |
+
z = rb * sinq
|
| 119 |
+
# end if wvno
|
| 120 |
+
|
| 121 |
+
# Calculate the new values of e1 and e2
|
| 122 |
+
e10 = e1 * cosq + e2 * xmu * z
|
| 123 |
+
e20 = e1 * y / xmu + e2 * cosq
|
| 124 |
+
|
| 125 |
+
# Normalize to prevent overflow
|
| 126 |
+
xnor = abs(e10)
|
| 127 |
+
ynor = abs(e20)
|
| 128 |
+
|
| 129 |
+
if ynor > xnor:
|
| 130 |
+
xnor = ynor
|
| 131 |
+
# end if
|
| 132 |
+
|
| 133 |
+
if xnor < 1.0e-40:
|
| 134 |
+
xnor = 1.0
|
| 135 |
+
# end if
|
| 136 |
+
|
| 137 |
+
e1 = e10 / xnor
|
| 138 |
+
e2 = e20 / xnor
|
| 139 |
+
# end for m
|
| 140 |
+
|
| 141 |
+
# Return the final value of e1
|
| 142 |
+
# This is the value of the period equation for Love waves
|
| 143 |
+
# The sign is determined by the calculations above and must match the expected values
|
| 144 |
+
# print(f"e1={e1}")
|
| 145 |
+
# print(">>>>>>>>>>>>>>>>>>>>>>")
|
| 146 |
+
return e1
|
| 147 |
+
# end def dltar1
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def dltar4(
|
| 151 |
+
wvno: float,
|
| 152 |
+
omega: float,
|
| 153 |
+
d: np.ndarray,
|
| 154 |
+
a: np.ndarray,
|
| 155 |
+
b: np.ndarray,
|
| 156 |
+
rho: np.ndarray,
|
| 157 |
+
rtp: np.ndarray,
|
| 158 |
+
dtp: np.ndarray,
|
| 159 |
+
btp: np.ndarray,
|
| 160 |
+
mmax: int,
|
| 161 |
+
llw: int,
|
| 162 |
+
twopi: int
|
| 163 |
+
) -> float:
|
| 164 |
+
"""
|
| 165 |
+
Compute the period equation for Rayleigh waves.
|
| 166 |
+
|
| 167 |
+
This is a pure Python implementation of the dltar4_ Fortran function.
|
| 168 |
+
|
| 169 |
+
Parameters
|
| 170 |
+
----------
|
| 171 |
+
wvno : float
|
| 172 |
+
Wave number in rad/km.
|
| 173 |
+
omega : float
|
| 174 |
+
Angular frequency in rad/s.
|
| 175 |
+
d : array_like
|
| 176 |
+
Layer thicknesses in km.
|
| 177 |
+
a : array_like
|
| 178 |
+
P-wave velocities in km/s.
|
| 179 |
+
b : array_like
|
| 180 |
+
S-wave velocities in km/s.
|
| 181 |
+
rho : array_like
|
| 182 |
+
Densities in g/cm^3.
|
| 183 |
+
|
| 184 |
+
Returns
|
| 185 |
+
-------
|
| 186 |
+
float
|
| 187 |
+
Value of the period equation for Rayleigh waves.
|
| 188 |
+
"""
|
| 189 |
+
# print("$$$$$$$$$$$$$$$$$$$$$$$$$$")
|
| 190 |
+
# print("Entering dltar4")
|
| 191 |
+
# Make sure omega is not too small
|
| 192 |
+
if omega < 1.0e-4:
|
| 193 |
+
omega = 1.0e-4
|
| 194 |
+
# end if
|
| 195 |
+
|
| 196 |
+
# Calculate wave number squared
|
| 197 |
+
wvno2 = wvno * wvno
|
| 198 |
+
|
| 199 |
+
# Calculate parameters for the bottom half-space
|
| 200 |
+
xka = omega / a[mmax-1]
|
| 201 |
+
xkb = omega / b[mmax-1]
|
| 202 |
+
wvnop = wvno + xka
|
| 203 |
+
wvnom = abs(wvno - xka)
|
| 204 |
+
ra = np.sqrt(wvnop * wvnom)
|
| 205 |
+
wvnop = wvno + xkb
|
| 206 |
+
wvnom = abs(wvno - xkb)
|
| 207 |
+
rb = np.sqrt(wvnop * wvnom)
|
| 208 |
+
t = b[mmax-1] / omega
|
| 209 |
+
|
| 210 |
+
# E matrix for the bottom half-space
|
| 211 |
+
gammk = 2.0 * t * t
|
| 212 |
+
gam = gammk * wvno2
|
| 213 |
+
gamm1 = gam - 1.0
|
| 214 |
+
rho1 = rho[mmax-1]
|
| 215 |
+
|
| 216 |
+
e = np.zeros(5)
|
| 217 |
+
e[0] = rho1 * rho1 * (gamm1 * gamm1 - gam * gammk * ra * rb)
|
| 218 |
+
e[1] = -rho1 * ra
|
| 219 |
+
e[2] = rho1 * (gamm1 - gammk * ra * rb)
|
| 220 |
+
e[3] = rho1 * rb
|
| 221 |
+
e[4] = wvno2 - ra * rb
|
| 222 |
+
# print(f"wvno2 = {wvno2}")
|
| 223 |
+
# print(f"xka = {xka}")
|
| 224 |
+
# print(f"xkb = {xkb}")
|
| 225 |
+
# print(f"wvnop = {wvnop}")
|
| 226 |
+
# print(f"wvnom = {wvnom}")
|
| 227 |
+
# print(f"ra = {ra}")
|
| 228 |
+
# print(f"rb = {rb}")
|
| 229 |
+
# print(f"t = {t}")
|
| 230 |
+
# print(f"gammk = {gammk}")
|
| 231 |
+
# print(f"gam = {gam}")
|
| 232 |
+
# print(f"gamm1 = {gamm1}")
|
| 233 |
+
# print(f"rho1 = {rho1}")
|
| 234 |
+
# print(f"e = {e}")
|
| 235 |
+
# print("--")
|
| 236 |
+
# Matrix multiplication from bottom layer upward
|
| 237 |
+
mmm1 = mmax - 2
|
| 238 |
+
# print(f"mmm1-1 = {mmm1}")
|
| 239 |
+
# print(f"llw = {llw-1}")
|
| 240 |
+
for m in range(mmm1, llw-2, -1):
|
| 241 |
+
xka = omega / a[m]
|
| 242 |
+
xkb = omega / b[m]
|
| 243 |
+
t = b[m] / omega
|
| 244 |
+
gammk = 2.0 * t * t
|
| 245 |
+
gam = gammk * wvno2
|
| 246 |
+
wvnop = wvno + xka
|
| 247 |
+
wvnom = abs(wvno - xka)
|
| 248 |
+
ra = np.sqrt(wvnop * wvnom)
|
| 249 |
+
wvnop = wvno + xkb
|
| 250 |
+
wvnom = abs(wvno - xkb)
|
| 251 |
+
rb = np.sqrt(wvnop * wvnom)
|
| 252 |
+
dpth = d[m]
|
| 253 |
+
rho1 = rho[m]
|
| 254 |
+
p = ra * dpth
|
| 255 |
+
q = rb * dpth
|
| 256 |
+
beta = b[m]
|
| 257 |
+
# print(f"xka = {xka}")
|
| 258 |
+
# print(f"xkb = {xkb}")
|
| 259 |
+
# print(f"t = {t}")
|
| 260 |
+
# print(f"gammk = {gammk}")
|
| 261 |
+
# print(f"gam = {gam}")
|
| 262 |
+
# print(f"wvnop = {wvnop}")
|
| 263 |
+
# print(f"wvnom = {wvnom}")
|
| 264 |
+
# print(f"ra = {ra}")
|
| 265 |
+
# print(f"rb = {rb}")
|
| 266 |
+
# print(f"dpth = {dpth}")
|
| 267 |
+
# print(f"rho1 = {rho1}")
|
| 268 |
+
# print(f"p = {p}")
|
| 269 |
+
# print(f"q = {q}")
|
| 270 |
+
# Evaluate variables for the compound matrix
|
| 271 |
+
w, cosp, exa, a0, cpcq, cpy, cpz, cqw, cqx, xy, xz, wy, wz = var(
|
| 272 |
+
p=p,
|
| 273 |
+
q=q,
|
| 274 |
+
ra=ra,
|
| 275 |
+
rb=rb,
|
| 276 |
+
wvno=wvno,
|
| 277 |
+
xka=xka,
|
| 278 |
+
xkb=xkb,
|
| 279 |
+
dpth=dpth
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
# Evaluate Dunkin's matrix
|
| 283 |
+
ca = dnka(
|
| 284 |
+
wvno2=wvno2,
|
| 285 |
+
gam=gam,
|
| 286 |
+
gammk=gammk,
|
| 287 |
+
rho=rho1,
|
| 288 |
+
a0=a0,
|
| 289 |
+
cpcq=cpcq,
|
| 290 |
+
cpy=cpy,
|
| 291 |
+
cpz=cpz,
|
| 292 |
+
cqw=cqw,
|
| 293 |
+
cqx=cqx,
|
| 294 |
+
xy=xy,
|
| 295 |
+
xz=xz,
|
| 296 |
+
wy=wy,
|
| 297 |
+
wz=wz
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
# Matrix multiplication
|
| 301 |
+
ee = np.zeros(5)
|
| 302 |
+
for i in range(5):
|
| 303 |
+
cr = 0.0
|
| 304 |
+
for j in range(5):
|
| 305 |
+
cr += e[j] * ca[j, i]
|
| 306 |
+
# end for
|
| 307 |
+
ee[i] = cr
|
| 308 |
+
# end for
|
| 309 |
+
|
| 310 |
+
# Normalize to prevent overflow
|
| 311 |
+
ee, _ = normc(
|
| 312 |
+
ee=ee
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# Update e matrix
|
| 316 |
+
e = ee.copy()
|
| 317 |
+
# end for m
|
| 318 |
+
|
| 319 |
+
# Include water layer if present
|
| 320 |
+
if llw != 1:
|
| 321 |
+
# Water layer at the top
|
| 322 |
+
xka = omega / a[0]
|
| 323 |
+
wvnop = wvno + xka
|
| 324 |
+
wvnom = abs(wvno - xka)
|
| 325 |
+
ra = np.sqrt(wvnop * wvnom)
|
| 326 |
+
dpth = d[0]
|
| 327 |
+
rho1 = rho[0]
|
| 328 |
+
p = ra * dpth
|
| 329 |
+
beta = b[0]
|
| 330 |
+
|
| 331 |
+
# Calculate variables for water layer
|
| 332 |
+
znul = 1.0e-5
|
| 333 |
+
w, cosp, exa, a0, cpcq, cpy, cpz, cqw, cqx, xy, xz, wy, wz = var(
|
| 334 |
+
p=p,
|
| 335 |
+
q=znul,
|
| 336 |
+
ra=ra,
|
| 337 |
+
rb=znul,
|
| 338 |
+
wvno=wvno,
|
| 339 |
+
xka=xka,
|
| 340 |
+
xkb=znul,
|
| 341 |
+
dpth=dpth
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
w0 = -rho1 * w
|
| 345 |
+
# print(f"out = {cosp * e[0] + w0 * e[1]}")
|
| 346 |
+
# print("$$$$$$$$$$$$$$$$$$$$$$$$$$")
|
| 347 |
+
return cosp * e[0] + w0 * e[1]
|
| 348 |
+
else:
|
| 349 |
+
# print(f"out = {e[0]}")
|
| 350 |
+
# print("$$$$$$$$$$$$$$$$$$$$$$$$$$")
|
| 351 |
+
return e[0]
|
| 352 |
+
# end if llw
|
| 353 |
+
|
| 354 |
+
# end def dltar4
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def dltar(
|
| 358 |
+
wvno: float,
|
| 359 |
+
omega: float,
|
| 360 |
+
kk: int,
|
| 361 |
+
d: np.ndarray,
|
| 362 |
+
a: np.ndarray,
|
| 363 |
+
b: np.ndarray,
|
| 364 |
+
rho: np.ndarray,
|
| 365 |
+
rtp: np.ndarray,
|
| 366 |
+
dtp: np.ndarray,
|
| 367 |
+
btp: np.ndarray,
|
| 368 |
+
mmax: int,
|
| 369 |
+
llw: int,
|
| 370 |
+
twopi: int
|
| 371 |
+
) -> float:
|
| 372 |
+
"""
|
| 373 |
+
Compute the period equation for Love or Rayleigh waves.
|
| 374 |
+
|
| 375 |
+
This is a pure Python implementation of the dltar_ Fortran function.
|
| 376 |
+
|
| 377 |
+
Parameters
|
| 378 |
+
----------
|
| 379 |
+
wvno : float
|
| 380 |
+
Wave number in rad/km.
|
| 381 |
+
omega : float
|
| 382 |
+
Angular frequency in rad/s.
|
| 383 |
+
kk : int
|
| 384 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 385 |
+
d : array_like
|
| 386 |
+
Layer thicknesses in km.
|
| 387 |
+
a : array_like
|
| 388 |
+
P-wave velocities in km/s.
|
| 389 |
+
b : array_like
|
| 390 |
+
S-wave velocities in km/s.
|
| 391 |
+
rho : array_like
|
| 392 |
+
Densities in g/cm^3.
|
| 393 |
+
|
| 394 |
+
Returns
|
| 395 |
+
-------
|
| 396 |
+
float
|
| 397 |
+
Value of the period equation for the specified wave type.
|
| 398 |
+
"""
|
| 399 |
+
# print(">>>>>>>>>>>>>>>>>>>>>>")
|
| 400 |
+
# print("Calling dltar ...")
|
| 401 |
+
# print(f"wvno={wvno}")
|
| 402 |
+
# print(f"omega={omega}")
|
| 403 |
+
# print(f"d={d}")
|
| 404 |
+
# print(f"a={a}")
|
| 405 |
+
# print(f"b={b}")
|
| 406 |
+
# print(f"rho={rho}")
|
| 407 |
+
# print(f"rtp={rtp}")
|
| 408 |
+
# print(f"dtp={dtp}")
|
| 409 |
+
# print(f"btp={btp}")
|
| 410 |
+
# print(f"mmax={mmax}")
|
| 411 |
+
# print(f"llw={llw}")
|
| 412 |
+
# print(f"twopi={twopi}")
|
| 413 |
+
# Check if kk is valid
|
| 414 |
+
if kk not in [1, 2]:
|
| 415 |
+
raise ValueError("kk must be 1 (Love waves) or 2 (Rayleigh waves)")
|
| 416 |
+
# end if
|
| 417 |
+
|
| 418 |
+
# Dispatch to the appropriate function
|
| 419 |
+
if kk == 1:
|
| 420 |
+
# print("Love wave period equation")
|
| 421 |
+
# Love wave period equation
|
| 422 |
+
return dltar1(
|
| 423 |
+
wvno=wvno,
|
| 424 |
+
omega=omega,
|
| 425 |
+
d=d,
|
| 426 |
+
a=a,
|
| 427 |
+
b=b,
|
| 428 |
+
rho=rho,
|
| 429 |
+
rtp=rtp,
|
| 430 |
+
dtp=dtp,
|
| 431 |
+
btp=btp,
|
| 432 |
+
mmax=mmax,
|
| 433 |
+
llw=llw,
|
| 434 |
+
twopi=twopi
|
| 435 |
+
)
|
| 436 |
+
else: # kk == 2
|
| 437 |
+
# print("Rayleigh wave period equation")
|
| 438 |
+
# Rayleigh wave period equation
|
| 439 |
+
return dltar4(
|
| 440 |
+
wvno=wvno,
|
| 441 |
+
omega=omega,
|
| 442 |
+
d=d,
|
| 443 |
+
a=a,
|
| 444 |
+
b=b,
|
| 445 |
+
rho=rho,
|
| 446 |
+
rtp=rtp,
|
| 447 |
+
dtp=dtp,
|
| 448 |
+
btp=btp,
|
| 449 |
+
mmax=mmax,
|
| 450 |
+
llw=llw,
|
| 451 |
+
twopi=twopi
|
| 452 |
+
)
|
| 453 |
+
# end if kk
|
| 454 |
+
# end def dltar
|
surfdisp2k25/dnka.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Calculate Dunkin's matrix for layered media.
|
| 3 |
+
|
| 4 |
+
This module contains the dnka function, which is a pure Python implementation
|
| 5 |
+
of the dnka_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# Imports
|
| 9 |
+
import numpy as np
|
| 10 |
+
from typing import List, Union, Tuple
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def dnka(
|
| 14 |
+
wvno2: float,
|
| 15 |
+
gam: float,
|
| 16 |
+
gammk: float,
|
| 17 |
+
rho: float,
|
| 18 |
+
a0: float,
|
| 19 |
+
cpcq: float,
|
| 20 |
+
cpy: float,
|
| 21 |
+
cpz: float,
|
| 22 |
+
cqw: float,
|
| 23 |
+
cqx: float,
|
| 24 |
+
xy: float,
|
| 25 |
+
xz: float,
|
| 26 |
+
wy: float,
|
| 27 |
+
wz: float
|
| 28 |
+
) -> np.ndarray:
|
| 29 |
+
"""
|
| 30 |
+
Calculate Dunkin's matrix for layered media.
|
| 31 |
+
|
| 32 |
+
This is a pure Python implementation of the dnka_ Fortran subroutine.
|
| 33 |
+
|
| 34 |
+
Parameters
|
| 35 |
+
----------
|
| 36 |
+
wvno2 : float
|
| 37 |
+
Square of horizontal wavenumber in (rad/km)^2.
|
| 38 |
+
gam : float
|
| 39 |
+
Parameter gamma.
|
| 40 |
+
gammk : float
|
| 41 |
+
Parameter gamma_k.
|
| 42 |
+
rho : float
|
| 43 |
+
Density in g/cm^3.
|
| 44 |
+
a0 : float
|
| 45 |
+
Parameter a0 from var function.
|
| 46 |
+
cpcq : float
|
| 47 |
+
Parameter cpcq from var function.
|
| 48 |
+
cpy : float
|
| 49 |
+
Parameter cpy from var function.
|
| 50 |
+
cpz : float
|
| 51 |
+
Parameter cpz from var function.
|
| 52 |
+
cqw : float
|
| 53 |
+
Parameter cqw from var function.
|
| 54 |
+
cqx : float
|
| 55 |
+
Parameter cqx from var function.
|
| 56 |
+
xy : float
|
| 57 |
+
Parameter xy from var function.
|
| 58 |
+
xz : float
|
| 59 |
+
Parameter xz from var function.
|
| 60 |
+
wy : float
|
| 61 |
+
Parameter wy from var function.
|
| 62 |
+
wz : float
|
| 63 |
+
Parameter wz from var function.
|
| 64 |
+
|
| 65 |
+
Returns
|
| 66 |
+
-------
|
| 67 |
+
numpy.ndarray
|
| 68 |
+
5x5 Dunkin's matrix used in the calculation of dispersion curves for Rayleigh waves.
|
| 69 |
+
"""
|
| 70 |
+
# Constants
|
| 71 |
+
one = 1.0
|
| 72 |
+
two = 2.0
|
| 73 |
+
|
| 74 |
+
# Calculate intermediate values
|
| 75 |
+
gamm1 = gam - one
|
| 76 |
+
twgm1 = gam + gamm1
|
| 77 |
+
gmgmk = gam * gammk
|
| 78 |
+
gmgm1 = gam * gamm1
|
| 79 |
+
gm1sq = gamm1 * gamm1
|
| 80 |
+
rho2 = rho * rho
|
| 81 |
+
a0pq = a0 - cpcq
|
| 82 |
+
|
| 83 |
+
# Initialize the output matrix
|
| 84 |
+
ca = np.zeros((5, 5))
|
| 85 |
+
|
| 86 |
+
# Calculate Dunkin's matrix elements
|
| 87 |
+
ca[0, 0] = cpcq - two * gmgm1 * a0pq - gmgmk * xz - wvno2 * gm1sq * wy
|
| 88 |
+
ca[0, 1] = (wvno2 * cpy - cqx) / rho
|
| 89 |
+
ca[0, 2] = -(twgm1 * a0pq + gammk * xz + wvno2 * gamm1 * wy) / rho
|
| 90 |
+
ca[0, 3] = (cpz - wvno2 * cqw) / rho
|
| 91 |
+
ca[0, 4] = -(two * wvno2 * a0pq + xz + wvno2 * wvno2 * wy) / rho2
|
| 92 |
+
|
| 93 |
+
ca[1, 0] = (gmgmk * cpz - gm1sq * cqw) * rho
|
| 94 |
+
ca[1, 1] = cpcq
|
| 95 |
+
ca[1, 2] = gammk * cpz - gamm1 * cqw
|
| 96 |
+
ca[1, 3] = -wz
|
| 97 |
+
ca[1, 4] = ca[0, 3]
|
| 98 |
+
|
| 99 |
+
ca[3, 0] = (gm1sq * cpy - gmgmk * cqx) * rho
|
| 100 |
+
ca[3, 1] = -xy
|
| 101 |
+
ca[3, 2] = gamm1 * cpy - gammk * cqx
|
| 102 |
+
ca[3, 3] = ca[1, 1]
|
| 103 |
+
ca[3, 4] = ca[0, 1]
|
| 104 |
+
|
| 105 |
+
ca[4, 0] = -(two * gmgmk * gm1sq * a0pq + gmgmk * gmgmk * xz + gm1sq * gm1sq * wy) * rho2
|
| 106 |
+
ca[4, 1] = ca[3, 0]
|
| 107 |
+
ca[4, 2] = -(gammk * gamm1 * twgm1 * a0pq + gam * gammk * gammk * xz + gamm1 * gm1sq * wy) * rho
|
| 108 |
+
ca[4, 3] = ca[1, 0]
|
| 109 |
+
ca[4, 4] = ca[0, 0]
|
| 110 |
+
|
| 111 |
+
t = -two * wvno2
|
| 112 |
+
ca[2, 0] = t * ca[4, 2]
|
| 113 |
+
ca[2, 1] = t * ca[3, 2]
|
| 114 |
+
ca[2, 2] = a0 + two * (cpcq - ca[0, 0])
|
| 115 |
+
ca[2, 3] = t * ca[1, 2]
|
| 116 |
+
ca[2, 4] = t * ca[0, 2]
|
| 117 |
+
|
| 118 |
+
return ca
|
| 119 |
+
# end def dnka
|
surfdisp2k25/getsol.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python implementation of the getsol subroutine from surfdisp96.
|
| 3 |
+
|
| 4 |
+
This module provides a Python implementation of the getsol subroutine
|
| 5 |
+
from the surfdisp96 Fortran code, which is responsible for bracketing
|
| 6 |
+
dispersion curves and then refining them.
|
| 7 |
+
"""
|
| 8 |
+
import math
|
| 9 |
+
from types import SimpleNamespace
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from typing import Tuple, List, Union
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
import migrate.extensions.surfdisp2k25 as sd2k25 # type: ignore
|
| 16 |
+
except ModuleNotFoundError: # pragma: no cover - fallback for pure Python envs
|
| 17 |
+
from .dltar import dltar as _dltar
|
| 18 |
+
from .nevill import nevill as _nevill
|
| 19 |
+
|
| 20 |
+
sd2k25 = SimpleNamespace(
|
| 21 |
+
dltar=_dltar,
|
| 22 |
+
nevill=_nevill,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
_del1st = 0.0
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def getsol(
|
| 30 |
+
t1: float,
|
| 31 |
+
c1: float,
|
| 32 |
+
clow: float,
|
| 33 |
+
dc: float,
|
| 34 |
+
cm: float,
|
| 35 |
+
betmx: float,
|
| 36 |
+
ifunc: int,
|
| 37 |
+
ifirst: int,
|
| 38 |
+
d: np.ndarray,
|
| 39 |
+
a: np.ndarray,
|
| 40 |
+
b: np.ndarray,
|
| 41 |
+
rho: np.ndarray,
|
| 42 |
+
rtp: np.ndarray,
|
| 43 |
+
dtp: np.ndarray,
|
| 44 |
+
btp: np.ndarray,
|
| 45 |
+
mmax: int,
|
| 46 |
+
llw: int
|
| 47 |
+
) -> Tuple[float, int]:
|
| 48 |
+
"""
|
| 49 |
+
Bracket dispersion curve and then refine it.
|
| 50 |
+
|
| 51 |
+
This is a pure Python implementation of the getsol_ Fortran subroutine.
|
| 52 |
+
|
| 53 |
+
Parameters
|
| 54 |
+
----------
|
| 55 |
+
t1 : float
|
| 56 |
+
Period in seconds.
|
| 57 |
+
c1 : float
|
| 58 |
+
Initial phase velocity estimate in km/s.
|
| 59 |
+
clow : float
|
| 60 |
+
Lower bound for phase velocity in km/s.
|
| 61 |
+
dc : float
|
| 62 |
+
Phase velocity increment for search in km/s.
|
| 63 |
+
cm : float
|
| 64 |
+
Minimum phase velocity to consider in km/s.
|
| 65 |
+
betmx : float
|
| 66 |
+
Maximum phase velocity to consider in km/s.
|
| 67 |
+
ifunc : int
|
| 68 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 69 |
+
ifirst : int
|
| 70 |
+
First call flag: 1 for first call, 0 otherwise.
|
| 71 |
+
d : array_like
|
| 72 |
+
Layer thicknesses in km.
|
| 73 |
+
a : array_like
|
| 74 |
+
P-wave velocities in km/s.
|
| 75 |
+
b : array_like
|
| 76 |
+
S-wave velocities in km/s.
|
| 77 |
+
rho : array_like
|
| 78 |
+
Densities in g/cm^3.
|
| 79 |
+
|
| 80 |
+
Returns
|
| 81 |
+
-------
|
| 82 |
+
tuple
|
| 83 |
+
A tuple containing:
|
| 84 |
+
- c1: float, the refined phase velocity in km/s.
|
| 85 |
+
- iret: int, return code (1 for success, -1 for failure).
|
| 86 |
+
"""
|
| 87 |
+
global _del1st
|
| 88 |
+
|
| 89 |
+
# Check if ifunc is valid
|
| 90 |
+
if ifunc not in [1, 2]:
|
| 91 |
+
raise ValueError("ifunc must be 1 (Love waves) or 2 (Rayleigh waves)")
|
| 92 |
+
# end if
|
| 93 |
+
# Initialize twopi
|
| 94 |
+
twopi = 2.0 * np.pi
|
| 95 |
+
|
| 96 |
+
# Bracket solution
|
| 97 |
+
omega = twopi / t1
|
| 98 |
+
wvno = omega / c1
|
| 99 |
+
|
| 100 |
+
# Bracket solution
|
| 101 |
+
del1 = sd2k25.dltar(
|
| 102 |
+
wvno=wvno,
|
| 103 |
+
omega=omega,
|
| 104 |
+
kk=ifunc,
|
| 105 |
+
d=d,
|
| 106 |
+
a=a,
|
| 107 |
+
b=b,
|
| 108 |
+
rho=rho,
|
| 109 |
+
rtp=rtp,
|
| 110 |
+
dtp=dtp,
|
| 111 |
+
btp=btp,
|
| 112 |
+
mmax=mmax,
|
| 113 |
+
llw=llw,
|
| 114 |
+
twopi=twopi
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
if ifirst == 1:
|
| 118 |
+
_del1st = del1
|
| 119 |
+
# end if
|
| 120 |
+
|
| 121 |
+
plmn = math.copysign(1.0, _del1st) * np.sign(del1)
|
| 122 |
+
|
| 123 |
+
if ifirst == 1:
|
| 124 |
+
idir = 1
|
| 125 |
+
elif ifirst != 1 and plmn >= 0.0:
|
| 126 |
+
idir = 1
|
| 127 |
+
elif ifirst != 1 and plmn < 0.0:
|
| 128 |
+
idir = -1
|
| 129 |
+
else:
|
| 130 |
+
raise ValueError("ifirst must be 1 or 0.")
|
| 131 |
+
# end if ifirst
|
| 132 |
+
|
| 133 |
+
# idir indicates the direction of the search for the true phase velocity from the initial estimate.
|
| 134 |
+
# Usually phase velocity increases with period and we always underestimate, so phase velocity should increase
|
| 135 |
+
# (idir = +1). For reversed dispersion, we should look downward from the present estimate.
|
| 136 |
+
# However, we never go below the floor of clow, when the direction is reversed
|
| 137 |
+
while True:
|
| 138 |
+
if idir > 0:
|
| 139 |
+
c2 = c1 + dc
|
| 140 |
+
else:
|
| 141 |
+
c2 = c1 - dc
|
| 142 |
+
# end if
|
| 143 |
+
|
| 144 |
+
if c2 <= clow:
|
| 145 |
+
idir = 1
|
| 146 |
+
c1 = clow
|
| 147 |
+
continue
|
| 148 |
+
# end if
|
| 149 |
+
|
| 150 |
+
omega = twopi / t1
|
| 151 |
+
wvno = omega / c2
|
| 152 |
+
del2 = sd2k25.dltar(
|
| 153 |
+
wvno=wvno,
|
| 154 |
+
omega=omega,
|
| 155 |
+
kk=ifunc,
|
| 156 |
+
d=d,
|
| 157 |
+
a=a,
|
| 158 |
+
b=b,
|
| 159 |
+
rho=rho,
|
| 160 |
+
rtp=rtp,
|
| 161 |
+
dtp=dtp,
|
| 162 |
+
btp=btp,
|
| 163 |
+
mmax=mmax,
|
| 164 |
+
llw=llw,
|
| 165 |
+
twopi=twopi
|
| 166 |
+
)
|
| 167 |
+
# Changed sign
|
| 168 |
+
if math.copysign(1.0, del1) != math.copysign(1.0, del2):
|
| 169 |
+
# Root bracketed, refine it
|
| 170 |
+
cn = sd2k25.nevill(
|
| 171 |
+
t=t1,
|
| 172 |
+
c1=c1,
|
| 173 |
+
c2=c2,
|
| 174 |
+
del1=del1,
|
| 175 |
+
del2=del2,
|
| 176 |
+
ifunc=ifunc,
|
| 177 |
+
d=d,
|
| 178 |
+
a=a,
|
| 179 |
+
b=b,
|
| 180 |
+
rho=rho,
|
| 181 |
+
rtp=rtp,
|
| 182 |
+
dtp=dtp,
|
| 183 |
+
btp=btp,
|
| 184 |
+
mmax=mmax,
|
| 185 |
+
llw=llw,
|
| 186 |
+
twopi=twopi
|
| 187 |
+
)
|
| 188 |
+
c1 = cn
|
| 189 |
+
|
| 190 |
+
# Clamp the refined phase velocity to betmx when it exceeds betmx
|
| 191 |
+
if c1 > betmx:
|
| 192 |
+
return c1, -1
|
| 193 |
+
# end if
|
| 194 |
+
return c1, 1
|
| 195 |
+
# end if np.sign
|
| 196 |
+
|
| 197 |
+
c1 = c2
|
| 198 |
+
del1 = del2
|
| 199 |
+
|
| 200 |
+
# Check that c1 is in a region of solutions
|
| 201 |
+
if c1 < cm or c1 >= (betmx + dc):
|
| 202 |
+
return c1, -1
|
| 203 |
+
# end if c1
|
| 204 |
+
# end while
|
| 205 |
+
# end def getsol
|
surfdisp2k25/getsolh.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python implementation of the getsolh subroutine from surfdisp96.
|
| 3 |
+
|
| 4 |
+
This module provides a Python implementation of the gtsolh subroutine
|
| 5 |
+
from the surfdisp96 Fortran code, which is responsible for calculating
|
| 6 |
+
a starting solution for phase velocity calculation.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from typing import Union
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def getsolh(
|
| 15 |
+
a: float,
|
| 16 |
+
b: float
|
| 17 |
+
) -> float:
|
| 18 |
+
"""
|
| 19 |
+
Calculate a starting solution for phase velocity.
|
| 20 |
+
|
| 21 |
+
This is a pure Python implementation of the gtsolh_ Fortran subroutine.
|
| 22 |
+
|
| 23 |
+
Parameters
|
| 24 |
+
----------
|
| 25 |
+
a : float
|
| 26 |
+
P-wave velocity in km/s.
|
| 27 |
+
b : float
|
| 28 |
+
S-wave velocity in km/s.
|
| 29 |
+
|
| 30 |
+
Returns
|
| 31 |
+
-------
|
| 32 |
+
float
|
| 33 |
+
The starting solution for phase velocity in km/s.
|
| 34 |
+
|
| 35 |
+
Raises
|
| 36 |
+
------
|
| 37 |
+
TypeError
|
| 38 |
+
If a or b is not a number.
|
| 39 |
+
ValueError
|
| 40 |
+
If a or b is not positive, or if b is greater than or equal to a.
|
| 41 |
+
"""
|
| 42 |
+
# Check for valid velocity values
|
| 43 |
+
if a <= 0:
|
| 44 |
+
raise ValueError("P-wave velocity (a) must be positive")
|
| 45 |
+
# end if
|
| 46 |
+
|
| 47 |
+
if b <= 0:
|
| 48 |
+
raise ValueError("S-wave velocity (b) must be positive")
|
| 49 |
+
# end if
|
| 50 |
+
|
| 51 |
+
if b > a: # Changed from b >= a to b > a to allow the case where a = b
|
| 52 |
+
raise ValueError("S-wave velocity (b) must be less than or equal to P-wave velocity (a)")
|
| 53 |
+
# end if
|
| 54 |
+
|
| 55 |
+
# Starting solution
|
| 56 |
+
c = 0.95 * b
|
| 57 |
+
|
| 58 |
+
# Iterate to refine the solution
|
| 59 |
+
for i in range(5):
|
| 60 |
+
gamma = b / a
|
| 61 |
+
kappa = c / b
|
| 62 |
+
k2 = kappa**2
|
| 63 |
+
gk2 = (gamma * kappa)**2
|
| 64 |
+
fac1 = np.sqrt(1.0 - gk2)
|
| 65 |
+
fac2 = np.sqrt(1.0 - k2)
|
| 66 |
+
fr = (2.0 - k2)**2 - 4.0 * fac1 * fac2
|
| 67 |
+
frp = -4.0 * (2.0 - k2) * kappa + \
|
| 68 |
+
4.0 * fac2 * gamma * gamma * kappa / fac1 + \
|
| 69 |
+
4.0 * fac1 * kappa / fac2
|
| 70 |
+
frp = frp / b
|
| 71 |
+
c = c - fr / frp
|
| 72 |
+
# end if
|
| 73 |
+
|
| 74 |
+
return c
|
| 75 |
+
# end def getsolh
|
surfdisp2k25/half.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Interval halving method for refining root.
|
| 3 |
+
|
| 4 |
+
This module contains the half function, which is a pure Python implementation
|
| 5 |
+
of the half_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from typing import List, Union, Tuple
|
| 10 |
+
from .dltar import dltar
|
| 11 |
+
|
| 12 |
+
def half(
|
| 13 |
+
c1: float,
|
| 14 |
+
c2: float,
|
| 15 |
+
omega: float,
|
| 16 |
+
ifunc: int,
|
| 17 |
+
d: np.ndarray,
|
| 18 |
+
a: np.ndarray,
|
| 19 |
+
b: np.ndarray,
|
| 20 |
+
rho: np.ndarray,
|
| 21 |
+
rtp: np.ndarray,
|
| 22 |
+
dtp: np.ndarray,
|
| 23 |
+
btp: np.ndarray,
|
| 24 |
+
mmax: int,
|
| 25 |
+
llw: int,
|
| 26 |
+
twopi: float
|
| 27 |
+
) -> Tuple[float, float]:
|
| 28 |
+
"""
|
| 29 |
+
Interval halving method for refining root.
|
| 30 |
+
|
| 31 |
+
This is a pure Python implementation of the half_ Fortran subroutine.
|
| 32 |
+
|
| 33 |
+
Parameters
|
| 34 |
+
----------
|
| 35 |
+
c1 : float
|
| 36 |
+
Lower bound of the interval.
|
| 37 |
+
c2 : float
|
| 38 |
+
Upper bound of the interval.
|
| 39 |
+
omega : float
|
| 40 |
+
Angular frequency in rad/s.
|
| 41 |
+
ifunc : int
|
| 42 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 43 |
+
d : array_like
|
| 44 |
+
Layer thicknesses in km.
|
| 45 |
+
a : array_like
|
| 46 |
+
P-wave velocities in km/s.
|
| 47 |
+
b : array_like
|
| 48 |
+
S-wave velocities in km/s.
|
| 49 |
+
rho : array_like
|
| 50 |
+
Densities in g/cm^3.
|
| 51 |
+
|
| 52 |
+
Returns
|
| 53 |
+
-------
|
| 54 |
+
tuple
|
| 55 |
+
A tuple containing:
|
| 56 |
+
- c3: float, the midpoint of the interval (c1 + c2) / 2.
|
| 57 |
+
- del3: float, the value of the period equation at c3.
|
| 58 |
+
"""
|
| 59 |
+
# Check if ifunc is valid
|
| 60 |
+
if ifunc not in [1, 2]:
|
| 61 |
+
raise ValueError("ifunc must be 1 (Love waves) or 2 (Rayleigh waves)")
|
| 62 |
+
# end if
|
| 63 |
+
|
| 64 |
+
# Interval halving method
|
| 65 |
+
c3 = 0.5 * (c1 + c2)
|
| 66 |
+
wvno = omega / c3
|
| 67 |
+
del3 = dltar(
|
| 68 |
+
wvno=wvno,
|
| 69 |
+
omega=omega,
|
| 70 |
+
kk=ifunc,
|
| 71 |
+
d=d,
|
| 72 |
+
a=a,
|
| 73 |
+
b=b,
|
| 74 |
+
rho=rho,
|
| 75 |
+
rtp=rtp,
|
| 76 |
+
dtp=dtp,
|
| 77 |
+
btp=btp,
|
| 78 |
+
mmax=mmax,
|
| 79 |
+
llw=llw,
|
| 80 |
+
twopi=twopi,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
return c3, del3
|
| 84 |
+
# end def half
|
surfdisp2k25/nevill.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hybrid method for refining root once it has been bracketed.
|
| 3 |
+
|
| 4 |
+
This module contains the nevill function, which is a pure Python implementation
|
| 5 |
+
of the nevill_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# Imports
|
| 10 |
+
import numpy as np
|
| 11 |
+
from typing import List, Union, Tuple
|
| 12 |
+
from .half import half
|
| 13 |
+
from .dltar import dltar
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def nevill(
|
| 17 |
+
t: float,
|
| 18 |
+
c1: float,
|
| 19 |
+
c2: float,
|
| 20 |
+
del1: float,
|
| 21 |
+
del2: float,
|
| 22 |
+
ifunc: int,
|
| 23 |
+
d: np.ndarray,
|
| 24 |
+
a: np.ndarray,
|
| 25 |
+
b: np.ndarray,
|
| 26 |
+
rho: np.ndarray,
|
| 27 |
+
rtp: np.ndarray,
|
| 28 |
+
dtp: np.ndarray,
|
| 29 |
+
btp: np.ndarray,
|
| 30 |
+
mmax: int,
|
| 31 |
+
llw: int,
|
| 32 |
+
twopi: float
|
| 33 |
+
) -> float:
|
| 34 |
+
"""
|
| 35 |
+
Hybrid method for refining root once it has been bracketed.
|
| 36 |
+
|
| 37 |
+
This is a pure Python implementation of the nevill_ Fortran subroutine.
|
| 38 |
+
|
| 39 |
+
Parameters
|
| 40 |
+
----------
|
| 41 |
+
t : float
|
| 42 |
+
Period in seconds.
|
| 43 |
+
c1 : float
|
| 44 |
+
Lower bound of the interval.
|
| 45 |
+
c2 : float
|
| 46 |
+
Upper bound of the interval.
|
| 47 |
+
del1 : float
|
| 48 |
+
Value of the period equation at c1.
|
| 49 |
+
del2 : float
|
| 50 |
+
Value of the period equation at c2.
|
| 51 |
+
ifunc : int
|
| 52 |
+
Wave type: 1 for Love waves, 2 for Rayleigh waves.
|
| 53 |
+
d : array_like
|
| 54 |
+
Layer thicknesses in km.
|
| 55 |
+
a : array_like
|
| 56 |
+
P-wave velocities in km/s.
|
| 57 |
+
b : array_like
|
| 58 |
+
S-wave velocities in km/s.
|
| 59 |
+
rho : array_like
|
| 60 |
+
Densities in g/cm^3.
|
| 61 |
+
|
| 62 |
+
Returns
|
| 63 |
+
-------
|
| 64 |
+
float
|
| 65 |
+
The refined phase velocity.
|
| 66 |
+
"""
|
| 67 |
+
# Check if ifunc is valid
|
| 68 |
+
if ifunc not in [1, 2]:
|
| 69 |
+
raise ValueError("ifunc must be 1 (Love waves) or 2 (Rayleigh waves)")
|
| 70 |
+
# end if
|
| 71 |
+
|
| 72 |
+
# Calculate angular frequency
|
| 73 |
+
omega = 2.0 * np.pi / t
|
| 74 |
+
|
| 75 |
+
# Initial guess using interval halving
|
| 76 |
+
c3, del3 = half(
|
| 77 |
+
c1,
|
| 78 |
+
c2,
|
| 79 |
+
omega,
|
| 80 |
+
ifunc,
|
| 81 |
+
d,
|
| 82 |
+
a,
|
| 83 |
+
b,
|
| 84 |
+
rho,
|
| 85 |
+
rtp=rtp,
|
| 86 |
+
dtp=dtp,
|
| 87 |
+
btp=btp,
|
| 88 |
+
mmax=mmax,
|
| 89 |
+
llw=llw,
|
| 90 |
+
twopi=twopi,
|
| 91 |
+
)
|
| 92 |
+
nev = 1
|
| 93 |
+
nctrl = 1
|
| 94 |
+
|
| 95 |
+
# Arrays for Neville iteration
|
| 96 |
+
x = np.zeros(20)
|
| 97 |
+
y = np.zeros(20)
|
| 98 |
+
|
| 99 |
+
# Main loop
|
| 100 |
+
while True:
|
| 101 |
+
nctrl += 1
|
| 102 |
+
if nctrl >= 100:
|
| 103 |
+
break
|
| 104 |
+
# end if
|
| 105 |
+
|
| 106 |
+
# Make sure new estimate is inside the previous values
|
| 107 |
+
# If not, perform interval halving
|
| 108 |
+
if c3 < min(c1, c2) or c3 > max(c1, c2):
|
| 109 |
+
nev = 0
|
| 110 |
+
c3, del3 = half(
|
| 111 |
+
c1=c1,
|
| 112 |
+
c2=c2,
|
| 113 |
+
omega=omega,
|
| 114 |
+
ifunc=ifunc,
|
| 115 |
+
d=d,
|
| 116 |
+
a=a,
|
| 117 |
+
b=b,
|
| 118 |
+
rho=rho,
|
| 119 |
+
rtp=rtp,
|
| 120 |
+
dtp=dtp,
|
| 121 |
+
btp=btp,
|
| 122 |
+
mmax=mmax,
|
| 123 |
+
llw=llw,
|
| 124 |
+
twopi=twopi,
|
| 125 |
+
)
|
| 126 |
+
# end if
|
| 127 |
+
|
| 128 |
+
s13 = del1 - del3
|
| 129 |
+
s32 = del3 - del2
|
| 130 |
+
|
| 131 |
+
# Define new bounds according to the sign of the period equation
|
| 132 |
+
if np.sign(del3) * np.sign(del1) < 0.0:
|
| 133 |
+
c2 = c3
|
| 134 |
+
del2 = del3
|
| 135 |
+
else:
|
| 136 |
+
c1 = c3
|
| 137 |
+
del1 = del3
|
| 138 |
+
# end if
|
| 139 |
+
|
| 140 |
+
# Check for convergence using relative error criteria
|
| 141 |
+
if abs(c1 - c2) <= 1e-6 * c1:
|
| 142 |
+
break
|
| 143 |
+
# end if
|
| 144 |
+
|
| 145 |
+
# If the slopes are not the same between c1, c3 and c3, c2
|
| 146 |
+
# do not use Neville iteration
|
| 147 |
+
if np.sign(s13) != np.sign(s32):
|
| 148 |
+
nev = 0
|
| 149 |
+
# end if
|
| 150 |
+
|
| 151 |
+
# If the period equation differs by more than a factor of 10
|
| 152 |
+
# use interval halving to avoid poor behavior of polynomial fit
|
| 153 |
+
ss1 = abs(del1)
|
| 154 |
+
s1 = 0.01 * ss1
|
| 155 |
+
ss2 = abs(del2)
|
| 156 |
+
s2 = 0.01 * ss2
|
| 157 |
+
|
| 158 |
+
if s1 > ss2 or s2 > ss1 or nev == 0:
|
| 159 |
+
c3, del3 = half(
|
| 160 |
+
c1=c1,
|
| 161 |
+
c2=c2,
|
| 162 |
+
omega=omega,
|
| 163 |
+
ifunc=ifunc,
|
| 164 |
+
d=d,
|
| 165 |
+
a=a,
|
| 166 |
+
b=b,
|
| 167 |
+
rho=rho,
|
| 168 |
+
rtp=rtp,
|
| 169 |
+
dtp=dtp,
|
| 170 |
+
btp=btp,
|
| 171 |
+
mmax=mmax,
|
| 172 |
+
llw=llw,
|
| 173 |
+
twopi=twopi,
|
| 174 |
+
)
|
| 175 |
+
nev = 1
|
| 176 |
+
m = 1
|
| 177 |
+
else:
|
| 178 |
+
if nev == 2:
|
| 179 |
+
x[m] = c3
|
| 180 |
+
y[m] = del3
|
| 181 |
+
else:
|
| 182 |
+
x[0] = c1
|
| 183 |
+
y[0] = del1
|
| 184 |
+
x[1] = c2
|
| 185 |
+
y[1] = del2
|
| 186 |
+
m = 1
|
| 187 |
+
# end if
|
| 188 |
+
|
| 189 |
+
# Perform Neville iteration
|
| 190 |
+
try:
|
| 191 |
+
for kk in range(1, m + 1):
|
| 192 |
+
j = m - kk
|
| 193 |
+
denom = y[m] - y[j]
|
| 194 |
+
if abs(denom) < 1.0e-10 * abs(y[m]):
|
| 195 |
+
raise ValueError("Denominator too small in Neville iteration")
|
| 196 |
+
# end if
|
| 197 |
+
x[j] = (-y[j] * x[j+1] + y[m] * x[j]) / denom
|
| 198 |
+
# end for
|
| 199 |
+
except ValueError:
|
| 200 |
+
# If there's an error in Neville iteration, fall back to interval halving
|
| 201 |
+
c3, del3 = half(
|
| 202 |
+
c1=c1,
|
| 203 |
+
c2=c2,
|
| 204 |
+
omega=omega,
|
| 205 |
+
ifunc=ifunc,
|
| 206 |
+
d=d,
|
| 207 |
+
a=a,
|
| 208 |
+
b=b,
|
| 209 |
+
rho=rho,
|
| 210 |
+
rtp=rtp,
|
| 211 |
+
dtp=dtp,
|
| 212 |
+
btp=btp,
|
| 213 |
+
mmax=mmax,
|
| 214 |
+
llw=llw,
|
| 215 |
+
twopi=twopi,
|
| 216 |
+
)
|
| 217 |
+
nev = 1
|
| 218 |
+
m = 1
|
| 219 |
+
else:
|
| 220 |
+
c3 = x[0]
|
| 221 |
+
wvno = omega / c3
|
| 222 |
+
del3 = dltar(
|
| 223 |
+
wvno=wvno,
|
| 224 |
+
omega=omega,
|
| 225 |
+
kk=ifunc,
|
| 226 |
+
d=d,
|
| 227 |
+
a=a,
|
| 228 |
+
b=b,
|
| 229 |
+
rho=rho,
|
| 230 |
+
rtp=rtp,
|
| 231 |
+
dtp=dtp,
|
| 232 |
+
btp=btp,
|
| 233 |
+
mmax=mmax,
|
| 234 |
+
llw=llw,
|
| 235 |
+
twopi=twopi,
|
| 236 |
+
)
|
| 237 |
+
nev = 2
|
| 238 |
+
m += 1
|
| 239 |
+
if m > 10:
|
| 240 |
+
m = 10
|
| 241 |
+
# end if
|
| 242 |
+
# end try
|
| 243 |
+
# end if s1, s2
|
| 244 |
+
# end while True
|
| 245 |
+
|
| 246 |
+
# Return the refined phase velocity
|
| 247 |
+
return c3
|
| 248 |
+
# end def nevill
|
surfdisp2k25/normc.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Normalize vectors to control over/underflow.
|
| 3 |
+
|
| 4 |
+
This module contains the normc function, which is a pure Python implementation
|
| 5 |
+
of the normc_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from typing import List, Union, Tuple
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def normc(
|
| 13 |
+
ee: np.ndarray
|
| 14 |
+
) -> Tuple[np.ndarray, float]:
|
| 15 |
+
"""
|
| 16 |
+
Normalize vectors to control over/underflow.
|
| 17 |
+
|
| 18 |
+
This is a pure Python implementation of the normc_ Fortran subroutine.
|
| 19 |
+
|
| 20 |
+
Parameters
|
| 21 |
+
----------
|
| 22 |
+
ee : array_like
|
| 23 |
+
Input array of length 5 to be normalized.
|
| 24 |
+
|
| 25 |
+
Returns
|
| 26 |
+
-------
|
| 27 |
+
tuple
|
| 28 |
+
A tuple containing:
|
| 29 |
+
- ee_norm: array_like, the normalized array
|
| 30 |
+
- ex: float, the natural logarithm of the normalization factor
|
| 31 |
+
"""
|
| 32 |
+
# Make sure ee is a numpy array of the right type and shape
|
| 33 |
+
ee = np.asarray(ee, dtype=np.float64)
|
| 34 |
+
if ee.shape != (5,):
|
| 35 |
+
raise ValueError(f"Expected ee to be an array of shape (5,), got {ee.shape}")
|
| 36 |
+
# end if
|
| 37 |
+
|
| 38 |
+
# Create a copy of the input array to avoid modifying the original
|
| 39 |
+
ee_copy = ee.copy()
|
| 40 |
+
|
| 41 |
+
# Initialize the normalization factor
|
| 42 |
+
ex = 0.0
|
| 43 |
+
|
| 44 |
+
# Find the maximum absolute value in the vector
|
| 45 |
+
t1 = 0.0
|
| 46 |
+
for i in range(5):
|
| 47 |
+
if abs(ee_copy[i]) > t1:
|
| 48 |
+
t1 = abs(ee_copy[i])
|
| 49 |
+
# end if
|
| 50 |
+
# end for
|
| 51 |
+
|
| 52 |
+
# If the maximum is very small, set it to 1.0 to avoid division by zero
|
| 53 |
+
if t1 < 1.0e-40:
|
| 54 |
+
t1 = 1.0
|
| 55 |
+
# end if
|
| 56 |
+
|
| 57 |
+
# Normalize the vector by the maximum value
|
| 58 |
+
for i in range(5):
|
| 59 |
+
t2 = ee_copy[i]
|
| 60 |
+
t2 = t2 / t1
|
| 61 |
+
ee_copy[i] = t2
|
| 62 |
+
# end for
|
| 63 |
+
|
| 64 |
+
# Store the normalization factor in exponential form
|
| 65 |
+
ex = np.log(t1)
|
| 66 |
+
|
| 67 |
+
# Return the normalized array and the normalization factor
|
| 68 |
+
return ee_copy, ex
|
| 69 |
+
# end normc
|
| 70 |
+
|
surfdisp2k25/sphere.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transform spherical earth to flat earth.
|
| 3 |
+
|
| 4 |
+
This module contains the sphere function, which is a pure Python implementation
|
| 5 |
+
of the sphere_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
import math
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
from typing import Tuple, List, Union, Optional
|
| 11 |
+
|
| 12 |
+
# Global variable to store dhalf between calls
|
| 13 |
+
_dhalf = 0.0
|
| 14 |
+
|
| 15 |
+
def sphere(
|
| 16 |
+
ifunc: int,
|
| 17 |
+
iflag: int,
|
| 18 |
+
d: np.ndarray,
|
| 19 |
+
a: np.ndarray,
|
| 20 |
+
b: np.ndarray,
|
| 21 |
+
rho: np.ndarray,
|
| 22 |
+
rtp: np.ndarray,
|
| 23 |
+
dtp: np.ndarray,
|
| 24 |
+
btp: np.ndarray,
|
| 25 |
+
mmax: int,
|
| 26 |
+
llw: int,
|
| 27 |
+
twopi: float
|
| 28 |
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
| 29 |
+
"""
|
| 30 |
+
Transform spherical earth to flat earth
|
| 31 |
+
|
| 32 |
+
Schwab, F. A., and L. Knopoff (1972). Fast surface wave and free
|
| 33 |
+
mode computations, in Methods in Computational Physics, Volume 11,
|
| 34 |
+
Seismology: Surface Waves and Earth Oscillations, B. A. Bolt (ed),
|
| 35 |
+
Academic Press, New York
|
| 36 |
+
|
| 37 |
+
Love Wave Equations 44, 45, 41 pp 112-113
|
| 38 |
+
Rayleigh Wave Equations 102, 108, 109 pp 142, 144
|
| 39 |
+
|
| 40 |
+
Revised 28 DEC 2007 to use mid-point, assume linear variation in
|
| 41 |
+
slowness instead of using average velocity for the layer
|
| 42 |
+
Use the Biswas (1972:PAGEOPH 96, 61-74, 1972) density mapping
|
| 43 |
+
|
| 44 |
+
This is a pure Python implementation of the sphere_ Fortran subroutine.
|
| 45 |
+
|
| 46 |
+
Parameters
|
| 47 |
+
----------
|
| 48 |
+
ifunc : int
|
| 49 |
+
Function type: 1 for Love waves, 2 for Rayleigh waves.
|
| 50 |
+
iflag : int
|
| 51 |
+
Flag: 0 for initialization, 1 for subsequent calls.
|
| 52 |
+
d : array_like
|
| 53 |
+
Layer thicknesses in km.
|
| 54 |
+
a : array_like
|
| 55 |
+
P-wave velocities in km/s.
|
| 56 |
+
b : array_like
|
| 57 |
+
S-wave velocities in km/s.
|
| 58 |
+
rho : array_like
|
| 59 |
+
Densities in g/cm^3.
|
| 60 |
+
rtp : array_like, optional
|
| 61 |
+
Original densities from forward transformation, used for backward transformation.
|
| 62 |
+
dtp : array_like, optional
|
| 63 |
+
Original thicknesses from forward transformation, used for backward transformation.
|
| 64 |
+
btp : array_like, optional
|
| 65 |
+
Transformation factors from forward transformation, used for backward transformation.
|
| 66 |
+
|
| 67 |
+
Returns
|
| 68 |
+
-------
|
| 69 |
+
tuple
|
| 70 |
+
A tuple containing:
|
| 71 |
+
- d_new: array_like, transformed layer thicknesses
|
| 72 |
+
- a_new: array_like, transformed P-wave velocities
|
| 73 |
+
- b_new: array_like, transformed S-wave velocities
|
| 74 |
+
- rho_new: array_like, transformed densities
|
| 75 |
+
- rtp: array_like, original densities
|
| 76 |
+
- dtp: array_like, original thicknesses
|
| 77 |
+
- btp: array_like, transformation factors
|
| 78 |
+
"""
|
| 79 |
+
global _dhalf
|
| 80 |
+
NL = 100
|
| 81 |
+
NP = 60
|
| 82 |
+
|
| 83 |
+
# Check size
|
| 84 |
+
assert d.ndim == 1 and d.shape[0] == NL, f"d! {d.shape[0]} != {NL}"
|
| 85 |
+
assert a.ndim == 1 and a.shape[0] == NL, f"a! {a.shape[0]} != {NL}"
|
| 86 |
+
assert b.ndim == 1 and b.shape[0] == NL, f"b! {b.shape[0]} != {NL}"
|
| 87 |
+
assert rho.ndim == 1 and rho.shape[0] == NL, f"rho! {rho.shape[0]} != {NL}"
|
| 88 |
+
assert rtp.ndim == 1 and rtp.shape[0] == NL, f"rtp! {rtp.shape[0]} != {NL}"
|
| 89 |
+
assert dtp.ndim == 1 and dtp.shape[0] == NL, f"dtp! {dtp.shape[0]} != {NL}"
|
| 90 |
+
assert btp.ndim == 1 and btp.shape[0] == NL, f"b! {btp.shape[0]} != {NL}"
|
| 91 |
+
|
| 92 |
+
ar = 6370.0
|
| 93 |
+
dr = 0.0
|
| 94 |
+
r0 = ar
|
| 95 |
+
d[mmax] = 1.0
|
| 96 |
+
|
| 97 |
+
# Check array lengths
|
| 98 |
+
if len(a) != mmax or len(b) != mmax or len(rho) != mmax:
|
| 99 |
+
raise ValueError("d, a, b, and rho must have the same length")
|
| 100 |
+
|
| 101 |
+
# Check if ifunc and iflag are valid
|
| 102 |
+
if ifunc not in [1, 2]:
|
| 103 |
+
raise ValueError("ifunc must be 1, or 2")
|
| 104 |
+
# end if
|
| 105 |
+
|
| 106 |
+
if iflag not in [0, 1]:
|
| 107 |
+
raise ValueError("iflag must be 0 or 1")
|
| 108 |
+
# end if
|
| 109 |
+
|
| 110 |
+
# Forward transformation (spherical to flat)
|
| 111 |
+
if iflag == 0:
|
| 112 |
+
# Save original values
|
| 113 |
+
for i in range(mmax):
|
| 114 |
+
rtp[i] = rho[i]
|
| 115 |
+
dtp[i] = d[i]
|
| 116 |
+
# end for
|
| 117 |
+
|
| 118 |
+
# Compute transformation factors
|
| 119 |
+
for i in range(mmax):
|
| 120 |
+
dr += d[i]
|
| 121 |
+
r1 = ar - dr
|
| 122 |
+
z0 = ar * math.log(ar / r0)
|
| 123 |
+
z1 = ar * math.log(ar / r1)
|
| 124 |
+
d[i] = z1 - z0
|
| 125 |
+
# end for
|
| 126 |
+
|
| 127 |
+
# Save the half-space depth
|
| 128 |
+
_dhalf = d[mmax]
|
| 129 |
+
# Backward transformation (flat to spherical)
|
| 130 |
+
else: # iflag == 1
|
| 131 |
+
d[mmax] = _dhalf
|
| 132 |
+
# Restore original values
|
| 133 |
+
for i in range(mmax):
|
| 134 |
+
if ifunc == 1:
|
| 135 |
+
rho[i] = rtp[i] * btp[i]**(-5)
|
| 136 |
+
elif ifunc == 2:
|
| 137 |
+
rho[i] = rtp[i] * btp[i]**(-2.275)
|
| 138 |
+
else:
|
| 139 |
+
raise ValueError("ifunc must be 1 or 2")
|
| 140 |
+
# end if
|
| 141 |
+
# end for
|
| 142 |
+
# end if
|
| 143 |
+
|
| 144 |
+
d[mmax] = 0.0
|
| 145 |
+
# Return the transformed arrays and the original values
|
| 146 |
+
return d, a, b, rho, rtp, dtp, btp
|
| 147 |
+
# end def sphere
|
surfdisp2k25/var.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate variables for the compound matrix.
|
| 3 |
+
|
| 4 |
+
This module contains the var function, which is a pure Python implementation
|
| 5 |
+
of the var_ Fortran subroutine.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
def var(
|
| 11 |
+
p: float,
|
| 12 |
+
q: float,
|
| 13 |
+
ra: float,
|
| 14 |
+
rb: float,
|
| 15 |
+
wvno: float,
|
| 16 |
+
xka: float,
|
| 17 |
+
xkb: float,
|
| 18 |
+
dpth: float
|
| 19 |
+
):
|
| 20 |
+
"""
|
| 21 |
+
Evaluate variables for the compound matrix.
|
| 22 |
+
|
| 23 |
+
This is a pure Python implementation of the var_ Fortran subroutine.
|
| 24 |
+
|
| 25 |
+
Parameters
|
| 26 |
+
----------
|
| 27 |
+
p : float
|
| 28 |
+
P-wave vertical slowness parameter (ra * depth).
|
| 29 |
+
q : float
|
| 30 |
+
S-wave vertical slowness parameter (rb * depth).
|
| 31 |
+
ra : float
|
| 32 |
+
P-wave vertical slowness.
|
| 33 |
+
rb : float
|
| 34 |
+
S-wave vertical slowness.
|
| 35 |
+
wvno : float
|
| 36 |
+
Horizontal wavenumber in rad/km.
|
| 37 |
+
xka : float
|
| 38 |
+
P-wave wavenumber (omega/alpha).
|
| 39 |
+
xkb : float
|
| 40 |
+
S-wave wavenumber (omega/beta).
|
| 41 |
+
dpth : float
|
| 42 |
+
Layer thickness in km.
|
| 43 |
+
|
| 44 |
+
Returns
|
| 45 |
+
-------
|
| 46 |
+
tuple
|
| 47 |
+
A tuple containing the following variables:
|
| 48 |
+
(w, cosp, exa, a0, cpcq, cpy, cpz, cqw, cqx, xy, xz, wy, wz)
|
| 49 |
+
"""
|
| 50 |
+
# Initialize variables
|
| 51 |
+
exa = 0.0
|
| 52 |
+
a0 = 1.0
|
| 53 |
+
|
| 54 |
+
# Examine P-wave eigenfunctions
|
| 55 |
+
# checking whether c > vp, c = vp or c < vp
|
| 56 |
+
pex = 0.0
|
| 57 |
+
sex = 0.0
|
| 58 |
+
|
| 59 |
+
if wvno < xka:
|
| 60 |
+
sinp = np.sin(p)
|
| 61 |
+
w = sinp / ra
|
| 62 |
+
x = -ra * sinp
|
| 63 |
+
cosp = np.cos(p)
|
| 64 |
+
elif wvno == xka:
|
| 65 |
+
cosp = 1.0
|
| 66 |
+
w = dpth
|
| 67 |
+
x = 0.0
|
| 68 |
+
elif wvno > xka:
|
| 69 |
+
pex = p
|
| 70 |
+
fac = 0.0
|
| 71 |
+
if p < 16:
|
| 72 |
+
fac = np.exp(-2.0 * p)
|
| 73 |
+
# end if
|
| 74 |
+
cosp = (1.0 + fac) * 0.5
|
| 75 |
+
sinp = (1.0 - fac) * 0.5
|
| 76 |
+
w = sinp / ra
|
| 77 |
+
x = ra * sinp
|
| 78 |
+
# end if
|
| 79 |
+
|
| 80 |
+
# Examine S-wave eigenfunctions
|
| 81 |
+
# checking whether c > vs, c = vs, c < vs
|
| 82 |
+
if wvno < xkb:
|
| 83 |
+
sinq = np.sin(q)
|
| 84 |
+
y = sinq / rb
|
| 85 |
+
z = -rb * sinq
|
| 86 |
+
cosq = np.cos(q)
|
| 87 |
+
elif wvno == xkb:
|
| 88 |
+
cosq = 1.0
|
| 89 |
+
y = dpth
|
| 90 |
+
z = 0.0
|
| 91 |
+
elif wvno > xkb:
|
| 92 |
+
sex = q
|
| 93 |
+
fac = 0.0
|
| 94 |
+
if q < 16:
|
| 95 |
+
fac = np.exp(-2.0 * q)
|
| 96 |
+
# end if
|
| 97 |
+
cosq = (1.0 + fac) * 0.5
|
| 98 |
+
sinq = (1.0 - fac) * 0.5
|
| 99 |
+
y = sinq / rb
|
| 100 |
+
z = rb * sinq
|
| 101 |
+
# end if
|
| 102 |
+
|
| 103 |
+
# Form eigenfunction products for use with compound matrices
|
| 104 |
+
exa = pex + sex
|
| 105 |
+
a0 = 0.0
|
| 106 |
+
|
| 107 |
+
if exa < 60.0:
|
| 108 |
+
a0 = np.exp(-exa)
|
| 109 |
+
# end if
|
| 110 |
+
|
| 111 |
+
cpcq = cosp * cosq
|
| 112 |
+
cpy = cosp * y
|
| 113 |
+
cpz = cosp * z
|
| 114 |
+
cqw = cosq * w
|
| 115 |
+
cqx = cosq * x
|
| 116 |
+
xy = x * y
|
| 117 |
+
xz = x * z
|
| 118 |
+
wy = w * y
|
| 119 |
+
wz = w * z
|
| 120 |
+
qmp = sex - pex
|
| 121 |
+
fac = 0.0
|
| 122 |
+
|
| 123 |
+
if qmp > -40.0:
|
| 124 |
+
fac = np.exp(qmp)
|
| 125 |
+
# end if
|
| 126 |
+
|
| 127 |
+
cosq = cosq * fac
|
| 128 |
+
y = fac * y
|
| 129 |
+
z = fac * z
|
| 130 |
+
|
| 131 |
+
# Return all computed values as a tuple
|
| 132 |
+
return w, cosp, exa, a0, cpcq, cpy, cpz, cqw, cqx, xy, xz, wy, wz
|
| 133 |
+
# end def var
|