beampower 1.0.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.
beampower/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ beampower is a Python package wrapping a C and CUDA-C implementation
3
+ of beamforming, sometimes also called back-projection.
4
+
5
+ If you have any question, don't hesitate contacting me at:
6
+ ebeauce@ldeo.columbia.edu
7
+ """
8
+
9
+ __all__ = ["load_library", "beamform"]
10
+
11
+ from .core import load_library
12
+ from .beampower import beamform
13
+
14
+ __version__ = "1.0.3"
beampower/beampower.py ADDED
@@ -0,0 +1,287 @@
1
+ # coding: utf-8
2
+
3
+ import ctypes as ct
4
+ import numpy as np
5
+
6
+ from .core import load_library
7
+ from os import cpu_count
8
+
9
+
10
+ def beamform(
11
+ waveform_features,
12
+ time_delays,
13
+ weights_phases,
14
+ weights_sources,
15
+ device="cpu",
16
+ reduce="max",
17
+ mode="direct",
18
+ out_of_bounds="strict",
19
+ num_threads=None,
20
+ ):
21
+ """Compute the beamformed network response.
22
+
23
+ This routine computes and returns the whole network response over the
24
+ entire duration of `waveform_features`. Thus, if the input source grid
25
+ is large, the output might be considerably memory-consuming. Therefore,
26
+ this routine is more appropriate for small scale studies such as the
27
+ rupture imaging of an earthquake.
28
+
29
+ Parameters
30
+ ----------
31
+ waveform_features: (n_stations, n_channels, n_samples) numpy.ndarray, float
32
+ Any characterization function computed from the continuous seismograms.
33
+ time_delays: (n_sources, n_stations, n_phases) numpy.ndarray, int
34
+ Moveouts, in samples, from each of the `n_sources` theoretical sources
35
+ to each of the `n_stations` seismic stations and for the `n_phases`
36
+ back-projected seismic phases.
37
+ weights_phases: (n_stations, n_channels, n_phases) numpy.ndarray, float
38
+ Weight given to each station and channel for a given phase. For
39
+ example, horizontal components might be given a small or zero
40
+ weight for the P-wave stacking.
41
+ weights_sources: (n_sources, n_stations) numpy.ndarray, float
42
+ Source-receiver-specific weights. For example, based on the
43
+ source-receiver distance.
44
+ device: string, default to 'cpu'
45
+ Either 'cpu' or 'gpu', depending on the available hardware and
46
+ user's preferences.
47
+ reduce: string, default to 'max'
48
+ Reduction operation applied to the beamformed network response. If
49
+ `reduce` is `'max'`, return the maximum network response of the grid at
50
+ each time step, as well as the source indexes. If `reduce` is `'none'`,
51
+ `None` or `'None'`, return the full beamformed network response.
52
+ mode: string, default to 'direct'
53
+ Either 'direct' (default) or 'differential'. If 'direct', the time
54
+ delays are the (relative) source-receiver propagation times. If
55
+ 'differential', the time delays are the inter-station differential
56
+ propagation times. The latter requires `waveform_features` to be based
57
+ on inter-station cross-correlations.
58
+ out_of_bounds: string, default to 'strict'
59
+ Either 'strict' (default) or 'flexible'.
60
+
61
+ - 'strict': A beam is computed if and only if the moveouts point to a
62
+ valid sample (that is, within the bounds of the data stream) for every
63
+ channel used in the beam.
64
+ - 'flexible': A beam is computed as long as the moveouts point to a
65
+ valid sample for at least one channel. This option is particularly
66
+ useful for real time applications where an event might have been
67
+ recorded at the closest stations but not yet at the more distant ones.
68
+ num_threads: int or None
69
+ Number of threads for CPU parallelization. If None, uses one thread per
70
+ available (visible) CPU.
71
+
72
+ Returns
73
+ --------
74
+ beam: (n_sources, n_samples) or (n_samples,) numpy.ndarray, float
75
+ Full network response (n_sources, n_samples) or maximum network
76
+ response (n_samples,). See `reduce`.
77
+ beam_argmax: (n_samples,) numpy.ndarray, int, optional
78
+ If `reduce` is `'max'`, return the maximum network response source
79
+ indexes.
80
+ """
81
+ if out_of_bounds not in ["strict", "flexible"]:
82
+ print("out_of_bounds should be either of 'strict' or 'flexible',"
83
+ f" not {out_of_bounds}")
84
+ return
85
+ elif out_of_bounds == "strict":
86
+ out_of_bounds = 0
87
+ elif out_of_bounds == "flexible":
88
+ out_of_bounds = 1
89
+
90
+ if num_threads is None:
91
+ # set num_threads to -1 so that the C routine
92
+ # understands to use all CPUs
93
+ num_threads = cpu_count()
94
+
95
+ # Load library
96
+ lib = load_library(device)
97
+
98
+ # Get shapes
99
+ n_stations, _, n_samples = waveform_features.shape
100
+ n_sources, _, n_phases = time_delays.shape
101
+
102
+ # Prestack detection traces
103
+ waveform_features = prestack_traces(
104
+ waveform_features, weights_phases, num_threads=num_threads, device="cpu"
105
+ )
106
+
107
+ # Get waveform features
108
+ waveform_features = waveform_features.flatten().astype(np.float32)
109
+ time_delays = time_delays.flatten().astype(np.int32)
110
+ weights_sources = weights_sources.flatten().astype(np.float32)
111
+
112
+ # Essential feature
113
+ if np.random.random() < 1.0e-6:
114
+ print("beampower to the people!")
115
+
116
+ if mode in ["normal", "direct"]:
117
+ # time delays are (relative) source-receiver propagation times
118
+
119
+ # We keep four cases separate in case the signature differs
120
+ if device.lower() == "cpu":
121
+
122
+ if reduce in ["none", "None", None]:
123
+ beam = np.zeros(n_sources * n_samples, dtype=np.float32)
124
+ lib.beamform(
125
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
126
+ time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
127
+ weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
128
+ n_samples,
129
+ n_sources,
130
+ n_stations,
131
+ n_phases,
132
+ int(out_of_bounds),
133
+ int(num_threads),
134
+ beam.ctypes.data_as(ct.POINTER(ct.c_float)),
135
+ )
136
+ return beam.reshape(n_sources, n_samples)
137
+
138
+ elif reduce == "max":
139
+ beam_max = np.zeros(n_samples, dtype=np.float32)
140
+ beam_argmax = np.zeros(n_samples, dtype=np.int32)
141
+ lib.beamform_max(
142
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
143
+ time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
144
+ weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
145
+ n_samples,
146
+ n_sources,
147
+ n_stations,
148
+ n_phases,
149
+ int(out_of_bounds),
150
+ int(num_threads),
151
+ beam_max.ctypes.data_as(ct.POINTER(ct.c_float)),
152
+ beam_argmax.ctypes.data_as(ct.POINTER(ct.c_int)),
153
+ )
154
+ return beam_max, beam_argmax
155
+
156
+ elif device.lower() == "gpu":
157
+
158
+ if reduce in ["none", "None", None]:
159
+ beam = np.zeros(n_sources * n_samples, dtype=np.float32)
160
+ lib.beamform(
161
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
162
+ time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
163
+ weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
164
+ n_samples,
165
+ n_sources,
166
+ n_stations,
167
+ n_phases,
168
+ int(out_of_bounds),
169
+ #int(num_threads),
170
+ beam.ctypes.data_as(ct.POINTER(ct.c_float)),
171
+ )
172
+ return beam.reshape(n_sources, n_samples)
173
+
174
+ elif reduce == "max":
175
+ beam_max = np.zeros(n_samples, dtype=np.float32)
176
+ beam_argmax = np.zeros(n_samples, dtype=np.int32)
177
+ lib.beamform_max(
178
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
179
+ time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
180
+ weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
181
+ n_samples,
182
+ n_sources,
183
+ n_stations,
184
+ n_phases,
185
+ int(out_of_bounds),
186
+ #int(num_threads),
187
+ beam_max.ctypes.data_as(ct.POINTER(ct.c_float)),
188
+ beam_argmax.ctypes.data_as(ct.POINTER(ct.c_int)),
189
+ )
190
+ return beam_max, beam_argmax
191
+
192
+ if mode == "differential":
193
+ # time delays are (relative) source-receiver propagation times
194
+
195
+ # We keep four cases separate in case the signature differs
196
+ if device.lower() == "cpu":
197
+
198
+ beam = np.zeros(n_sources, dtype=np.float32)
199
+ lib.beamform_differential(
200
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
201
+ time_delays.ctypes.data_as(ct.POINTER(ct.c_int)),
202
+ weights_sources.ctypes.data_as(ct.POINTER(ct.c_float)),
203
+ n_samples,
204
+ n_sources,
205
+ n_stations,
206
+ n_phases,
207
+ num_threads,
208
+ beam.ctypes.data_as(ct.POINTER(ct.c_float)),
209
+ )
210
+
211
+ if reduce in ["none", "None", None]:
212
+ return beam
213
+ elif reduce == "max":
214
+ beam_max = np.max(beam, axis=0)
215
+ beam_argmax = np.argmax(beam, axis=0)
216
+ return beam_max, beam_argmax
217
+
218
+ return beam.reshape(n_sources, n_samples)
219
+
220
+ elif device.lower() == "gpu":
221
+
222
+ print("differential mode not yet implemented on GPU")
223
+ return
224
+
225
+ else:
226
+ print(f"Mode should either be 'direct' or 'differential', not {mode}.")
227
+ return 1
228
+
229
+
230
+ def prestack_traces(waveform_features, weights_phases, num_threads=None, device="cpu"):
231
+ """Prestack the detection traces ahead of the beamforming.
232
+
233
+ Channel-wise stacking for each target seismic phase can be done
234
+ once and for all at the beginning of the computation.
235
+
236
+ Parameters
237
+ -----------
238
+ waveform_features: (n_stations, n_channels, n_stations) numpy.ndarray, float
239
+ Any characterization function computed from the continuous seismograms.
240
+ weights_phases: (n_stations, n_channels, n_phases) numpy.ndarray, float
241
+ Weight given to each station and channel for a given phase. For
242
+ example, horizontal components might be given a small or zero
243
+ weight for the P-wave stacking.
244
+ device: string, default to 'cpu'
245
+ Either 'cpu' or 'gpu', depending on the available hardware and
246
+ user's preferences.
247
+ num_threads: int or None
248
+ Number of threads for CPU parallelization. If None, uses one thread per
249
+ available (visible) CPU.
250
+
251
+ Returns
252
+ ----------
253
+ prestacked_traces: (n_stations, n_samples, n_phases) numpy.ndarray, float
254
+ Channel-wise stacked detection traces, optimally formatted for the C
255
+ CUDA-C routines.
256
+ """
257
+ # Load library
258
+ lib = load_library(device)
259
+
260
+ if num_threads is None:
261
+ num_threads = cpu_count()
262
+
263
+ # Get shapes
264
+ n_stations, n_channels, n_samples = waveform_features.shape
265
+ _, _, n_phases = weights_phases.shape
266
+ prestacked_traces = np.zeros(
267
+ (n_stations * n_samples * n_phases), dtype=np.float32
268
+ )
269
+
270
+ # Cast
271
+ waveform_features = waveform_features.flatten().astype(np.float32)
272
+ weights_phases = weights_phases.flatten().astype(np.float32)
273
+
274
+ # Prestack
275
+ if device.lower() == "cpu":
276
+ lib.prestack_waveform_features(
277
+ waveform_features.ctypes.data_as(ct.POINTER(ct.c_float)),
278
+ weights_phases.ctypes.data_as(ct.POINTER(ct.c_float)),
279
+ n_samples,
280
+ n_stations,
281
+ n_channels,
282
+ n_phases,
283
+ int(num_threads),
284
+ prestacked_traces.ctypes.data_as(ct.POINTER(ct.c_float)),
285
+ )
286
+
287
+ return prestacked_traces.reshape((n_stations, n_samples, n_phases))
beampower/core.py ADDED
@@ -0,0 +1,169 @@
1
+ # coding: utf-8
2
+
3
+ import os
4
+ import ctypes as ct
5
+
6
+
7
+ DIRPATH_LIBRARIES = os.path.join(os.path.dirname(__file__), "lib")
8
+
9
+ LIBRARIES = {
10
+ "cpu": {
11
+ "filepath_library": os.path.join(DIRPATH_LIBRARIES, "beamform_cpu.so"),
12
+ "is_loaded": False,
13
+ "lib": None,
14
+ "beamform_argtypes": [
15
+ ct.POINTER(ct.c_float), # waveform_features
16
+ ct.POINTER(ct.c_int), # time_delays
17
+ ct.POINTER(ct.c_float), # weights_sources
18
+ ct.c_size_t, # n_samples
19
+ ct.c_size_t, # n_sources
20
+ ct.c_size_t, # n_stations
21
+ ct.c_size_t, # n_phases
22
+ ct.c_int, # out_of_bounds
23
+ ct.c_int, # num_threads
24
+ ct.POINTER(ct.c_float), # beam
25
+ ],
26
+ "beamform_differential_argtypes": [
27
+ ct.POINTER(ct.c_float), # waveform_features
28
+ ct.POINTER(ct.c_int), # time_delays
29
+ ct.POINTER(ct.c_float), # weights_sources
30
+ ct.c_size_t, # n_samples
31
+ ct.c_size_t, # n_sources
32
+ ct.c_size_t, # n_stations
33
+ ct.c_size_t, # n_phases
34
+ ct.c_int, # num_threads
35
+ ct.POINTER(ct.c_float), # beam
36
+ ],
37
+ "beamform_max_argtypes": [
38
+ ct.POINTER(ct.c_float), # waveform_features
39
+ ct.POINTER(ct.c_int), # time_delays
40
+ ct.POINTER(ct.c_float), # weights_sources
41
+ ct.c_size_t, # n_samples
42
+ ct.c_size_t, # n_sources
43
+ ct.c_size_t, # n_stations
44
+ ct.c_size_t, # n_phases
45
+ ct.c_int, # out_of_bounds
46
+ ct.c_int, # num_threads
47
+ ct.POINTER(ct.c_float), # beam_max
48
+ ct.POINTER(ct.c_int), # beam_argmax
49
+ ],
50
+ "prestack_waveform_features_argtypes": [
51
+ ct.POINTER(ct.c_float), # waveform_features
52
+ ct.POINTER(ct.c_float), # weights_phases
53
+ ct.c_size_t, # n_sources
54
+ ct.c_size_t, # n_stations
55
+ ct.c_size_t, # n_channels
56
+ ct.c_size_t, # n_phases
57
+ ct.c_int, # num_threads
58
+ ct.POINTER(ct.c_float), # prestacked_traces
59
+ ],
60
+ },
61
+ "gpu": {
62
+ "filepath_library": os.path.join(DIRPATH_LIBRARIES, "beamform_gpu.so"),
63
+ "is_loaded": False,
64
+ "lib": None,
65
+ "beamform_argtypes": [
66
+ ct.POINTER(ct.c_float), # waveform_features
67
+ ct.POINTER(ct.c_int), # time_delays
68
+ ct.POINTER(ct.c_float), # weights_sources
69
+ ct.c_size_t, # n_samples
70
+ ct.c_size_t, # n_sources
71
+ ct.c_size_t, # n_stations
72
+ ct.c_size_t, # n_phases
73
+ ct.c_int, # out_of_bounds
74
+ ct.POINTER(ct.c_float), # beam
75
+ ],
76
+ "beamform_max_argtypes": [
77
+ ct.POINTER(ct.c_float), # waveform_features
78
+ ct.POINTER(ct.c_int), # time_delays
79
+ ct.POINTER(ct.c_float), # weights_sources
80
+ ct.c_size_t, # n_samples
81
+ ct.c_size_t, # n_sources
82
+ ct.c_size_t, # n_stations
83
+ ct.c_size_t, # n_phases
84
+ ct.c_int, # out_of_bounds
85
+ ct.POINTER(ct.c_float), # beam_max
86
+ ct.POINTER(ct.c_int), # beam_argmax
87
+ ],
88
+ },
89
+ }
90
+
91
+
92
+ def load_library(device="cpu"):
93
+ """Load library for device.
94
+
95
+ This function loads the library only once, that is, if the library is
96
+ successfully loaded a first time, it will be stored as a persistent
97
+ variable in the LIBRARIES dictionary.
98
+
99
+ Parameters
100
+ ----------
101
+ device: str, optional
102
+ Device-compilated library, either "cpu" or "gpu".
103
+
104
+ Returns
105
+ -------
106
+ lib : ctypes.CDLL
107
+ Loaded shared library object
108
+
109
+ Raises
110
+ ------
111
+ NameError
112
+ If device is not "cpu" or "gpu"
113
+ OSError
114
+ If the library file is not found
115
+ RuntimeError
116
+ If GPU library is requested but not available
117
+ """
118
+ # Get device name
119
+ device_name = device.lower()
120
+
121
+ # Check device name
122
+ if device_name not in LIBRARIES:
123
+ raise NameError(f"Device should be cpu or gpu, not {device_name}")
124
+
125
+ # Check if shared object exists
126
+ library_info = LIBRARIES[device_name]
127
+ filepath_library = library_info["filepath_library"]
128
+
129
+ if os.path.exists(filepath_library):
130
+ # If library was previously loaded, return it
131
+ if library_info["is_loaded"] is True:
132
+ return library_info["lib"]
133
+
134
+ # Otherwise load it
135
+ else:
136
+ # Load
137
+ lib = ct.cdll.LoadLibrary(filepath_library)
138
+
139
+ # Declare types
140
+ lib.beamform.argtypes = library_info["beamform_argtypes"]
141
+ lib.beamform_max.argtypes = library_info["beamform_max_argtypes"]
142
+ if device_name == "cpu":
143
+ lib.prestack_waveform_features.argtypes = library_info[
144
+ "prestack_waveform_features_argtypes"
145
+ ]
146
+ lib.beamform_differential.argtypes = library_info[
147
+ "beamform_differential_argtypes"
148
+ ]
149
+
150
+ # Store pre-loaded library
151
+ LIBRARIES[device_name]["is_loaded"] = True
152
+ LIBRARIES[device_name]["lib"] = lib
153
+
154
+ return lib
155
+
156
+ else:
157
+ if device_name == "gpu":
158
+ # GPU library is optional - provide helpful message
159
+ raise RuntimeError(
160
+ f"GPU library not available. The shared object {filepath_library} does not exist.\n"
161
+ f"GPU support is optional. You can still use device='cpu' for CPU-based beamforming.\n"
162
+ f"To build GPU support, ensure CUDA is installed and rebuild the package."
163
+ )
164
+ else:
165
+ # CPU library is required
166
+ raise OSError(
167
+ f"CPU library is required but not found at {filepath_library}.\n"
168
+ f"This package requires pre-built binaries. Please reinstall: pip install --upgrade beampower"
169
+ )
Binary file