im-calculation 0.0.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.
@@ -0,0 +1,25 @@
1
+ *.pyc
2
+ /IM/rspectra_calculations/rspectra.c
3
+ /IM/rspectra_calculations/rspectra.so
4
+ .idea
5
+ build/
6
+ test/test_calculate_ims/sample1/input/*.bin
7
+ test/test_calculate_ims/sample1/output/
8
+ test/test_calculate_ims/*.xml
9
+ test/test_calculate_ims/html*
10
+ test/test_calculate_ims/.coverage
11
+ test/test_calculate_ims/output/__pycache*
12
+ .cache/
13
+ *.sl
14
+ *.err
15
+ *.out
16
+ rrups.csv
17
+ .calculate_rrups.py.swp
18
+ *.log
19
+ runs/
20
+ **.so
21
+ **sample0
22
+
23
+ # special cases to unignore
24
+ !periods.out
25
+ .venv/
File without changes
@@ -0,0 +1,254 @@
1
+ import multiprocessing
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from IM import ims
9
+ from IM.ims import IM
10
+
11
+ DEFAULT_PERIODS = np.asarray(
12
+ [
13
+ 0.010,
14
+ 0.020,
15
+ 0.022,
16
+ 0.025,
17
+ 0.029,
18
+ 0.030,
19
+ 0.032,
20
+ 0.035,
21
+ 0.036,
22
+ 0.040,
23
+ 0.042,
24
+ 0.044,
25
+ 0.045,
26
+ 0.046,
27
+ 0.048,
28
+ 0.050,
29
+ 0.055,
30
+ 0.060,
31
+ 0.065,
32
+ 0.067,
33
+ 0.070,
34
+ 0.075,
35
+ 0.080,
36
+ 0.085,
37
+ 0.090,
38
+ 0.095,
39
+ 0.100,
40
+ 0.110,
41
+ 0.120,
42
+ 0.130,
43
+ 0.133,
44
+ 0.140,
45
+ 0.150,
46
+ 0.160,
47
+ 0.170,
48
+ 0.180,
49
+ 0.190,
50
+ 0.200,
51
+ 0.220,
52
+ 0.240,
53
+ 0.250,
54
+ 0.260,
55
+ 0.280,
56
+ 0.290,
57
+ 0.300,
58
+ 0.320,
59
+ 0.340,
60
+ 0.350,
61
+ 0.360,
62
+ 0.380,
63
+ 0.400,
64
+ 0.420,
65
+ 0.440,
66
+ 0.450,
67
+ 0.460,
68
+ 0.480,
69
+ 0.500,
70
+ 0.550,
71
+ 0.600,
72
+ 0.650,
73
+ 0.667,
74
+ 0.700,
75
+ 0.750,
76
+ 0.800,
77
+ 0.850,
78
+ 0.900,
79
+ 0.950,
80
+ 1.000,
81
+ 1.100,
82
+ 1.200,
83
+ 1.300,
84
+ 1.400,
85
+ 1.500,
86
+ 1.600,
87
+ 1.700,
88
+ 1.800,
89
+ 1.900,
90
+ 2.000,
91
+ 2.200,
92
+ 2.400,
93
+ 2.500,
94
+ 2.600,
95
+ 2.800,
96
+ 3.000,
97
+ 3.200,
98
+ 3.400,
99
+ 3.500,
100
+ 3.600,
101
+ 3.800,
102
+ 4.000,
103
+ 4.200,
104
+ 4.400,
105
+ 4.600,
106
+ 4.800,
107
+ 5.000,
108
+ 5.500,
109
+ 6.000,
110
+ 6.500,
111
+ 7.000,
112
+ 7.500,
113
+ 8.000,
114
+ 8.500,
115
+ 9.000,
116
+ 9.500,
117
+ 10.000,
118
+ 11.000,
119
+ 12.000,
120
+ 13.000,
121
+ 14.000,
122
+ 15.000,
123
+ 20.000,
124
+ ]
125
+ )
126
+ DEFAULT_FREQUENCIES = np.logspace(
127
+ np.log10(0.01318257),
128
+ np.log10(100),
129
+ num=389,
130
+ )
131
+
132
+
133
+ def calculate_ims(
134
+ waveform: np.ndarray,
135
+ dt: float,
136
+ ims_list: list[IM] = list(IM),
137
+ periods: np.ndarray = DEFAULT_PERIODS,
138
+ frequencies: np.ndarray = DEFAULT_FREQUENCIES,
139
+ cores: int = multiprocessing.cpu_count(),
140
+ ko_directory: Path | None = None,
141
+ use_numexpr: bool = False,
142
+ ):
143
+ """
144
+ Calculate intensity measures for a single waveform.
145
+
146
+ Parameters
147
+ ----------
148
+ waveform : np.ndarray
149
+ Waveform data as a NumPy array.
150
+ dt : float
151
+ Sampling interval (dt) of the waveform.
152
+ ims_list : list of IM, optional
153
+ List of intensity measures (IMs) to calculate, e.g., [IM.PGA, IM.pSA, IM.CAV].
154
+ periods : np.ndarray, optional
155
+ List of periods required for calculating the pseudo-spectral acceleration (pSA).
156
+ frequencies : np.ndarray, optional
157
+ List of frequencies required for calculating the Fourier amplitude spectrum (FAS).
158
+ cores : int, optional
159
+ Number of cores to use for parallel processing in pSA and FAS calculations.
160
+ ko_directory : Path, optional
161
+ Path to the directory containing the Konno-Ohmachi matrices.
162
+ Only required if FAS is in the list of IMs.
163
+ use_numexpr : bool, optional
164
+ If True, use numexpr for calculations. (Faster off for single waveform and multiprocessing)
165
+ Default is False.
166
+
167
+ Returns
168
+ -------
169
+ pd.DataFrame
170
+ DataFrame containing the calculated intensity measures.
171
+ The columns are the IMs and the rows are the different components.
172
+
173
+ Raises
174
+ ------
175
+ ValueError
176
+ If the IM is not recognized or if required environment variables are not set to 1.
177
+ """
178
+ if cores == 1:
179
+ required_env_vars = [
180
+ "NUMEXPR_NUM_THREADS",
181
+ "NUMBA_MAX_THREADS",
182
+ "NUMBA_NUM_THREADS",
183
+ "OPENBLAS_NUM_THREADS",
184
+ ]
185
+ unset_vars = [var for var in required_env_vars if os.getenv(var) != "1"]
186
+ if unset_vars:
187
+ raise ValueError(
188
+ f"The following environment variables must be set to 1: {', '.join(unset_vars)}"
189
+ )
190
+ if ko_directory is None and IM.FAS in ims_list:
191
+ raise ValueError(
192
+ "The Konno-Ohmachi directory must be provided if Fourier amplitude spectrum is in the list of IMs."
193
+ )
194
+
195
+ results = []
196
+
197
+ # Iterate through IMs and calculate them
198
+ for im in ims_list:
199
+ if im == IM.PGA:
200
+ result = ims.peak_ground_acceleration(waveform, use_numexpr=use_numexpr)
201
+ result.index = [im.value]
202
+ elif im == IM.PGV:
203
+ result = ims.peak_ground_velocity(waveform, dt, use_numexpr=use_numexpr)
204
+ result.index = [im.value]
205
+ elif im == IM.pSA:
206
+ data_array = ims.pseudo_spectral_acceleration(
207
+ waveform, periods, np.float32(dt), cores=cores, use_numexpr=use_numexpr
208
+ )
209
+ # Convert the data array to a DataFrame
210
+ result = data_array.to_dataframe().unstack(level="component")
211
+ result.index = [
212
+ f"{im.value}_{idx}" for idx in data_array.coords["period"].values
213
+ ]
214
+ result.columns = result.columns.droplevel(0)
215
+ elif im == IM.CAV:
216
+ result = ims.cumulative_absolute_velocity(waveform, dt)
217
+ result.index = [im.value]
218
+ elif im == IM.CAV5:
219
+ result = ims.cumulative_absolute_velocity(waveform, dt, 5)
220
+ result.index = [im.value]
221
+ elif im == IM.Ds575:
222
+ result = ims.ds575(waveform, dt, use_numexpr=use_numexpr)
223
+ result.index = [im.value]
224
+ elif im == IM.Ds595:
225
+ result = ims.ds595(waveform, dt, use_numexpr=use_numexpr)
226
+ result.index = [im.value]
227
+ elif im == IM.AI:
228
+ result = ims.arias_intensity(waveform, dt)
229
+ result.index = [im.value]
230
+ elif im == IM.FAS:
231
+ assert ko_directory
232
+ data_array = ims.fourier_amplitude_spectra(
233
+ waveform,
234
+ dt,
235
+ frequencies,
236
+ cores=cores,
237
+ # ko_directory must be Path because of the check earlier.
238
+ ko_directory=ko_directory,
239
+ )
240
+ # Convert the data array to a DataFrame
241
+ result = data_array.to_dataframe().unstack(level="component")
242
+ result.index = [
243
+ f"{im.value}_{idx}" for idx in data_array.coords["frequency"].values
244
+ ]
245
+ result.columns = result.columns.droplevel(0)
246
+ else:
247
+ raise ValueError(
248
+ f"IM {im} not recognized. Available IMs are {IM.__members__.keys()}"
249
+ )
250
+ results.append(result)
251
+
252
+ # Combine all results into a single DataFrame
253
+ output_ims = pd.concat(results).T
254
+ return output_ims
@@ -0,0 +1,100 @@
1
+ from pathlib import Path
2
+
3
+ # Importing pint_xarray registers pint units with xarray, allowing for
4
+ # unit-aware operations. It is not explicitly used, so we ignore the flake8
5
+ # F401 error.
6
+ import pint_xarray # noqa: F401
7
+ import xarray as xr
8
+
9
+ from IM.ims import IM
10
+
11
+
12
+ def read_intensity_measures(
13
+ intensity_measure_file: str | Path,
14
+ ) -> xr.Dataset:
15
+ """
16
+ Read intensity measures from a file and return as an xarray Dataset.
17
+
18
+ Parameters
19
+ ----------
20
+ intensity_measure_file : str or Path
21
+ The file path or filename of the intensity measures dataset to be read.
22
+
23
+ Returns
24
+ -------
25
+ xr.Dataset
26
+ The xarray dataset containing the intensity measures."""
27
+ return xr.open_dataset(intensity_measure_file, engine="h5netcdf").pint.quantify()
28
+
29
+
30
+ COORDINATE_METADATA = {
31
+ "station": {"description": "Station identifiers", "units": None},
32
+ "component": {"description": "Component of motion", "units": None},
33
+ "period": {"description": "Oscillation period", "units": "s"},
34
+ "vs30": {
35
+ "description": "Average shear-wave velocity to 30m depth",
36
+ "units": "m/s",
37
+ },
38
+ "epi": {"description": "Epicentral distance", "units": "km"},
39
+ "hyp": {"description": "Hypocentral distance", "units": "km"},
40
+ "rrup": {"description": "Rupture distance", "units": "km"},
41
+ "rjb": {"description": "Joyner-Boore distance", "units": "km"},
42
+ "latitude": {"description": "Station latitude", "units": "degrees"},
43
+ "longitude": {"description": "Station longitude", "units": "degrees"},
44
+ "frequency": {"description": "Frequency of motion", "units": "Hz"},
45
+ }
46
+
47
+ IM_METADATA = {
48
+ IM.PGA: "Peak ground acceleration",
49
+ IM.PGV: "Peak ground velocity",
50
+ IM.CAV: "Cumulative absolute velocity",
51
+ IM.CAV5: "Cumulative absolute velocity (above 5 cm/s)",
52
+ IM.AI: "Arias intensity",
53
+ IM.Ds575: "Significant duration (5-75%)",
54
+ IM.Ds595: "Significant duration (5-95%)",
55
+ IM.pSA: "Pseudo-spectral acceleration",
56
+ IM.FAS: "Fourier amplitude spectrum",
57
+ }
58
+
59
+
60
+ # The 'g0' unit is used for acceleration and is equivalent to 9.81 m/s^2. The
61
+ # reason for this is that 'g' is reserved for 'grams'. This is a decision
62
+ # made by the `pint` library, which is used to handle the units.
63
+ IM_UNITS = {
64
+ IM.PGA: "g0",
65
+ IM.PGV: "cm/s",
66
+ IM.CAV: "m/s",
67
+ IM.CAV5: "m/s",
68
+ IM.AI: "m/s",
69
+ IM.Ds575: "s",
70
+ IM.Ds595: "s",
71
+ IM.FAS: "g0 * s",
72
+ IM.pSA: "g0",
73
+ "frequency": "Hz",
74
+ "period": "s",
75
+ }
76
+
77
+
78
+ def write_intensity_measures(dataset: xr.Dataset, output_ffp: str | Path) -> None:
79
+ """Write intensity measures to a file, updating coordinate and variable metadata.
80
+
81
+ Parameters
82
+ ----------
83
+ dataset : xr.Dataset
84
+ The xarray dataset containing intensity measures to be written.
85
+ output_ffp : str or Path
86
+ The file path where the output dataset should be saved.
87
+ """
88
+ for name, description in COORDINATE_METADATA.items():
89
+ if name not in dataset.coords:
90
+ continue
91
+ dataset.coords[name].attrs.update(COORDINATE_METADATA[name])
92
+
93
+ for im_name, description in IM_METADATA.items():
94
+ if im_name not in dataset:
95
+ continue
96
+ dataset[im_name].attrs["description"] = IM_METADATA[im_name]
97
+
98
+ dataset = dataset.pint.quantify(IM_UNITS)
99
+
100
+ dataset.pint.dequantify().to_netcdf(output_ffp, engine="h5netcdf")