pyStrata 0.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pystrata/__init__.py +38 -0
- pystrata/data/published_curves.toml +1565 -0
- pystrata/motion.py +367 -0
- pystrata/output.py +832 -0
- pystrata/propagation.py +690 -0
- pystrata/site.py +1422 -0
- pystrata/tools.py +299 -0
- pystrata/variation.py +929 -0
- pystrata-0.5.3.dist-info/METADATA +112 -0
- pystrata-0.5.3.dist-info/RECORD +13 -0
- pystrata-0.5.3.dist-info/WHEEL +4 -0
- pystrata-0.5.3.dist-info/licenses/AUTHORS.md +9 -0
- pystrata-0.5.3.dist-info/licenses/LICENSE +21 -0
pystrata/motion.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# The MIT License (MIT)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2016-2018 Albert Kottke
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
# SOFTWARE.
|
|
23
|
+
"""Classes used to define input motions."""
|
|
24
|
+
import enum
|
|
25
|
+
import re
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pyrvt
|
|
29
|
+
from scipy.constants import g as GRAVITY
|
|
30
|
+
|
|
31
|
+
# Gravity in m/sec²
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WaveField(enum.Enum):
|
|
35
|
+
outcrop = 0
|
|
36
|
+
within = 1
|
|
37
|
+
incoming_only = 2
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Motion(object):
|
|
41
|
+
def __init__(self, freqs=None):
|
|
42
|
+
object.__init__(self)
|
|
43
|
+
|
|
44
|
+
self._freqs = np.array([] if freqs is None else freqs)
|
|
45
|
+
self._pga = None
|
|
46
|
+
self._pgv = None
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def freqs(self):
|
|
50
|
+
return self._freqs
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def angular_freqs(self):
|
|
54
|
+
return 2 * np.pi * self.freqs
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def pgv(self, centimeters=False):
|
|
58
|
+
"""Peak-ground velocity [m/sec]."""
|
|
59
|
+
if self._pgv is None:
|
|
60
|
+
# Compute transfer function -- only over non-zero frequencies
|
|
61
|
+
mask = ~np.isclose(self.angular_freqs, 0)
|
|
62
|
+
tf = np.zeros_like(mask, dtype=complex)
|
|
63
|
+
tf[mask] = 1 / (self.angular_freqs[mask] * 1j)
|
|
64
|
+
if centimeters:
|
|
65
|
+
self._pgv = GRAVITY * 100 * self.calc_peak(tf)
|
|
66
|
+
else:
|
|
67
|
+
self._pgv = GRAVITY * self.calc_peak(tf)
|
|
68
|
+
return self._pgv
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def pga(self):
|
|
72
|
+
if self._pga is None:
|
|
73
|
+
self._pga = self.calc_peak()
|
|
74
|
+
return self._pga
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TimeSeriesMotion(Motion):
|
|
78
|
+
"""Time-series motion for time series based site response analysis."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, filename: str, description: str, time_step: float, accels, fa_length=None):
|
|
81
|
+
"""Initialize the class from specified acceleration values.
|
|
82
|
+
|
|
83
|
+
The *filename* and *description* parameters are only used to help track the motion.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
filename: str
|
|
88
|
+
Source of data
|
|
89
|
+
description: str
|
|
90
|
+
Description to store helpful information
|
|
91
|
+
time_step: float
|
|
92
|
+
Time step of the accleration values
|
|
93
|
+
accels: array_like
|
|
94
|
+
Accelerations in units of *g*
|
|
95
|
+
fa_length: optional int
|
|
96
|
+
Length to use for the Fourier amplitude spectrum. If not provided, will be automatically computed to the next power of 2.
|
|
97
|
+
"""
|
|
98
|
+
Motion.__init__(self)
|
|
99
|
+
|
|
100
|
+
self._filename = filename
|
|
101
|
+
self._description = description
|
|
102
|
+
self._time_step = time_step
|
|
103
|
+
self._accels = np.asarray(accels)
|
|
104
|
+
|
|
105
|
+
self._calc_fourier_spectrum(fa_length)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def accels(self):
|
|
109
|
+
return self._accels
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def filename(self):
|
|
113
|
+
return self._filename
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def description(self):
|
|
117
|
+
return self._description
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def time_step(self):
|
|
121
|
+
return self._time_step
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def times(self):
|
|
125
|
+
return self._time_step * np.arange(self._accels.size)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def freqs(self):
|
|
129
|
+
"""Return the frequencies."""
|
|
130
|
+
if self._freqs is None:
|
|
131
|
+
self._calc_fourier_spectrum()
|
|
132
|
+
|
|
133
|
+
return self._freqs
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def fourier_amps(self):
|
|
137
|
+
"""Return the frequencies."""
|
|
138
|
+
if self._fourier_amps is None:
|
|
139
|
+
self._calc_fourier_spectrum()
|
|
140
|
+
|
|
141
|
+
# Normalize the Fourier amplitude by the time step
|
|
142
|
+
return self.time_step * self._fourier_amps
|
|
143
|
+
|
|
144
|
+
def calc_time_series(self, tf=None):
|
|
145
|
+
if tf is None:
|
|
146
|
+
ts = np.fft.irfft(self.fourier_amps / self.time_step)
|
|
147
|
+
else:
|
|
148
|
+
ts = np.fft.irfft(tf * self.fourier_amps / self.time_step)
|
|
149
|
+
return ts
|
|
150
|
+
|
|
151
|
+
def calc_peak(self, tf=None, **kwargs):
|
|
152
|
+
ts = self.calc_time_series(tf)
|
|
153
|
+
return np.abs(ts).max()
|
|
154
|
+
|
|
155
|
+
def calc_osc_accels(self, osc_freqs, osc_damping=0.05, tf=None):
|
|
156
|
+
"""Compute the pseudo-acceleration spectral response of an oscillator
|
|
157
|
+
with a specific frequency and damping.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
osc_freq : float
|
|
162
|
+
Frequency of the oscillator (Hz).
|
|
163
|
+
osc_damping : float
|
|
164
|
+
Fractional damping of the oscillator (dec). For example, 0.05 for a
|
|
165
|
+
damping ratio of 5%.
|
|
166
|
+
tf : array_like, optional
|
|
167
|
+
Transfer function to be applied to motion prior calculation of the
|
|
168
|
+
oscillator response.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
spec_accels : :class:`numpy.ndarray`
|
|
173
|
+
Peak pseudo-spectral acceleration of the oscillator
|
|
174
|
+
"""
|
|
175
|
+
if tf is None:
|
|
176
|
+
tf = np.ones_like(self.freqs)
|
|
177
|
+
else:
|
|
178
|
+
tf = np.asarray(tf).astype(complex)
|
|
179
|
+
|
|
180
|
+
resp = np.array(
|
|
181
|
+
[self.calc_peak(tf * self._calc_sdof_tf(of, osc_damping)) for of in osc_freqs]
|
|
182
|
+
)
|
|
183
|
+
return resp
|
|
184
|
+
|
|
185
|
+
def _calc_fourier_spectrum(self, fa_length=None):
|
|
186
|
+
"""Compute the Fourier Amplitude Spectrum of the time series."""
|
|
187
|
+
|
|
188
|
+
if fa_length is None:
|
|
189
|
+
# Use the next power of 2 for the length
|
|
190
|
+
n = 1
|
|
191
|
+
while n < self.accels.size:
|
|
192
|
+
n <<= 1
|
|
193
|
+
else:
|
|
194
|
+
n = fa_length
|
|
195
|
+
|
|
196
|
+
self._fourier_amps = np.fft.rfft(self._accels, n)
|
|
197
|
+
|
|
198
|
+
freq_step = 1.0 / (2 * self._time_step * (n / 2))
|
|
199
|
+
self._freqs = freq_step * np.arange(1 + n / 2)
|
|
200
|
+
|
|
201
|
+
def _calc_sdof_tf(self, osc_freq, damping=0.05):
|
|
202
|
+
"""Compute the transfer function for a single-degree-of-freedom
|
|
203
|
+
oscillator.
|
|
204
|
+
|
|
205
|
+
The transfer function computes the pseudo-spectral acceleration.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
osc_freq : float
|
|
210
|
+
natural frequency of the oscillator [Hz]
|
|
211
|
+
damping : float, optional
|
|
212
|
+
damping ratio of the oscillator in decimal. Default value is
|
|
213
|
+
0.05, or 5%.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
tf : :class:`numpy.ndarray`
|
|
218
|
+
Complex-valued transfer function with length equal to `self.freq`.
|
|
219
|
+
"""
|
|
220
|
+
return -(osc_freq**2.0) / (
|
|
221
|
+
np.square(self.freqs) - np.square(osc_freq) - 2.0j * damping * osc_freq * self.freqs
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def load_at2_file(cls, filename, scale=1.0):
|
|
226
|
+
"""Read an AT2 formatted time series.
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
filename: str
|
|
231
|
+
Filename to open.
|
|
232
|
+
scale: float, default: 1.
|
|
233
|
+
Scale factor to apply to the motion.
|
|
234
|
+
"""
|
|
235
|
+
with open(filename) as fp:
|
|
236
|
+
next(fp)
|
|
237
|
+
description = next(fp).strip()
|
|
238
|
+
next(fp)
|
|
239
|
+
parts = next(fp).split()
|
|
240
|
+
time_step = float(parts[1])
|
|
241
|
+
|
|
242
|
+
accels = np.array([float(p) for l in fp for p in l.split()])
|
|
243
|
+
|
|
244
|
+
accels *= scale
|
|
245
|
+
return cls(filename, description, time_step, accels)
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def load_smc_file(cls, filename, scale=1.0):
|
|
249
|
+
"""Read an SMC formatted time series.
|
|
250
|
+
|
|
251
|
+
Format of the time series is provided by:
|
|
252
|
+
https://escweb.wr.usgs.gov/nsmp-data/smcfmt.html
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
filename: str
|
|
257
|
+
Filename to open.
|
|
258
|
+
scale: float, default: 1.
|
|
259
|
+
Scale factor to apply to the motion.
|
|
260
|
+
"""
|
|
261
|
+
from .tools import parse_fixed_width
|
|
262
|
+
|
|
263
|
+
with open(filename) as fp:
|
|
264
|
+
lines = list(fp)
|
|
265
|
+
|
|
266
|
+
# 11 lines of strings
|
|
267
|
+
lines_str = [lines.pop(0) for _ in range(11)]
|
|
268
|
+
|
|
269
|
+
if lines_str[0].strip() != "2 CORRECTED ACCELEROGRAM":
|
|
270
|
+
raise RuntimeWarning("Loading uncorrected SMC file.")
|
|
271
|
+
|
|
272
|
+
m = re.search("station =(.+)component=(.+)", lines_str[5])
|
|
273
|
+
description = "; ".join([g.strip() for g in m.groups()])
|
|
274
|
+
|
|
275
|
+
# 6 lines of (8i10) formatted integers
|
|
276
|
+
values_int = parse_fixed_width(48 * [(10, int)], [lines.pop(0) for _ in range(6)])
|
|
277
|
+
count_comment = values_int[15]
|
|
278
|
+
count = values_int[16]
|
|
279
|
+
|
|
280
|
+
# 10 lines of (5e15.7) formatted floats
|
|
281
|
+
values_float = parse_fixed_width(50 * [(15, float)], [lines.pop(0) for _ in range(10)])
|
|
282
|
+
time_step = 1 / values_float[1]
|
|
283
|
+
|
|
284
|
+
# Skip comments
|
|
285
|
+
lines = lines[count_comment:]
|
|
286
|
+
|
|
287
|
+
accels = np.array(
|
|
288
|
+
parse_fixed_width(
|
|
289
|
+
count
|
|
290
|
+
* [
|
|
291
|
+
(10, float),
|
|
292
|
+
],
|
|
293
|
+
lines,
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
accels *= scale
|
|
297
|
+
|
|
298
|
+
return TimeSeriesMotion(filename, description, time_step, accels)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class RvtMotion(pyrvt.motions.RvtMotion, Motion):
|
|
302
|
+
"""RVT motion based on user specified Fourier amplitude spectrum and duration."""
|
|
303
|
+
|
|
304
|
+
def __init__(self, freqs, fourier_amps, duration=None, peak_calculator=None, calc_kwds=None):
|
|
305
|
+
Motion.__init__(self)
|
|
306
|
+
pyrvt.motions.RvtMotion.__init__(
|
|
307
|
+
self,
|
|
308
|
+
freqs,
|
|
309
|
+
fourier_amps,
|
|
310
|
+
duration=duration,
|
|
311
|
+
peak_calculator=peak_calculator,
|
|
312
|
+
calc_kwds=calc_kwds,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class CompatibleRvtMotion(pyrvt.motions.CompatibleRvtMotion, Motion):
|
|
317
|
+
"""RVT motion based on user specified acceleration response spectrum and duration."""
|
|
318
|
+
|
|
319
|
+
def __init__(
|
|
320
|
+
self,
|
|
321
|
+
osc_freqs,
|
|
322
|
+
osc_accels_target,
|
|
323
|
+
duration=None,
|
|
324
|
+
osc_damping=0.05,
|
|
325
|
+
event_kwds=None,
|
|
326
|
+
window_len=None,
|
|
327
|
+
peak_calculator=None,
|
|
328
|
+
calc_kwds=None,
|
|
329
|
+
):
|
|
330
|
+
Motion.__init__(self)
|
|
331
|
+
pyrvt.motions.CompatibleRvtMotion.__init__(
|
|
332
|
+
self,
|
|
333
|
+
osc_freqs,
|
|
334
|
+
osc_accels_target,
|
|
335
|
+
duration=duration,
|
|
336
|
+
osc_damping=osc_damping,
|
|
337
|
+
event_kwds=event_kwds,
|
|
338
|
+
window_len=window_len,
|
|
339
|
+
peak_calculator=peak_calculator,
|
|
340
|
+
calc_kwds=calc_kwds,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class SourceTheoryRvtMotion(pyrvt.motions.SourceTheoryMotion, Motion):
|
|
345
|
+
"""RVT motion based on seismological point source model and earthquake scenario parameters."""
|
|
346
|
+
|
|
347
|
+
def __init__(
|
|
348
|
+
self,
|
|
349
|
+
magnitude,
|
|
350
|
+
distance,
|
|
351
|
+
region,
|
|
352
|
+
stress_drop=None,
|
|
353
|
+
depth=8,
|
|
354
|
+
peak_calculator=None,
|
|
355
|
+
calc_kwds=None,
|
|
356
|
+
):
|
|
357
|
+
Motion.__init__(self)
|
|
358
|
+
pyrvt.motions.SourceTheoryMotion.__init__(
|
|
359
|
+
self,
|
|
360
|
+
magnitude,
|
|
361
|
+
distance,
|
|
362
|
+
region,
|
|
363
|
+
stress_drop,
|
|
364
|
+
depth,
|
|
365
|
+
peak_calculator=peak_calculator,
|
|
366
|
+
calc_kwds=calc_kwds,
|
|
367
|
+
)
|