schattin commited on
Commit
54e8081
·
1 Parent(s): cd69d69

feature(initial): Initial commit

Browse files
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
- import gradio as gr
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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