ground-motion-tools 0.1.0__tar.gz
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.
- ground_motion_tools-0.1.0/PKG-INFO +24 -0
- ground_motion_tools-0.1.0/README.md +5 -0
- ground_motion_tools-0.1.0/ground_motion_tools/__init__.py +3 -0
- ground_motion_tools-0.1.0/ground_motion_tools/enums/__init__.py +3 -0
- ground_motion_tools-0.1.0/ground_motion_tools/enums/gm_data_enum.py +15 -0
- ground_motion_tools-0.1.0/ground_motion_tools/enums/gm_im_enum.py +42 -0
- ground_motion_tools-0.1.0/ground_motion_tools/enums/gm_spectrum_enum.py +20 -0
- ground_motion_tools-0.1.0/ground_motion_tools/im.py +368 -0
- ground_motion_tools-0.1.0/ground_motion_tools/io.py +123 -0
- ground_motion_tools-0.1.0/ground_motion_tools/process.py +155 -0
- ground_motion_tools-0.1.0/ground_motion_tools/sbs_integration_linear.py +131 -0
- ground_motion_tools-0.1.0/ground_motion_tools/spectrum.py +113 -0
- ground_motion_tools-0.1.0/pyproject.toml +20 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ground-motion-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: RichardoGu
|
|
7
|
+
Author-email: xiaopenggu@qq.com
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: geopy (>=2.4.1,<3.0.0)
|
|
16
|
+
Requires-Dist: numpy (>=2.2.4,<3.0.0)
|
|
17
|
+
Requires-Dist: scipy (>=1.15.2,<2.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Ground-Motion-Tools
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
##
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from enum import Enum, unique
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@unique
|
|
5
|
+
class GMIMEnum(Enum):
|
|
6
|
+
"""
|
|
7
|
+
Type of Ground Motions Intensity Measures.
|
|
8
|
+
|
|
9
|
+
['PGA', 'PGV', 'PGD', 'RMSA', 'RMSV', 'RMSD', 'I_A', 'I_C', 'SED', 'CAV',
|
|
10
|
+
'ASI', 'VSI', 'HI', 'SMA', 'SMV', 'Ia', 'Id', 'Iv',
|
|
11
|
+
'If', 'Sa(T1)', 'Sv(T1)', 'Sd(T1)', 'T70', 'T90', 'FFT']
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
PGA: Peek Ground Acceleration.
|
|
15
|
+
TODO Add More
|
|
16
|
+
"""
|
|
17
|
+
PGA = 0
|
|
18
|
+
PGV = 1
|
|
19
|
+
PGD = 2
|
|
20
|
+
RMSA = 3
|
|
21
|
+
RMSV = 4
|
|
22
|
+
RMSD = 5
|
|
23
|
+
I_SUFFIX_A = 6
|
|
24
|
+
I_SUFFIX_C = 7
|
|
25
|
+
SED = 8
|
|
26
|
+
CAV = 9
|
|
27
|
+
SA_T1 = 10
|
|
28
|
+
SV_T1 = 11
|
|
29
|
+
SD_T1 = 12
|
|
30
|
+
ASI = 13
|
|
31
|
+
VSI = 14
|
|
32
|
+
HI = 15
|
|
33
|
+
SMA = 16
|
|
34
|
+
SMV = 17
|
|
35
|
+
I_A = 18
|
|
36
|
+
I_D = 19
|
|
37
|
+
I_V = 20
|
|
38
|
+
I_F = 21
|
|
39
|
+
MIV = 22
|
|
40
|
+
DI = 23
|
|
41
|
+
T70 = 24
|
|
42
|
+
T90 = 25
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from enum import Enum, unique
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@unique
|
|
5
|
+
class GMSpectrumEnum(Enum):
|
|
6
|
+
"""
|
|
7
|
+
Type of Ground Motion Spectrum.
|
|
8
|
+
Each Ground Motion have five different Spectrum types, see Details: http://www.jdcui.com/?p=713.
|
|
9
|
+
Attributes:
|
|
10
|
+
ACC: Acceleration response spectrum
|
|
11
|
+
VEL: Velocity response spectrum
|
|
12
|
+
DISP: Displacement response spectrum
|
|
13
|
+
PSE_ACC: Pseudo acceleration response spectrum
|
|
14
|
+
PSE_VEL: Pseudo velocity response spectrum
|
|
15
|
+
"""
|
|
16
|
+
ACC = 0
|
|
17
|
+
VEL = 1
|
|
18
|
+
DISP = 2
|
|
19
|
+
PSE_ACC = 3
|
|
20
|
+
PSE_VEL = 4
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# @Time: 2025/3/17 17:15
|
|
3
|
+
# @Author: RichardoGu
|
|
4
|
+
"""
|
|
5
|
+
Intensity measures
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
from process import gm_data_fill
|
|
9
|
+
from sbs_integration_linear import segmented_parsing
|
|
10
|
+
from spectrum import SPECTRUM_PERIOD, get_spectrum
|
|
11
|
+
from enums import GMIMEnum, GMDataEnum
|
|
12
|
+
|
|
13
|
+
IM_ADJUST_DICT = {
|
|
14
|
+
GMIMEnum.PGA.name: { # 按照PGA进行调幅的其余IM变化率
|
|
15
|
+
GMIMEnum.PGA.name: lambda x: x,
|
|
16
|
+
GMIMEnum.PGV.name: lambda x: x,
|
|
17
|
+
GMIMEnum.PGD.name: lambda x: x,
|
|
18
|
+
|
|
19
|
+
GMIMEnum.RMSA.name: lambda x: x,
|
|
20
|
+
GMIMEnum.RMSV.name: lambda x: x,
|
|
21
|
+
GMIMEnum.RMSD.name: lambda x: x,
|
|
22
|
+
|
|
23
|
+
GMIMEnum.I_SUFFIX_A.name: lambda x: x,
|
|
24
|
+
GMIMEnum.I_SUFFIX_C.name: lambda x: x ** (3 / 2),
|
|
25
|
+
|
|
26
|
+
GMIMEnum.SED.name: lambda x: x,
|
|
27
|
+
GMIMEnum.CAV.name: lambda x: x,
|
|
28
|
+
|
|
29
|
+
GMIMEnum.ASI.name: lambda x: x,
|
|
30
|
+
GMIMEnum.VSI.name: lambda x: x,
|
|
31
|
+
GMIMEnum.HI.name: lambda x: x,
|
|
32
|
+
|
|
33
|
+
GMIMEnum.SMA.name: lambda x: x,
|
|
34
|
+
GMIMEnum.SMV.name: lambda x: x,
|
|
35
|
+
|
|
36
|
+
GMIMEnum.I_A.name: lambda x: x,
|
|
37
|
+
GMIMEnum.I_D.name: lambda x: x,
|
|
38
|
+
GMIMEnum.I_V.name: lambda x: x ** (2 / 3),
|
|
39
|
+
GMIMEnum.I_F.name: lambda x: x,
|
|
40
|
+
|
|
41
|
+
GMIMEnum.SA_T1.name: lambda x: x,
|
|
42
|
+
GMIMEnum.SV_T1.name: lambda x: x,
|
|
43
|
+
GMIMEnum.SD_T1.name: lambda x: x
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GMIntensityMeasures:
|
|
49
|
+
def __init__(self, gm_acc_data: np.ndarray, time_step: float):
|
|
50
|
+
self.acc, self.vel, self.disp = gm_data_fill(gm_acc_data, time_step, GMDataEnum.ACC)
|
|
51
|
+
if self.acc.ndim == 1:
|
|
52
|
+
self.acc = np.expand_dims(self.acc, axis=0)
|
|
53
|
+
self.vel = np.expand_dims(self.vel, axis=0)
|
|
54
|
+
self.disp = np.expand_dims(self.disp, axis=0)
|
|
55
|
+
|
|
56
|
+
self.time_step = time_step
|
|
57
|
+
self.batch_size = self.acc.shape[0]
|
|
58
|
+
self.seq_len = self.acc.shape[1]
|
|
59
|
+
self.duration = self.seq_len * self.time_step
|
|
60
|
+
|
|
61
|
+
self.spectrum_acc = None
|
|
62
|
+
self.spectrum_vel = None
|
|
63
|
+
self.spectrum_disp = None
|
|
64
|
+
|
|
65
|
+
self.intensity_measures = {}
|
|
66
|
+
|
|
67
|
+
def _get_spectrum(self):
|
|
68
|
+
self.spectrum_acc, self.spectrum_vel, self.spectrum_disp, _, _ = get_spectrum(
|
|
69
|
+
self.acc, self.time_step, 0.05
|
|
70
|
+
)
|
|
71
|
+
if self.spectrum_acc.ndim == 1:
|
|
72
|
+
self.spectrum_acc = np.expand_dims(self.spectrum_acc, axis=0)
|
|
73
|
+
self.spectrum_vel = np.expand_dims(self.spectrum_vel, axis=0)
|
|
74
|
+
self.spectrum_disp = np.expand_dims(self.spectrum_disp, axis=0)
|
|
75
|
+
|
|
76
|
+
def get_im(
|
|
77
|
+
self,
|
|
78
|
+
im_list: [list, GMIMEnum],
|
|
79
|
+
period: float = 1
|
|
80
|
+
) -> dict[str, np.ndarray[float]]:
|
|
81
|
+
"""
|
|
82
|
+
An external query must call this interface to get intensity measures.
|
|
83
|
+
|
|
84
|
+
The parameter ``im`` is the intensity measures' name, and this func will output the corresponding result.
|
|
85
|
+
|
|
86
|
+
All the input is saved by a dict named ``self.intensity_measures``, and each im will just be calculated once
|
|
87
|
+
when it is first queried.
|
|
88
|
+
|
|
89
|
+
The input im can be both upper and lower, but must be included in :class:`GroundMotionDataIntensityMeasures`.
|
|
90
|
+
Args:
|
|
91
|
+
im_list: Intensity measures' name.
|
|
92
|
+
period: Some intensity measures need param ``period`` to calculate. Default 0.9s
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
A dict ``{str,np.ndarray[float]}`` of ``{im_name, im_value}``
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
if im_list is None or len(im_list) == 0:
|
|
99
|
+
raise ValueError("Parameter 'im_list' can not be None or empty.")
|
|
100
|
+
|
|
101
|
+
result = {}
|
|
102
|
+
if type(im_list) is GMIMEnum:
|
|
103
|
+
im_list = [im_list]
|
|
104
|
+
for im in im_list:
|
|
105
|
+
im_upper = im.name.upper()
|
|
106
|
+
im_lower = im.name.lower()
|
|
107
|
+
try:
|
|
108
|
+
result[im_upper] = self.intensity_measures[im_upper]
|
|
109
|
+
except KeyError:
|
|
110
|
+
self.intensity_measures[im_upper] = eval("self.im_" + im_lower)(period=period)
|
|
111
|
+
result[im_upper] = self.intensity_measures[im_upper]
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def im_pga(self, **kwargs):
|
|
115
|
+
"""
|
|
116
|
+
PGA
|
|
117
|
+
.. math:: |max(a(t))|
|
|
118
|
+
"""
|
|
119
|
+
return np.abs(self.acc).max(1)
|
|
120
|
+
|
|
121
|
+
def im_pgv(self, **kwargs):
|
|
122
|
+
"""
|
|
123
|
+
PGV
|
|
124
|
+
.. math:: |max(v(t))|
|
|
125
|
+
"""
|
|
126
|
+
return np.abs(self.vel).max(1)
|
|
127
|
+
|
|
128
|
+
def im_pgd(self, **kwargs):
|
|
129
|
+
"""
|
|
130
|
+
PGD
|
|
131
|
+
.. math:: |max(d(t))|
|
|
132
|
+
"""
|
|
133
|
+
return np.abs(self.disp).max(1)
|
|
134
|
+
|
|
135
|
+
def im_rmsa(self, **kwargs):
|
|
136
|
+
"""
|
|
137
|
+
Arms
|
|
138
|
+
.. math:: \\sqrt{\\frac{1}{t_{tot}} \\int_{0}^{tot}a(t)^2dt}
|
|
139
|
+
"""
|
|
140
|
+
return ((self.acc ** 2).sum(1) * self.time_step / self.duration) ** 0.5
|
|
141
|
+
|
|
142
|
+
def im_rmsv(self, **kwargs):
|
|
143
|
+
"""
|
|
144
|
+
Vrms
|
|
145
|
+
.. math:: \\sqrt{\\frac{1}{t_{tot}} \\int_{0}^{tot}v(t)^2dt}
|
|
146
|
+
"""
|
|
147
|
+
return ((self.vel ** 2).sum(1) * self.time_step / self.duration) ** 0.5
|
|
148
|
+
|
|
149
|
+
def im_rmsd(self, **kwargs):
|
|
150
|
+
"""
|
|
151
|
+
Drms
|
|
152
|
+
.. math:: \\sqrt{\\frac{1}{t_{tot}} \\int_{0}^{tot}d(t)^2dt}
|
|
153
|
+
"""
|
|
154
|
+
return ((self.disp ** 2).sum(1) * self.time_step / self.duration) ** 0.5
|
|
155
|
+
|
|
156
|
+
def im_i_suffix_a(self, **kwargs):
|
|
157
|
+
"""
|
|
158
|
+
IA
|
|
159
|
+
.. math:: \\frac{\\pi}{2g}\\int^{t_{tot}}_{0}{a(t)^2dt}
|
|
160
|
+
"""
|
|
161
|
+
return (self.acc ** 2).sum(1) * self.time_step * np.pi / (2 * 9.8)
|
|
162
|
+
|
|
163
|
+
def im_i_suffix_c(self, **kwargs):
|
|
164
|
+
"""
|
|
165
|
+
IC
|
|
166
|
+
.. math:: (Arms)^{3/2}\\sqrt{t_{tot}}
|
|
167
|
+
"""
|
|
168
|
+
return self.get_im(GMIMEnum.RMSA)[GMIMEnum.RMSA.name.upper()] ** 1.5 * (self.duration ** 0.5)
|
|
169
|
+
|
|
170
|
+
def im_sed(self, **kwargs):
|
|
171
|
+
"""
|
|
172
|
+
SED
|
|
173
|
+
.. math:: \\int_{0}^{tot}{v(t)^2}dt
|
|
174
|
+
"""
|
|
175
|
+
return (self.vel ** 2).sum(1) * self.time_step
|
|
176
|
+
|
|
177
|
+
def im_cav(self, **kwargs):
|
|
178
|
+
"""
|
|
179
|
+
CAV
|
|
180
|
+
.. math:: \\int_{0}^{tot}{|a(t)|}dt
|
|
181
|
+
"""
|
|
182
|
+
return np.abs(self.acc).sum(1) * self.time_step
|
|
183
|
+
|
|
184
|
+
def im_sa_t1(self, **kwargs):
|
|
185
|
+
"""
|
|
186
|
+
Sa(T1)
|
|
187
|
+
Spectrum acceleration at the first natural period of vibration.
|
|
188
|
+
"""
|
|
189
|
+
acc, vel, disp = segmented_parsing(mass=1,
|
|
190
|
+
stiffness=((2 * np.pi) / kwargs["period"]) ** 2,
|
|
191
|
+
damping_ratio=0.05,
|
|
192
|
+
load=self.acc, time_step=self.time_step)
|
|
193
|
+
self.intensity_measures[GMIMEnum.SV_T1.name.upper()] = np.abs(vel).max(1)
|
|
194
|
+
self.intensity_measures[GMIMEnum.SD_T1.name.upper()] = np.abs(disp).max(1)
|
|
195
|
+
return np.abs(acc).max(1)
|
|
196
|
+
|
|
197
|
+
def im_sv_t1(self, **kwargs):
|
|
198
|
+
"""
|
|
199
|
+
Sv(T1)
|
|
200
|
+
Spectrum velocity at the first natural period of vibration.
|
|
201
|
+
"""
|
|
202
|
+
acc, vel, disp = segmented_parsing(mass=1,
|
|
203
|
+
stiffness=((2 * np.pi) / kwargs["period"]) ** 2,
|
|
204
|
+
damping_ratio=0.05,
|
|
205
|
+
load=self.acc, time_step=self.time_step)
|
|
206
|
+
self.intensity_measures[GMIMEnum.SA_T1.name.upper()] = np.abs(acc).max(1)
|
|
207
|
+
self.intensity_measures[GMIMEnum.SD_T1.name.upper()] = np.abs(disp).max(1)
|
|
208
|
+
return np.abs(vel).max(1)
|
|
209
|
+
|
|
210
|
+
def im_sd_t1(self, **kwargs):
|
|
211
|
+
"""
|
|
212
|
+
Sd(T1)
|
|
213
|
+
Spectrum displacement at the first natural period of vibration.
|
|
214
|
+
"""
|
|
215
|
+
acc, vel, disp = segmented_parsing(mass=1,
|
|
216
|
+
stiffness=((2 * np.pi) / kwargs["period"]) ** 2,
|
|
217
|
+
damping_ratio=0.05,
|
|
218
|
+
load=self.acc, time_step=self.time_step)
|
|
219
|
+
self.intensity_measures[GMIMEnum.SA_T1.name.upper()] = np.abs(acc).max(1)
|
|
220
|
+
self.intensity_measures[GMIMEnum.SV_T1.name.upper()] = np.abs(vel).max(1)
|
|
221
|
+
return np.abs(disp).max(1)
|
|
222
|
+
|
|
223
|
+
def im_asi(self, **kwargs):
|
|
224
|
+
"""
|
|
225
|
+
ASI
|
|
226
|
+
.. math:: \\int_{0.1}^{0.5}{Sa(\\xi = 0.05, t) dt}
|
|
227
|
+
"""
|
|
228
|
+
if self.spectrum_acc is None:
|
|
229
|
+
self._get_spectrum()
|
|
230
|
+
|
|
231
|
+
result = np.zeros(self.batch_size)
|
|
232
|
+
for i in range(len(SPECTRUM_PERIOD)):
|
|
233
|
+
if SPECTRUM_PERIOD[i] < 0.1:
|
|
234
|
+
continue
|
|
235
|
+
if SPECTRUM_PERIOD[i] > 0.5:
|
|
236
|
+
break
|
|
237
|
+
result += self.spectrum_acc[:, i] * (SPECTRUM_PERIOD[i] - SPECTRUM_PERIOD[i - 1])
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
def im_vsi(self, **kwargs):
|
|
241
|
+
"""
|
|
242
|
+
VSI
|
|
243
|
+
.. math:: \\int_{0.1}^{2.5}{Sv(\\xi = 0.05, t) dt}
|
|
244
|
+
"""
|
|
245
|
+
if self.spectrum_vel is None:
|
|
246
|
+
self._get_spectrum()
|
|
247
|
+
result = np.zeros(self.batch_size)
|
|
248
|
+
for i in range(len(SPECTRUM_PERIOD)):
|
|
249
|
+
if SPECTRUM_PERIOD[i] < 0.1:
|
|
250
|
+
continue
|
|
251
|
+
if SPECTRUM_PERIOD[i] > 2.5:
|
|
252
|
+
break
|
|
253
|
+
result += self.spectrum_vel[:, i] * (SPECTRUM_PERIOD[i] - SPECTRUM_PERIOD[i - 1])
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
def im_hi(self, **kwargs):
|
|
257
|
+
"""
|
|
258
|
+
HI
|
|
259
|
+
.. math:: \\int_{0.1}^{2.5}{PSv(\\xi = 0.05, t) dt}
|
|
260
|
+
"""
|
|
261
|
+
if self.spectrum_disp is None:
|
|
262
|
+
self._get_spectrum()
|
|
263
|
+
result = np.zeros(self.batch_size)
|
|
264
|
+
for i in range(len(SPECTRUM_PERIOD)):
|
|
265
|
+
if SPECTRUM_PERIOD[i] < 0.1:
|
|
266
|
+
continue
|
|
267
|
+
if SPECTRUM_PERIOD[i] > 2.5:
|
|
268
|
+
break
|
|
269
|
+
result += self.spectrum_disp[:, i] * (SPECTRUM_PERIOD[i] - SPECTRUM_PERIOD[i - 1])
|
|
270
|
+
return result
|
|
271
|
+
|
|
272
|
+
def im_sma(self, **kwargs):
|
|
273
|
+
"""
|
|
274
|
+
The third peek in acceleration time history.
|
|
275
|
+
"""
|
|
276
|
+
return np.sort(np.abs(self.acc), axis=1)[:, -3]
|
|
277
|
+
|
|
278
|
+
def im_smv(self, **kwargs):
|
|
279
|
+
"""
|
|
280
|
+
The third peek in velocity time history.
|
|
281
|
+
"""
|
|
282
|
+
return np.sort(np.abs(self.vel), axis=1)[:, -3]
|
|
283
|
+
|
|
284
|
+
def im_i_a(self, **kwargs):
|
|
285
|
+
"""
|
|
286
|
+
Ia
|
|
287
|
+
.. math:: PGA{\\dot}t^{1/3}_{tot}
|
|
288
|
+
"""
|
|
289
|
+
return self.get_im(GMIMEnum.PGA)[GMIMEnum.PGA.name.upper()] * self.duration ** (1 / 3)
|
|
290
|
+
|
|
291
|
+
def im_i_d(self, **kwargs):
|
|
292
|
+
"""
|
|
293
|
+
Id
|
|
294
|
+
.. math:: PGD{\\dot}t^{1/3}_{tot}
|
|
295
|
+
"""
|
|
296
|
+
return self.get_im(GMIMEnum.PGD)[GMIMEnum.PGD.name.upper()] * self.duration ** (1 / 3)
|
|
297
|
+
|
|
298
|
+
def im_i_v(self, **kwargs):
|
|
299
|
+
"""
|
|
300
|
+
Iv
|
|
301
|
+
.. math:: PGV^{2/3}{\\dot}t^{1/3}_{tot}
|
|
302
|
+
"""
|
|
303
|
+
return self.get_im(GMIMEnum.PGV)[GMIMEnum.PGV.name.upper()] ** (2 / 3) * self.duration ** (1 / 3)
|
|
304
|
+
|
|
305
|
+
def im_i_f(self, **kwargs):
|
|
306
|
+
"""
|
|
307
|
+
IF
|
|
308
|
+
.. math:: PGV{\\dot}t^{1/4}_{tot}
|
|
309
|
+
"""
|
|
310
|
+
return self.get_im(GMIMEnum.PGV)[GMIMEnum.PGV.name.upper()] * self.duration ** (1 / 4)
|
|
311
|
+
|
|
312
|
+
def im_miv(self, **kwargs):
|
|
313
|
+
"""
|
|
314
|
+
MIV
|
|
315
|
+
"""
|
|
316
|
+
# TODO ADD
|
|
317
|
+
return np.zeros(self.batch_size)
|
|
318
|
+
|
|
319
|
+
def im_di(self, **kwargs):
|
|
320
|
+
"""
|
|
321
|
+
DI
|
|
322
|
+
"""
|
|
323
|
+
# TODO ADD
|
|
324
|
+
return np.zeros(self.batch_size)
|
|
325
|
+
|
|
326
|
+
def im_t70(self, **kwargs):
|
|
327
|
+
"""
|
|
328
|
+
T0.75-T0.05
|
|
329
|
+
"""
|
|
330
|
+
# TODO ADD
|
|
331
|
+
return np.zeros(self.batch_size)
|
|
332
|
+
|
|
333
|
+
def im_t90(self, **kwargs):
|
|
334
|
+
"""
|
|
335
|
+
T0.95-T0.05
|
|
336
|
+
"""
|
|
337
|
+
# TODO ADD
|
|
338
|
+
return np.zeros(self.batch_size)
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def im_adjust(im_data: dict, base_im: GMIMEnum, target_value: float):
|
|
342
|
+
"""
|
|
343
|
+
对IM指标进行调幅
|
|
344
|
+
Args:
|
|
345
|
+
im_data: 需要进行调幅的IM指标
|
|
346
|
+
base_im: 以哪个IM为基准调幅
|
|
347
|
+
target_value: 要调幅的值
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
# 初始化一个新数组,不改变原有的数据
|
|
353
|
+
adjusted_im_data = {}
|
|
354
|
+
|
|
355
|
+
# 首先将base_im调幅到target_value,并记录调幅参数
|
|
356
|
+
base_im_data = im_data[base_im.name.upper()]
|
|
357
|
+
base_adjust_value = target_value / base_im_data
|
|
358
|
+
adjusted_im_data[base_im.name.upper()] = im_data[base_im.name.upper()] * base_adjust_value
|
|
359
|
+
|
|
360
|
+
# 逐项计算
|
|
361
|
+
for im_key in im_data.keys():
|
|
362
|
+
if im_key not in IM_ADJUST_DICT[base_im.name].keys():
|
|
363
|
+
raise KeyError(f"调幅系数中并未收录IM指标:{im_key}")
|
|
364
|
+
adjust_func = IM_ADJUST_DICT[base_im.name][GMIMEnum[im_key].name] # 获取该IM的调幅系数
|
|
365
|
+
adjust_value = np.array([adjust_func(bav_i) for bav_i in base_adjust_value]) # 计算调幅矩阵
|
|
366
|
+
adjusted_im_data[im_key] = im_data[im_key] * adjust_value # 原始值与调幅矩阵逐项相乘
|
|
367
|
+
|
|
368
|
+
return adjusted_im_data
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# @Time: 2025/3/17 16:10
|
|
3
|
+
# @Author: RichardoGu
|
|
4
|
+
"""
|
|
5
|
+
This file is mainly used to read or write ground motion.
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
TIME_RE = r"\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}"
|
|
11
|
+
NUMBER_RE = r"[+-]?(\d+([.]\d*)?([eE][+-]?\d+)?|[.]\d+([eE][+-]?\d+)?)"
|
|
12
|
+
TIME_FORMAT = "%Y/%m/%d %H:%M:%S"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_from_kik(file_path: str) -> (
|
|
16
|
+
np.ndarray[np.float64], float
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Read ground motion data from KIK format.
|
|
20
|
+
Args:
|
|
21
|
+
file_path: File path
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
gm_data, time_step
|
|
25
|
+
"""
|
|
26
|
+
gm_data = None
|
|
27
|
+
with open(file_path, 'r') as f:
|
|
28
|
+
lines = f.readlines()
|
|
29
|
+
for line in lines:
|
|
30
|
+
if re.match('^Memo.', line):
|
|
31
|
+
# if line is begin of "Memo.", it means all the params are read.
|
|
32
|
+
# The following data is time-history data.
|
|
33
|
+
idx = lines.index(line) + 1
|
|
34
|
+
gm_data = []
|
|
35
|
+
for i in range(idx, len(lines)):
|
|
36
|
+
temp = lines[i].split()
|
|
37
|
+
for j in temp:
|
|
38
|
+
gm_data.append(eval(j) * 0.01) # Times 0.01 to convert gal(cm/s^2) to SI(m/s^2)
|
|
39
|
+
break
|
|
40
|
+
elif re.match("^Scale Factor.", line):
|
|
41
|
+
match_result = re.search(r'(\d+)\D*(\d+)/?$', line)
|
|
42
|
+
scale_factor = eval(match_result.group(1)) / eval(match_result.group(2))
|
|
43
|
+
elif re.match("^Sampling Freq.", line):
|
|
44
|
+
time_step = 1 / eval(re.search(NUMBER_RE, line).group())
|
|
45
|
+
|
|
46
|
+
if gm_data is not None and scale_factor:
|
|
47
|
+
gm_data = np.array(gm_data, dtype=np.float64)
|
|
48
|
+
gm_data = (gm_data - gm_data.mean()) * scale_factor
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError("Read ground motion data error. Parameter 'scale_factor' or gm_data may be None.")
|
|
51
|
+
return gm_data, time_step
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_from_peer(file_path: str) -> (
|
|
55
|
+
np.ndarray[np.float64], float
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Read ground motion data from PEER format.
|
|
59
|
+
Args:
|
|
60
|
+
file_path:
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
gm_data, time_step
|
|
64
|
+
"""
|
|
65
|
+
with open(file_path, 'r') as fp:
|
|
66
|
+
lines = fp.readlines()
|
|
67
|
+
time_step = eval(re.findall(r"\.[0-9]*", lines[3])[0])
|
|
68
|
+
gm_data = []
|
|
69
|
+
for i in range(4, len(lines)):
|
|
70
|
+
temp = lines[i].split()
|
|
71
|
+
for j in temp:
|
|
72
|
+
gm_data.append(eval(j) * 9.8) # Times 9.8 to convert G(9.8m/s^2) to m/s^2
|
|
73
|
+
return np.array(gm_data, dtype=np.float64), time_step
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_from_single(file_path: str, start_line: int = 1,
|
|
77
|
+
end_line: int = None, time_step: [int, float] = 0) -> (
|
|
78
|
+
np.ndarray[np.float64], float
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Reading seismic wave data from a single column file
|
|
82
|
+
The default single column file format is: the first row is the sampling interval,
|
|
83
|
+
the second row to the end of the file is the seismic wave data.
|
|
84
|
+
Args:
|
|
85
|
+
file_path: File path
|
|
86
|
+
start_line: Number of rows of ground motion. Default is the second row.
|
|
87
|
+
end_line: Number of end rows of ground motion. Default is None
|
|
88
|
+
time_step:
|
|
89
|
+
Ground motion Sampling Interval Directly indicates the sampling interval.
|
|
90
|
+
if it is a floating point number,
|
|
91
|
+
otherwise it indicates the number of rows where the sampling interval is located.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
gm_data, time_step
|
|
95
|
+
"""
|
|
96
|
+
with open(file_path, 'r') as fp:
|
|
97
|
+
lines = fp.readlines()
|
|
98
|
+
wave_data = [float(line) for line in lines[start_line:end_line]]
|
|
99
|
+
if type(time_step) is float:
|
|
100
|
+
pass
|
|
101
|
+
elif type(time_step) is int:
|
|
102
|
+
time_step = float(lines[time_step].split(" ")[-1])
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(f"Type of parameter 'time_step' need to be float or int. But got {type(time_step)}.")
|
|
105
|
+
return np.array(wave_data, dtype=np.float64), time_step
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def save_to_single(file_path: str, gm_data: np.ndarray[np.float64], time_step: float = None) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Save ground motion to single file.
|
|
111
|
+
Args:
|
|
112
|
+
gm_data: Data of ground motion.
|
|
113
|
+
time_step: Time step of ground motion.
|
|
114
|
+
file_path: Path to save.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
None
|
|
118
|
+
"""
|
|
119
|
+
with open(file_path, 'w') as fp:
|
|
120
|
+
if time_step is not None:
|
|
121
|
+
fp.write(f"Time Step: {time_step}\n")
|
|
122
|
+
for data in gm_data:
|
|
123
|
+
fp.write(f"{data}\n")
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# @Time: 2025/3/17 16:49
|
|
3
|
+
# @Author: RichardoGu
|
|
4
|
+
"""
|
|
5
|
+
Some utils for processing ground motion.
|
|
6
|
+
"""
|
|
7
|
+
import numpy as np
|
|
8
|
+
from scipy import signal
|
|
9
|
+
from enums import GMDataEnum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def gm_data_fill(gm_data: np.ndarray,
|
|
13
|
+
time_step: float = 0.02,
|
|
14
|
+
wave_type: GMDataEnum = GMDataEnum.ACC) -> (
|
|
15
|
+
np.ndarray[np.float64], np.ndarray[np.float64], np.ndarray[np.float64]
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Fit wave by input.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
gm_data:
|
|
22
|
+
wave_type:
|
|
23
|
+
time_step:
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
None
|
|
27
|
+
"""
|
|
28
|
+
if gm_data.ndim == 1:
|
|
29
|
+
# All the wave_array hereafter should have 2 dims as [batch_size, seq_len]
|
|
30
|
+
gm_data = np.expand_dims(gm_data, axis=0)
|
|
31
|
+
if wave_type.value == GMDataEnum.ACC.value:
|
|
32
|
+
acc = gm_data
|
|
33
|
+
vel = np.cumsum(acc, axis=1) * time_step
|
|
34
|
+
disp = np.cumsum(vel, axis=1) * time_step
|
|
35
|
+
elif wave_type.value == GMDataEnum.VEL.value:
|
|
36
|
+
vel = gm_data
|
|
37
|
+
disp = np.cumsum(vel, axis=1) * time_step
|
|
38
|
+
acc = np.gradient(vel, time_step, axis=1)
|
|
39
|
+
elif wave_type.value == GMDataEnum.DISP.value:
|
|
40
|
+
disp = gm_data
|
|
41
|
+
vel = np.gradient(disp, time_step, axis=1)
|
|
42
|
+
acc = np.gradient(vel, time_step, axis=1)
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError("Parameter wave_type must be included in [ACC, VEL, DISP].")
|
|
45
|
+
return np.squeeze(acc), np.squeeze(vel), np.squeeze(disp)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fourier(gm_data: np.ndarray, time_step: float) -> (
|
|
49
|
+
np.ndarray, np.ndarray, np.ndarray
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Calculate the fourier spectrum of wave.
|
|
53
|
+
Args:
|
|
54
|
+
gm_data: Ground motion data.
|
|
55
|
+
time_step: Time step.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
x-axis of fourier spectrum(HZ)
|
|
59
|
+
amp: A
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
x_fourier = np.abs(np.fft.fftfreq(d=time_step, n=len(gm_data))[len(gm_data) // 2:])[::-1]
|
|
63
|
+
# Take the absolute value of the complex number, i.e., the mode of the complex number (bilateral spectrum)
|
|
64
|
+
amp = np.abs(np.fft.fft(gm_data)) / len(gm_data)
|
|
65
|
+
# extract a unilateral spectrum
|
|
66
|
+
amp = (amp[len(gm_data) // 2:])[::-1]
|
|
67
|
+
return x_fourier, amp, amp ** 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def butter_worth_filter(
|
|
71
|
+
gm_data: np.ndarray[np.float64],
|
|
72
|
+
time_step: float,
|
|
73
|
+
order: int = 4,
|
|
74
|
+
start_freq: float = 0.1,
|
|
75
|
+
end_freq: float = 15,
|
|
76
|
+
pass_way: str = 'band'):
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
The start frequency and stop frequency are suggested as 0.1HZ and 25HZ.
|
|
80
|
+
Because commonly the effective frequency in ground motions is range from 0.1hz to 25hz.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
gm_data: Ground motion data.
|
|
84
|
+
time_step: Time step.
|
|
85
|
+
order: The order of butterworth filter. Default 4.
|
|
86
|
+
start_freq: The start freq of filter. Default 0.1.
|
|
87
|
+
end_freq: The end freq of filter. Default 0.1.
|
|
88
|
+
pass_way: The pass way of filter. Default bandpass.
|
|
89
|
+
|
|
90
|
+
Returns: Filtered waves.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
b, a = signal.butter(order, [2 * start_freq * time_step, 2 * end_freq * time_step], pass_way)
|
|
94
|
+
# ! Result by using ``lfilter`` is sample to Seismic Signal, not filtfilt
|
|
95
|
+
return signal.lfilter(b, a, gm_data)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def down_sample(gm_data: np.ndarray, ori_time_step: float, tar_time_step: float) -> np.ndarray:
|
|
99
|
+
"""
|
|
100
|
+
Down-sample the wave data.
|
|
101
|
+
|
|
102
|
+
The method used for down-sampling is mean-down-sampling.
|
|
103
|
+
Use mean down-samping method can let the calculated displacement and velocity be the same as before.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
gm_data: The input wave data.
|
|
107
|
+
ori_time_step: Origin time step.
|
|
108
|
+
tar_time_step: Target time step.
|
|
109
|
+
Returns:
|
|
110
|
+
Downsized wave_data. Using scipy.signal.resample
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# The two lines that are commented out are the previous methods.
|
|
114
|
+
# tar_data_size = int(ori_time_step / tar_time_step * wave_data.shape[wave_data.ndim - 1])
|
|
115
|
+
# return signal.resample(wave_data, tar_data_size, axis=axis).mean()
|
|
116
|
+
|
|
117
|
+
num_samples = int(gm_data.shape[-1] * ori_time_step / tar_time_step)
|
|
118
|
+
return signal.resample(gm_data, num_samples, axis=gm_data.ndim - 1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def length_normalize(gm_data: np.ndarray[np.float64], normal_length: int):
|
|
122
|
+
"""
|
|
123
|
+
Seismic wave length normalisation method.
|
|
124
|
+
|
|
125
|
+
Normalisation algorithm:
|
|
126
|
+
1. If the original seismic wave length l1 is less than the normalised seismic wave length ln,
|
|
127
|
+
then zero is added directly at the end.
|
|
128
|
+
2. If the original seismic wave length l1 is greater than the seismic wave length ln to be normalised,
|
|
129
|
+
the following operation is performed.
|
|
130
|
+
2.1 Extraction of ground shaking PGA occurrences i1
|
|
131
|
+
2.2 Calculate the ratio a(0<a<1) of the original length to the normalised length,
|
|
132
|
+
then the original ground shaking should be extracted int(i1*a) units, before the peak appears.
|
|
133
|
+
When the peak appears, it is dealt with directly by the truncation and zero filling method.
|
|
134
|
+
Args:
|
|
135
|
+
gm_data: Ground motion data.
|
|
136
|
+
normal_length: 要归一化的长度
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
# 1 The normalised length is greater than the original length
|
|
142
|
+
if gm_data.shape[0] <= normal_length:
|
|
143
|
+
return np.pad(
|
|
144
|
+
gm_data,
|
|
145
|
+
(0, normal_length - gm_data.shape[0]),
|
|
146
|
+
'constant',
|
|
147
|
+
constant_values=(0, 0)
|
|
148
|
+
)
|
|
149
|
+
# 2 The normalised length is less than the original length
|
|
150
|
+
else:
|
|
151
|
+
cut_rate = normal_length / gm_data.shape[0]
|
|
152
|
+
pga_loca = np.argmax(np.abs(gm_data))
|
|
153
|
+
forward_length = int(pga_loca * cut_rate)
|
|
154
|
+
res = gm_data[pga_loca - forward_length:normal_length - forward_length + pga_loca]
|
|
155
|
+
return res
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# @FileName :sbs_integration_linear.py
|
|
3
|
+
# @Time :2024/8/25 下午7:57
|
|
4
|
+
# @Author :RichardoGu
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def segmented_parsing(mass: float,
|
|
9
|
+
stiffness: float,
|
|
10
|
+
load: np.ndarray,
|
|
11
|
+
time_step: float,
|
|
12
|
+
damping_ratio: float = 0.05,
|
|
13
|
+
disp_0: float = 0,
|
|
14
|
+
vel_0: float = 0) -> (np.ndarray, np.ndarray, np.ndarray):
|
|
15
|
+
"""
|
|
16
|
+
This function is Segmented Parsing method, which is generally applicable
|
|
17
|
+
for solving the dynamic response of single degree of freedom system.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
mass:
|
|
21
|
+
stiffness:
|
|
22
|
+
load: The dynamic load array, changeable over time, is often the ground motion.
|
|
23
|
+
This parameter should have 2 dims as (batch size, sequence length)
|
|
24
|
+
time_step: The time step of load
|
|
25
|
+
damping_ratio:
|
|
26
|
+
disp_0: The init displacement of system, is often 0
|
|
27
|
+
vel_0: The init velocity of system, is often 0
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The result is tuple, which consist of the ``(acceleration, velocity, displacement)`` response in order.
|
|
31
|
+
-------
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
# Array fit
|
|
35
|
+
if load.ndim == 1:
|
|
36
|
+
load = np.expand_dims(load, 0)
|
|
37
|
+
# Data preparation
|
|
38
|
+
batch_size = load.shape[0]
|
|
39
|
+
seq_length = load.shape[1]
|
|
40
|
+
|
|
41
|
+
omega_n = np.sqrt(stiffness / mass)
|
|
42
|
+
omega_d = omega_n * np.sqrt(1 - damping_ratio ** 2)
|
|
43
|
+
temp_1 = np.e ** (-damping_ratio * omega_n * time_step)
|
|
44
|
+
temp_2 = damping_ratio / np.sqrt(1 - damping_ratio ** 2)
|
|
45
|
+
temp_3 = 2 * damping_ratio / (omega_n * time_step)
|
|
46
|
+
temp_4 = (1 - 2 * damping_ratio ** 2) / (omega_d * time_step)
|
|
47
|
+
temp_5 = omega_n / np.sqrt(1 - damping_ratio ** 2)
|
|
48
|
+
sin = np.sin(omega_d * time_step)
|
|
49
|
+
cos = np.cos(omega_d * time_step)
|
|
50
|
+
|
|
51
|
+
p_a = temp_1 * (temp_2 * sin + cos)
|
|
52
|
+
p_b = temp_1 * (sin / omega_d)
|
|
53
|
+
p_c = 1 / stiffness * (temp_3 + temp_1 * (
|
|
54
|
+
(temp_4 - temp_2) * sin - (1 + temp_3) * cos
|
|
55
|
+
))
|
|
56
|
+
p_d = 1 / stiffness * (1 - temp_3 + temp_1 * (
|
|
57
|
+
-temp_4 * sin + temp_3 * cos
|
|
58
|
+
))
|
|
59
|
+
p_a_prime = -temp_1 * (temp_5 * sin)
|
|
60
|
+
p_b_prime = temp_1 * (cos - temp_2 * sin)
|
|
61
|
+
p_c_prime = 1 / stiffness * (-1 / time_step + temp_1 * (
|
|
62
|
+
(temp_5 + temp_2 / time_step) * sin + 1 / time_step * cos
|
|
63
|
+
))
|
|
64
|
+
p_d_prime = 1 / (stiffness * time_step) * (
|
|
65
|
+
1 - temp_1 * (temp_2 * sin + cos)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Init the start displacement and velocity.
|
|
69
|
+
disp = np.zeros((batch_size, seq_length))
|
|
70
|
+
vel = np.zeros((batch_size, seq_length))
|
|
71
|
+
acc = np.zeros((batch_size, seq_length))
|
|
72
|
+
|
|
73
|
+
if type(disp_0) is not np.ndarray:
|
|
74
|
+
disp_0 = np.zeros(batch_size)
|
|
75
|
+
if type(vel_0) is not np.ndarray:
|
|
76
|
+
vel_0 = np.zeros(batch_size)
|
|
77
|
+
disp[:, 0] = disp_0
|
|
78
|
+
vel[:, 0] = vel_0
|
|
79
|
+
|
|
80
|
+
# Start Iteration
|
|
81
|
+
for i in range(seq_length - 1):
|
|
82
|
+
disp[:, i + 1] = p_a * disp[:, i] + p_b * vel[:, i] + p_c * load[:, i] + p_d * load[:, i + 1]
|
|
83
|
+
vel[:, i + 1] = (p_a_prime * disp[:, i] +
|
|
84
|
+
p_b_prime * vel[:, i] + p_c_prime * load[:, i] + p_d_prime * load[:, i + 1])
|
|
85
|
+
acc[:, i + 1] = -2 * damping_ratio * omega_n * vel[:, i + 1] - stiffness / mass * disp[:, i + 1]
|
|
86
|
+
|
|
87
|
+
return acc, vel, disp
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def newmark_beta_single(mass, stiffness, load, time_step,
|
|
91
|
+
damping_ratio=0.05, disp_0=0, vel_0=0,
|
|
92
|
+
acc_0=0, beta=0.25, gamma=0.5,
|
|
93
|
+
result_length=0):
|
|
94
|
+
batch_size = load.shape[0]
|
|
95
|
+
seq_length = load.shape[1]
|
|
96
|
+
if result_length == 0:
|
|
97
|
+
result_length = int(1.2 * load.shape[1]) # 计算持时
|
|
98
|
+
load = np.append(load, np.zeros((batch_size, result_length - seq_length)), axis=1)
|
|
99
|
+
|
|
100
|
+
disp = np.zeros((batch_size, result_length))
|
|
101
|
+
vel = np.zeros((batch_size, result_length))
|
|
102
|
+
acc = np.zeros((batch_size, result_length))
|
|
103
|
+
if type(disp_0) is not np.ndarray:
|
|
104
|
+
disp_0 = np.zeros(batch_size)
|
|
105
|
+
if type(vel_0) is not np.ndarray:
|
|
106
|
+
vel_0 = np.zeros(batch_size)
|
|
107
|
+
if type(acc_0) is not np.ndarray:
|
|
108
|
+
acc_0 = np.zeros(batch_size)
|
|
109
|
+
disp[:, 0] = disp_0
|
|
110
|
+
vel[:, 0] = vel_0
|
|
111
|
+
acc[:, 0] = acc_0
|
|
112
|
+
a_0 = 1 / (beta * time_step ** 2)
|
|
113
|
+
a_1 = gamma / (beta * time_step)
|
|
114
|
+
a_2 = 1 / (beta * time_step)
|
|
115
|
+
a_3 = 1 / (2 * beta) - 1
|
|
116
|
+
a_4 = gamma / beta - 1
|
|
117
|
+
a_5 = time_step / 2 * (a_4 - 1)
|
|
118
|
+
a_6 = time_step * (1 - gamma)
|
|
119
|
+
a_7 = gamma * time_step
|
|
120
|
+
omega_n = np.sqrt(stiffness / mass)
|
|
121
|
+
damping = 2 * mass * omega_n * damping_ratio
|
|
122
|
+
equ_k = stiffness + a_0 * mass + a_1 * damping # 计算等效刚度
|
|
123
|
+
# 迭代正式开始
|
|
124
|
+
for i in range(result_length - 1):
|
|
125
|
+
equ_p = load[:, i + 1] + mass * (
|
|
126
|
+
a_0 * disp[:, i] + a_2 * vel[:, i] + a_3 * acc[:, i]) + damping * (
|
|
127
|
+
a_1 * disp[:, i] + a_4 * vel[:, i] + a_5 * acc[:, i]) # 计算等效荷载
|
|
128
|
+
disp[:, i + 1] = equ_p / equ_k # 计算位移
|
|
129
|
+
acc[:, i + 1] = a_0 * (disp[:, i + 1] - disp[:, i]) - a_2 * vel[:, i] - a_3 * acc[:, i] # 计算加速度
|
|
130
|
+
vel[:, i + 1] = vel[:, i] + a_6 * acc[:, i] + a_7 * acc[:, i + 1] # 计算速度
|
|
131
|
+
return acc, vel, disp
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# -*- coding:utf-8 -*-
|
|
2
|
+
# @Time: 2025/3/17 17:32
|
|
3
|
+
# @Author: RichardoGu
|
|
4
|
+
"""
|
|
5
|
+
Ground motion spectrum Calc
|
|
6
|
+
"""
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy import ndarray, float64
|
|
11
|
+
from sbs_integration_linear import newmark_beta_single
|
|
12
|
+
|
|
13
|
+
SPECTRUM_PERIOD = [
|
|
14
|
+
0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09,
|
|
15
|
+
0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.8, 1.0,
|
|
16
|
+
1.2, 1.4, 1.6, 1.8, 2.0, 2.5, 3.0,
|
|
17
|
+
3.5, 4.0, 5.0, 6.0
|
|
18
|
+
] # 反应谱取点,单位秒
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_spectrum(
|
|
22
|
+
gm_acc_data: np.ndarray,
|
|
23
|
+
time_step: float,
|
|
24
|
+
damping_ratio: float = 0.05,
|
|
25
|
+
# Calculation way options
|
|
26
|
+
calc_func: any = newmark_beta_single,
|
|
27
|
+
calc_opt: int = 0,
|
|
28
|
+
max_process: int = 4) -> (
|
|
29
|
+
ndarray[float64],
|
|
30
|
+
ndarray[float64],
|
|
31
|
+
ndarray[float64],
|
|
32
|
+
ndarray[float64],
|
|
33
|
+
ndarray[float64]):
|
|
34
|
+
"""
|
|
35
|
+
There are three types of response spectrum of ground motion: acceleration, velocity and displacement.
|
|
36
|
+
Type must in tuple ("ACC", "VEL", "DISP"), and can be both upper and lower.
|
|
37
|
+
|
|
38
|
+
Class :class:`GroundMotionData` use ``self.spectrum_acc`` , ``self.spectrum_acc`` , ``self.spectrum_acc``
|
|
39
|
+
to save. And this three variants will be calculated when they are first used.
|
|
40
|
+
|
|
41
|
+
TODO we use default damping ratio 0.05, try to use changeable damping ratio as input.
|
|
42
|
+
|
|
43
|
+
Warnings:
|
|
44
|
+
------
|
|
45
|
+
The programme starts multi-threaded calculations by default
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
gm_acc_data: Ground motion acc data.
|
|
49
|
+
time_step: Time step.
|
|
50
|
+
damping_ratio: Damping ratio.
|
|
51
|
+
calc_opt: The type of calculation to use.
|
|
52
|
+
|
|
53
|
+
- 0 Use single_threaded. Slow
|
|
54
|
+
|
|
55
|
+
- 1 Use multi_threaded. Faster TODO This func not completed.
|
|
56
|
+
|
|
57
|
+
calc_func: The type of calculation to use.
|
|
58
|
+
The return of calc_func should be a tuple ``(acc, vel, disp)``.
|
|
59
|
+
|
|
60
|
+
max_process: if calc_opt in [1,2], the multi thread will be used.
|
|
61
|
+
This is the maximum number of threads.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Calculated spectrum. np.ndarray[float] (batch size)
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
if gm_acc_data.ndim == 1:
|
|
68
|
+
gm_acc_data = np.expand_dims(gm_acc_data, axis=0)
|
|
69
|
+
elif gm_acc_data.ndim == 2:
|
|
70
|
+
pass
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError("ndim of gm_acc_data must be 1 or 2.")
|
|
73
|
+
|
|
74
|
+
batch_size = gm_acc_data.shape[0]
|
|
75
|
+
seq_len = gm_acc_data.shape[1]
|
|
76
|
+
spectrum_acc = np.zeros((batch_size, len(SPECTRUM_PERIOD)))
|
|
77
|
+
spectrum_vel = np.zeros((batch_size, len(SPECTRUM_PERIOD)))
|
|
78
|
+
spectrum_disp = np.zeros((batch_size, len(SPECTRUM_PERIOD)))
|
|
79
|
+
spectrum_pse_acc = np.zeros((batch_size, len(SPECTRUM_PERIOD)))
|
|
80
|
+
spectrum_pse_vel = np.zeros((batch_size, len(SPECTRUM_PERIOD)))
|
|
81
|
+
|
|
82
|
+
if calc_opt == 0:
|
|
83
|
+
for i in range(len(SPECTRUM_PERIOD)):
|
|
84
|
+
acc, vel, disp = calc_func(
|
|
85
|
+
mass=1,
|
|
86
|
+
stiffness=(2 * np.pi / SPECTRUM_PERIOD[i]) ** 2,
|
|
87
|
+
load=gm_acc_data,
|
|
88
|
+
damping_ratio=damping_ratio,
|
|
89
|
+
time_step=time_step,
|
|
90
|
+
result_length=seq_len
|
|
91
|
+
)
|
|
92
|
+
spectrum_acc[:, i] = np.abs(acc).max(1)
|
|
93
|
+
spectrum_vel[:, i] = np.abs(vel).max(1)
|
|
94
|
+
spectrum_disp[:, i] = np.abs(disp).max(1)
|
|
95
|
+
spectrum_pse_acc[:, i] = np.abs(disp).max(1) * (2 * np.pi / SPECTRUM_PERIOD[i]) ** 2
|
|
96
|
+
spectrum_pse_vel[:, i] = np.abs(disp).max(1) * (2 * np.pi / SPECTRUM_PERIOD[i])
|
|
97
|
+
|
|
98
|
+
elif calc_opt == 1:
|
|
99
|
+
# Create Processes
|
|
100
|
+
# TODO IF really need mutil-process, use cpp dll. Don't use python's mutil-process.
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
raise KeyError("Parameter 'calc_opt' should be 0 or 1.")
|
|
105
|
+
|
|
106
|
+
if gm_acc_data.shape[0] == 1:
|
|
107
|
+
spectrum_acc = spectrum_acc.squeeze()
|
|
108
|
+
spectrum_vel = spectrum_vel.squeeze()
|
|
109
|
+
spectrum_disp = spectrum_disp.squeeze()
|
|
110
|
+
spectrum_pse_acc = spectrum_pse_acc.squeeze()
|
|
111
|
+
spectrum_pse_acc = spectrum_pse_acc.squeeze()
|
|
112
|
+
|
|
113
|
+
return spectrum_acc, spectrum_vel, spectrum_disp, spectrum_pse_acc, spectrum_pse_acc
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ground-motion-tools"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "RichardoGu",email = "xiaopenggu@qq.com"}
|
|
7
|
+
]
|
|
8
|
+
license = {text = "MIT"}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"numpy (>=2.2.4,<3.0.0)",
|
|
13
|
+
"scipy (>=1.15.2,<2.0.0)",
|
|
14
|
+
"geopy (>=2.4.1,<3.0.0)"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
20
|
+
build-backend = "poetry.core.masonry.api"
|