requake 0.8.2__tar.gz → 0.8.3__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.
- {requake-0.8.2 → requake-0.8.3}/CHANGELOG.md +20 -0
- {requake-0.8.2 → requake-0.8.3}/PKG-INFO +19 -3
- {requake-0.8.2 → requake-0.8.3}/README.md +17 -1
- {requake-0.8.2 → requake-0.8.3}/requake/_version.py +3 -3
- {requake-0.8.2 → requake-0.8.3}/requake/config/configspec.conf +11 -1
- {requake-0.8.2 → requake-0.8.3}/requake/config/parse_arguments.py +173 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/rq_setup.py +13 -15
- {requake-0.8.2 → requake-0.8.3}/requake/config/utils.py +31 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/families.py +26 -8
- {requake-0.8.2 → requake-0.8.3}/requake/database/pairs.py +37 -15
- {requake-0.8.2 → requake-0.8.3}/requake/families/families.py +9 -5
- {requake-0.8.2 → requake-0.8.3}/requake/main.py +15 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/colormaps.py +13 -13
- {requake-0.8.2 → requake-0.8.3}/requake/plot/plot_families.py +4 -1
- requake-0.8.3/requake/scan/parallel_diagnostics.py +273 -0
- {requake-0.8.2 → requake-0.8.3}/requake/scan/scan_catalog.py +51 -38
- requake-0.8.3/requake/scan/scan_catalog_helpers.py +359 -0
- {requake-0.8.2 → requake-0.8.3}/requake/scan/scan_catalog_pairs.py +34 -42
- {requake-0.8.2 → requake-0.8.3}/requake/scan/scan_catalog_workers.py +332 -77
- requake-0.8.3/requake/scan/slurm_diagnostics.py +130 -0
- {requake-0.8.2 → requake-0.8.3}/requake/waveforms/station_metadata.py +85 -1
- {requake-0.8.2 → requake-0.8.3}/requake/waveforms/waveform_pair.py +72 -2
- {requake-0.8.2 → requake-0.8.3}/requake/waveforms/waveforms.py +126 -67
- requake-0.8.3/requake/wfcache/__init__.py +35 -0
- requake-0.8.3/requake/wfcache/commands.py +919 -0
- requake-0.8.3/requake/wfcache/storage.py +765 -0
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/PKG-INFO +19 -3
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/SOURCES.txt +6 -1
- requake-0.8.2/requake/scan/scan_catalog_helpers.py +0 -317
- {requake-0.8.2 → requake-0.8.3}/CONTRIBUTORS.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/LICENSE.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/MANIFEST.in +0 -0
- {requake-0.8.2 → requake-0.8.3}/pyproject.toml +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/catalog.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/print_catalog.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/read_catalog.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/read_catalog_from_csv.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/read_catalog_from_fdsnws.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/catalog/read_catalog_from_quakeml.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/config.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/configobj/LICENSE +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/configobj/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/configobj/_version.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/configobj/validate.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/config/generic_printer.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/catalog.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/db.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/templates.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/database/trace_metadata.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/build_families.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/build_templates.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/flag_family.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/pairs.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/print_families.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/families/print_pairs.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/formulas/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/formulas/conversion.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/formulas/moment.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/formulas/slip.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/cached_tiler.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/map_families.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/map_tiles.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/plot_cumulative.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/plot_pair.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/plot_timespans.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/plot/plot_utils.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/scan/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/scan/scan_templates.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/waveforms/__init__.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake/waveforms/arrivals.py +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/dependency_links.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/entry_points.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/requires.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/requake.egg-info/top_level.txt +0 -0
- {requake-0.8.2 → requake-0.8.3}/setup.cfg +0 -0
- {requake-0.8.2 → requake-0.8.3}/setup.py +0 -0
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2021-2026 Claudio Satriano <satriano@ipgp.fr>
|
|
4
4
|
|
|
5
|
+
## [0.8.3] - 2026-06-19
|
|
6
|
+
|
|
7
|
+
Note: you should run `requake update_config` to update your config file
|
|
8
|
+
to the latest version.
|
|
9
|
+
|
|
10
|
+
The waveform disk cache is now a single SQLite database
|
|
11
|
+
(`OUTDIR/waveform_cache.sqlite`) with a persistent negative cache that
|
|
12
|
+
remembers failed downloads across runs, eliminating redundant HTTP requests.
|
|
13
|
+
|
|
14
|
+
- New `wfcache` CLI: `prefetch`, `print`/`inspect`, `extract`,
|
|
15
|
+
`reset-failures`.
|
|
16
|
+
- New config: `catalog_waveform_cache_failure_max_retries`,
|
|
17
|
+
`catalog_waveform_cache_failure_backoff_s`,
|
|
18
|
+
`catalog_waveform_require_prefetch`.
|
|
19
|
+
- Several optimization for parallel runs in `scan_catalog` (especially on
|
|
20
|
+
Slurm clusters) and for startup time of `scan_catalog -c` (continue an
|
|
21
|
+
interrupted scan).
|
|
22
|
+
- Extended documentation for `read_catalog`, `scan_catalog` and `wfcache`.
|
|
23
|
+
|
|
5
24
|
## [0.8.2] - 2026-06-03
|
|
6
25
|
|
|
7
26
|
Note: you might want to run `requake update_config` to update your config file
|
|
@@ -187,6 +206,7 @@ Mostly a bugfix release with a slightly improved trace plotting.
|
|
|
187
206
|
|
|
188
207
|
- Initial release, not yet feature complete
|
|
189
208
|
|
|
209
|
+
[0.8.3]: https://github.com/SeismicSource/requake/compare/v0.8.2...v0.8.3
|
|
190
210
|
[0.8.2]: https://github.com/SeismicSource/requake/compare/v0.8.1...v0.8.2
|
|
191
211
|
[0.8.1]: https://github.com/SeismicSource/requake/compare/v0.8...v0.8.1
|
|
192
212
|
[0.8]: https://github.com/SeismicSource/requake/compare/v0.7.3...v0.8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: requake
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
4
4
|
Summary: Repeating earthquakes search and analysis
|
|
5
5
|
Author-email: Claudio Satriano <satriano@ipgp.fr>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -37,7 +37,7 @@ Dynamic: description
|
|
|
37
37
|
Dynamic: description-content-type
|
|
38
38
|
Dynamic: license-file
|
|
39
39
|
|
|
40
|
-
<img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@
|
|
40
|
+
<img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@407e6c96ec3574f6e3743741763ca5b7c4daf914/imgs/Requake_logo.svg" width="400">
|
|
41
41
|
|
|
42
42
|
# Requake
|
|
43
43
|
|
|
@@ -181,6 +181,8 @@ Different commands are available:
|
|
|
181
181
|
families
|
|
182
182
|
scan_templates scan a continuous waveform stream using one or more
|
|
183
183
|
templates
|
|
184
|
+
wfcache manage persistent waveform cache (prefetch, print,
|
|
185
|
+
inspect, extract, reset_failures)
|
|
184
186
|
|
|
185
187
|
Certain commands (e.g., `plot_pair`) require further arguments (use, e.g.,
|
|
186
188
|
`requake plot_pair -h` to get help).
|
|
@@ -216,6 +218,14 @@ Now, build the catalog of event pairs with:
|
|
|
216
218
|
|
|
217
219
|
requake scan_catalog
|
|
218
220
|
|
|
221
|
+
When relying on FDSN web services for waveform data, it is strongly
|
|
222
|
+
recommended to prefetch all waveform windows before running the scan.
|
|
223
|
+
This downloads every required waveform once and stores it in a local
|
|
224
|
+
SQLite cache, avoiding repeated downloads and dramatically reducing
|
|
225
|
+
overall runtime for large catalogs:
|
|
226
|
+
|
|
227
|
+
requake wfcache prefetch
|
|
228
|
+
|
|
219
229
|
Once done ([it will take time!](#performances)), you are ready to build
|
|
220
230
|
repeating earthquake families:
|
|
221
231
|
|
|
@@ -232,6 +242,12 @@ repeating earthquake families:
|
|
|
232
242
|
performance is typically dominated by waveform download latency rather than
|
|
233
243
|
computation.
|
|
234
244
|
|
|
245
|
+
- For large FDSN-based runs, use `requake wfcache prefetch` before
|
|
246
|
+
`requake scan_catalog` to download all waveforms upfront into a local
|
|
247
|
+
SQLite cache. This eliminates repeated downloads, lets the scan read
|
|
248
|
+
exclusively from disk, and is the single most effective way to speed up
|
|
249
|
+
catalog scanning.
|
|
250
|
+
|
|
235
251
|
- `requake build_families` is fast™.
|
|
236
252
|
|
|
237
253
|
## How to Cite
|
|
@@ -244,7 +260,7 @@ If you used Requake for a scientific paper, please cite it as:
|
|
|
244
260
|
Please replace `X.Y` with the Requake version number you used.
|
|
245
261
|
|
|
246
262
|
You can also cite the following abstract presented at the
|
|
247
|
-
|
|
263
|
+
2021 AGU Fall Meeting:
|
|
248
264
|
|
|
249
265
|
> Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
|
|
250
266
|
> Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
|
|
@@ -142,6 +142,8 @@ Different commands are available:
|
|
|
142
142
|
families
|
|
143
143
|
scan_templates scan a continuous waveform stream using one or more
|
|
144
144
|
templates
|
|
145
|
+
wfcache manage persistent waveform cache (prefetch, print,
|
|
146
|
+
inspect, extract, reset_failures)
|
|
145
147
|
|
|
146
148
|
Certain commands (e.g., `plot_pair`) require further arguments (use, e.g.,
|
|
147
149
|
`requake plot_pair -h` to get help).
|
|
@@ -177,6 +179,14 @@ Now, build the catalog of event pairs with:
|
|
|
177
179
|
|
|
178
180
|
requake scan_catalog
|
|
179
181
|
|
|
182
|
+
When relying on FDSN web services for waveform data, it is strongly
|
|
183
|
+
recommended to prefetch all waveform windows before running the scan.
|
|
184
|
+
This downloads every required waveform once and stores it in a local
|
|
185
|
+
SQLite cache, avoiding repeated downloads and dramatically reducing
|
|
186
|
+
overall runtime for large catalogs:
|
|
187
|
+
|
|
188
|
+
requake wfcache prefetch
|
|
189
|
+
|
|
180
190
|
Once done ([it will take time!](#performances)), you are ready to build
|
|
181
191
|
repeating earthquake families:
|
|
182
192
|
|
|
@@ -193,6 +203,12 @@ repeating earthquake families:
|
|
|
193
203
|
performance is typically dominated by waveform download latency rather than
|
|
194
204
|
computation.
|
|
195
205
|
|
|
206
|
+
- For large FDSN-based runs, use `requake wfcache prefetch` before
|
|
207
|
+
`requake scan_catalog` to download all waveforms upfront into a local
|
|
208
|
+
SQLite cache. This eliminates repeated downloads, lets the scan read
|
|
209
|
+
exclusively from disk, and is the single most effective way to speed up
|
|
210
|
+
catalog scanning.
|
|
211
|
+
|
|
196
212
|
- `requake build_families` is fast™.
|
|
197
213
|
|
|
198
214
|
## How to Cite
|
|
@@ -205,7 +221,7 @@ If you used Requake for a scientific paper, please cite it as:
|
|
|
205
221
|
Please replace `X.Y` with the Requake version number you used.
|
|
206
222
|
|
|
207
223
|
You can also cite the following abstract presented at the
|
|
208
|
-
|
|
224
|
+
2021 AGU Fall Meeting:
|
|
209
225
|
|
|
210
226
|
> Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
|
|
211
227
|
> Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-06-
|
|
11
|
+
"date": "2026-06-19T10:48:17+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.8.
|
|
14
|
+
"full-revisionid": "407e6c96ec3574f6e3743741763ca5b7c4daf914",
|
|
15
|
+
"version": "0.8.3"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -76,9 +76,19 @@ catalog_waveform_cache_size = integer(min=2, default=5000)
|
|
|
76
76
|
## Set to 0 to derive it automatically from catalog_waveform_cache_size.
|
|
77
77
|
catalog_waveform_cache_size_parallel = integer(min=0, default=0)
|
|
78
78
|
## Keep a persistent copy of fetched event waveforms on disk to speed up
|
|
79
|
-
## reruns. This on-disk cache lives in OUTDIR/waveform_cache and is
|
|
79
|
+
## reruns. This on-disk cache lives in OUTDIR/waveform_cache.sqlite and is
|
|
80
80
|
## limited by catalog_waveform_cache_size.
|
|
81
81
|
catalog_waveform_disk_cache_enabled = boolean(default=true)
|
|
82
|
+
## Maximum number of retries for a failed waveform download before
|
|
83
|
+
## suppressing new attempts for the same cache key.
|
|
84
|
+
catalog_waveform_cache_failure_max_retries = integer(min=0, default=3)
|
|
85
|
+
## Base backoff in seconds for persistent waveform download failures.
|
|
86
|
+
## The retry delay grows exponentially with each failure.
|
|
87
|
+
catalog_waveform_cache_failure_backoff_s = float(min=0, default=600)
|
|
88
|
+
## When True, skip live downloads for events absent from both positive
|
|
89
|
+
## and negative cache, trusting a prior prefetch run to have covered
|
|
90
|
+
## all fetchable events. Requires disk cache to be enabled.
|
|
91
|
+
catalog_waveform_require_prefetch = boolean(default=false)
|
|
82
92
|
## Number of worker processes for catalog scan.
|
|
83
93
|
## 0 means automatic selection; 1 disables parallelism.
|
|
84
94
|
catalog_scan_nprocs = integer(min=0, default=0)
|
|
@@ -498,6 +498,179 @@ def parse_arguments(progname='requake'):
|
|
|
498
498
|
'"A" header field'
|
|
499
499
|
)
|
|
500
500
|
# ---
|
|
501
|
+
# --- wfcache
|
|
502
|
+
wfcache = subparser.add_parser(
|
|
503
|
+
'wfcache',
|
|
504
|
+
help='manage persistent waveform cache'
|
|
505
|
+
)
|
|
506
|
+
wfcache_sub = wfcache.add_subparsers(dest='wfcache_action')
|
|
507
|
+
wfcache_sub.required = True
|
|
508
|
+
|
|
509
|
+
wfcache_row_filters = argparse.ArgumentParser(add_help=False)
|
|
510
|
+
wfcache_row_filters.add_argument(
|
|
511
|
+
'--event-id',
|
|
512
|
+
action='append',
|
|
513
|
+
default=[],
|
|
514
|
+
help='event ID filter (repeatable)'
|
|
515
|
+
)
|
|
516
|
+
wfcache_row_filters.add_argument(
|
|
517
|
+
'--event-id-file',
|
|
518
|
+
type=str,
|
|
519
|
+
default=None,
|
|
520
|
+
help='path to text file with event IDs'
|
|
521
|
+
)
|
|
522
|
+
wfcache_row_filters.add_argument(
|
|
523
|
+
'--trace-id',
|
|
524
|
+
action='append',
|
|
525
|
+
default=[],
|
|
526
|
+
help='trace-ID filter (repeatable)'
|
|
527
|
+
)
|
|
528
|
+
wfcache_row_filters.add_argument(
|
|
529
|
+
'--start-time',
|
|
530
|
+
type=str,
|
|
531
|
+
default=None,
|
|
532
|
+
help='minimum start time (UTCDateTime-compatible string)'
|
|
533
|
+
)
|
|
534
|
+
wfcache_row_filters.add_argument(
|
|
535
|
+
'--end-time',
|
|
536
|
+
type=str,
|
|
537
|
+
default=None,
|
|
538
|
+
help='maximum end time (UTCDateTime-compatible string)'
|
|
539
|
+
)
|
|
540
|
+
wfcache_row_filters.add_argument(
|
|
541
|
+
'--limit',
|
|
542
|
+
type=_nonnegative_int,
|
|
543
|
+
default=None,
|
|
544
|
+
help='limit number of returned rows'
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
wfcache_prefetch = wfcache_sub.add_parser(
|
|
548
|
+
'prefetch',
|
|
549
|
+
help='prefetch waveform windows into the persistent cache'
|
|
550
|
+
)
|
|
551
|
+
wfcache_prefetch.add_argument(
|
|
552
|
+
'--event-id',
|
|
553
|
+
action='append',
|
|
554
|
+
default=[],
|
|
555
|
+
help='event ID filter (repeatable)'
|
|
556
|
+
)
|
|
557
|
+
wfcache_prefetch.add_argument(
|
|
558
|
+
'--event-id-file',
|
|
559
|
+
type=str,
|
|
560
|
+
default=None,
|
|
561
|
+
help='path to text file with event IDs'
|
|
562
|
+
)
|
|
563
|
+
wfcache_prefetch.add_argument(
|
|
564
|
+
'--trace-id',
|
|
565
|
+
action='append',
|
|
566
|
+
default=[],
|
|
567
|
+
help='trace-ID filter (repeatable)'
|
|
568
|
+
)
|
|
569
|
+
wfcache_prefetch.add_argument(
|
|
570
|
+
'--max-events',
|
|
571
|
+
type=_nonnegative_int,
|
|
572
|
+
default=None,
|
|
573
|
+
help='maximum number of catalog events to prefetch'
|
|
574
|
+
)
|
|
575
|
+
wfcache_prefetch.add_argument(
|
|
576
|
+
'--batch-size',
|
|
577
|
+
type=_nonnegative_int,
|
|
578
|
+
default=500,
|
|
579
|
+
help='progress logging batch size (default: %(default)s)'
|
|
580
|
+
)
|
|
581
|
+
wfcache_prefetch.add_argument(
|
|
582
|
+
'--group-window',
|
|
583
|
+
type=str,
|
|
584
|
+
default='1h',
|
|
585
|
+
metavar='DURATION',
|
|
586
|
+
help='max grouped download span (e.g., 30m, 1h; default: '
|
|
587
|
+
'%(default)s)'
|
|
588
|
+
)
|
|
589
|
+
wfcache_prefetch.set_defaults(action='wfcache_prefetch')
|
|
590
|
+
|
|
591
|
+
wfcache_print = wfcache_sub.add_parser(
|
|
592
|
+
'print',
|
|
593
|
+
parents=[wfcache_row_filters],
|
|
594
|
+
help='print cached waveform rows'
|
|
595
|
+
)
|
|
596
|
+
wfcache_print.add_argument(
|
|
597
|
+
'--json',
|
|
598
|
+
action='store_true',
|
|
599
|
+
help='print rows as JSON'
|
|
600
|
+
)
|
|
601
|
+
wfcache_print.set_defaults(action='wfcache_print')
|
|
602
|
+
|
|
603
|
+
wfcache_inspect = wfcache_sub.add_parser(
|
|
604
|
+
'inspect',
|
|
605
|
+
help='print persistent waveform-cache summary'
|
|
606
|
+
)
|
|
607
|
+
wfcache_inspect.add_argument(
|
|
608
|
+
'--integrity',
|
|
609
|
+
action='store_true',
|
|
610
|
+
help='run PRAGMA integrity_check'
|
|
611
|
+
)
|
|
612
|
+
wfcache_inspect.add_argument(
|
|
613
|
+
'--json',
|
|
614
|
+
action='store_true',
|
|
615
|
+
help='print summary as JSON'
|
|
616
|
+
)
|
|
617
|
+
wfcache_inspect.set_defaults(action='wfcache_inspect')
|
|
618
|
+
|
|
619
|
+
wfcache_extract = wfcache_sub.add_parser(
|
|
620
|
+
'extract',
|
|
621
|
+
parents=[wfcache_row_filters],
|
|
622
|
+
help='extract cached waveforms to external files'
|
|
623
|
+
)
|
|
624
|
+
wfcache_extract.add_argument(
|
|
625
|
+
'--format',
|
|
626
|
+
type=str,
|
|
627
|
+
choices=['mseed', 'sac'],
|
|
628
|
+
default='mseed',
|
|
629
|
+
help='output format (default: %(default)s)'
|
|
630
|
+
)
|
|
631
|
+
wfcache_extract.add_argument(
|
|
632
|
+
'--output-dir',
|
|
633
|
+
type=str,
|
|
634
|
+
default='waveform_cache',
|
|
635
|
+
help='output directory (default: %(default)s)'
|
|
636
|
+
)
|
|
637
|
+
wfcache_extract.set_defaults(action='wfcache_extract')
|
|
638
|
+
|
|
639
|
+
wfcache_reset = wfcache_sub.add_parser(
|
|
640
|
+
'reset-failures',
|
|
641
|
+
help='reset persistent waveform failure-cache rows'
|
|
642
|
+
)
|
|
643
|
+
wfcache_reset.add_argument(
|
|
644
|
+
'--event-id',
|
|
645
|
+
action='append',
|
|
646
|
+
default=[],
|
|
647
|
+
help='event ID to reset (repeatable)'
|
|
648
|
+
)
|
|
649
|
+
wfcache_reset.add_argument(
|
|
650
|
+
'--event-id-file',
|
|
651
|
+
type=str,
|
|
652
|
+
default=None,
|
|
653
|
+
help='path to text file with event IDs to reset'
|
|
654
|
+
)
|
|
655
|
+
wfcache_reset.add_argument(
|
|
656
|
+
'--all',
|
|
657
|
+
action='store_true',
|
|
658
|
+
help='reset all failure rows'
|
|
659
|
+
)
|
|
660
|
+
wfcache_reset.add_argument(
|
|
661
|
+
'--older-than',
|
|
662
|
+
type=str,
|
|
663
|
+
default=None,
|
|
664
|
+
metavar='DURATION',
|
|
665
|
+
help='reset rows older than DURATION (e.g., 6h, 7d)'
|
|
666
|
+
)
|
|
667
|
+
wfcache_reset.add_argument(
|
|
668
|
+
'--dry-run',
|
|
669
|
+
action='store_true',
|
|
670
|
+
help='show affected rows without deleting'
|
|
671
|
+
)
|
|
672
|
+
wfcache_reset.set_defaults(action='wfcache_reset_failures')
|
|
673
|
+
# ---
|
|
501
674
|
argcomplete.autocomplete(parser)
|
|
502
675
|
args = parser.parse_args()
|
|
503
676
|
if args.action is None:
|
|
@@ -28,6 +28,12 @@ from .utils import (
|
|
|
28
28
|
# Note: modules are lazily imported to speed up the startup time.
|
|
29
29
|
# pylint: disable=relative-beyond-top-level,import-outside-toplevel
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
def _memory_log_filter(record):
|
|
33
|
+
"""Suppress [MEM] messages from TTY console handlers."""
|
|
34
|
+
return '[MEM]' not in record.getMessage()
|
|
35
|
+
|
|
36
|
+
|
|
31
37
|
logger = None # pylint: disable=invalid-name
|
|
32
38
|
PYTHON_VERSION_STR = None
|
|
33
39
|
NUMPY_VERSION_STR = None
|
|
@@ -112,6 +118,7 @@ def _setup_tqdm_logging(logger_root):
|
|
|
112
118
|
self.handleError(record)
|
|
113
119
|
console = TqdmLoggingHandler()
|
|
114
120
|
console.setLevel(logging.INFO)
|
|
121
|
+
console.addFilter(_memory_log_filter)
|
|
115
122
|
# Add logger color coding on all platforms but win32
|
|
116
123
|
if sys.platform != 'win32' and sys.stdout.isatty():
|
|
117
124
|
console.emit = _color_handler_emit(console.emit)
|
|
@@ -172,23 +179,13 @@ def _connect_station_dataselect():
|
|
|
172
179
|
Connect to station and dataselect services.
|
|
173
180
|
|
|
174
181
|
Those can be either FDSN web services or local files.
|
|
182
|
+
Both FDSN clients are created lazily on first use to avoid
|
|
183
|
+
unnecessary network I/O when data comes from local sources.
|
|
175
184
|
"""
|
|
176
|
-
|
|
177
|
-
if config.station_metadata_path is None:
|
|
178
|
-
config.station_client = FDSNClient(config.fdsn_station_url)
|
|
179
|
-
logger.info(
|
|
180
|
-
f'Connected to FDSN station server: {config.fdsn_station_url}'
|
|
181
|
-
)
|
|
185
|
+
config.station_client = None
|
|
182
186
|
config.dataselect_client = None
|
|
183
187
|
if config.sds_data_path is not None:
|
|
184
188
|
_connect_sds()
|
|
185
|
-
if config.event_data_path is not None:
|
|
186
|
-
return
|
|
187
|
-
config.dataselect_client = FDSNClient(config.fdsn_dataselect_url)
|
|
188
|
-
logger.info(
|
|
189
|
-
'Connected to FDSN dataselect server: '
|
|
190
|
-
f'{config.fdsn_dataselect_url}'
|
|
191
|
-
)
|
|
192
189
|
|
|
193
190
|
|
|
194
191
|
def _connect_sds():
|
|
@@ -267,6 +264,7 @@ def configure(args):
|
|
|
267
264
|
# update config with the contents of config_obj
|
|
268
265
|
config.update(config_obj)
|
|
269
266
|
config.args = args
|
|
267
|
+
config.args.outdir = os.path.abspath(config.args.outdir)
|
|
270
268
|
config.scan_catalog_pairs_file = os.path.join(
|
|
271
269
|
config.args.outdir, 'requake.event_pairs.csv'
|
|
272
270
|
)
|
|
@@ -293,14 +291,14 @@ def configure(args):
|
|
|
293
291
|
# save config to output dir (only for actions that write to outdir)
|
|
294
292
|
actions_writing_to_outdir = (
|
|
295
293
|
'read_catalog', 'scan_catalog', 'build_families',
|
|
296
|
-
'build_templates', 'scan_templates'
|
|
294
|
+
'build_templates', 'scan_templates', 'wfcache_prefetch'
|
|
297
295
|
)
|
|
298
296
|
if args.action in actions_writing_to_outdir:
|
|
299
297
|
shutil.copy(args.configfile, args.outdir)
|
|
300
298
|
_parse_catalog_options()
|
|
301
299
|
actions_needing_fdsn_station_dataselect = (
|
|
302
300
|
'scan_catalog', 'plot_pair', 'plot_families', 'build_templates',
|
|
303
|
-
'scan_templates'
|
|
301
|
+
'scan_templates', 'wfcache_prefetch'
|
|
304
302
|
)
|
|
305
303
|
try:
|
|
306
304
|
if args.action in actions_needing_fdsn_station_dataselect:
|
|
@@ -13,12 +13,43 @@ import os
|
|
|
13
13
|
import sys
|
|
14
14
|
import locale
|
|
15
15
|
import shutil
|
|
16
|
+
import hashlib
|
|
16
17
|
from datetime import datetime
|
|
17
18
|
from .configobj import ConfigObj, ParseError
|
|
18
19
|
from .configobj.validate import Validator
|
|
19
20
|
locale.setlocale(locale.LC_ALL, '')
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
_seen_msgs = set()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def log_once(logger, level, msg, *args, **kwargs):
|
|
27
|
+
"""
|
|
28
|
+
Log a message only once.
|
|
29
|
+
|
|
30
|
+
:param logger: Logger object.
|
|
31
|
+
:type logger: logging.Logger
|
|
32
|
+
:param level: Log level.
|
|
33
|
+
:type level: str
|
|
34
|
+
:param msg: Message to log.
|
|
35
|
+
:type msg: str
|
|
36
|
+
:param args: Arguments for the message.
|
|
37
|
+
:param kwargs: Keyword arguments for the message.
|
|
38
|
+
"""
|
|
39
|
+
text = str(msg)
|
|
40
|
+
digest = hashlib.blake2b(
|
|
41
|
+
text.encode('utf-8'),
|
|
42
|
+
digest_size=8,
|
|
43
|
+
).digest()
|
|
44
|
+
if digest in _seen_msgs:
|
|
45
|
+
return
|
|
46
|
+
_seen_msgs.add(digest)
|
|
47
|
+
log_fn = getattr(logger, level, None)
|
|
48
|
+
if log_fn is None:
|
|
49
|
+
raise ValueError(f'Invalid log level: {level}')
|
|
50
|
+
log_fn(text, *args, **kwargs)
|
|
51
|
+
|
|
52
|
+
|
|
22
53
|
def err_exit(msg):
|
|
23
54
|
"""
|
|
24
55
|
Exit with error message.
|
|
@@ -95,8 +95,14 @@ def write_families(families):
|
|
|
95
95
|
conn.close()
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def read_families():
|
|
99
|
-
"""Read catalog-scan families from SQLite.
|
|
98
|
+
def read_families(family_numbers=None):
|
|
99
|
+
"""Read catalog-scan families from SQLite.
|
|
100
|
+
|
|
101
|
+
:param family_numbers: If given, only read families with these numbers.
|
|
102
|
+
:type family_numbers: iterable of int or None
|
|
103
|
+
:return: List of families.
|
|
104
|
+
:rtype: list of Family
|
|
105
|
+
"""
|
|
100
106
|
from ..catalog import RequakeEvent
|
|
101
107
|
from ..families.families import Family
|
|
102
108
|
|
|
@@ -104,12 +110,24 @@ def read_families():
|
|
|
104
110
|
try:
|
|
105
111
|
cursor = conn.cursor()
|
|
106
112
|
try:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
if family_numbers is not None:
|
|
114
|
+
family_numbers = sorted(set(family_numbers))
|
|
115
|
+
placeholders = ','.join('?' * len(family_numbers))
|
|
116
|
+
rows = cursor.execute(
|
|
117
|
+
f'''
|
|
118
|
+
SELECT * FROM {FAMILIES_TABLE}
|
|
119
|
+
WHERE family_number IN ({placeholders})
|
|
120
|
+
ORDER BY family_number, orig_time, evid, trace_id
|
|
121
|
+
''',
|
|
122
|
+
family_numbers,
|
|
123
|
+
).fetchall()
|
|
124
|
+
else:
|
|
125
|
+
rows = cursor.execute(
|
|
126
|
+
f'''
|
|
127
|
+
SELECT * FROM {FAMILIES_TABLE}
|
|
128
|
+
ORDER BY family_number, orig_time, evid, trace_id
|
|
129
|
+
'''
|
|
130
|
+
).fetchall()
|
|
113
131
|
except sqlite3.OperationalError as err:
|
|
114
132
|
if MISSING_FAMILIES_TABLE in str(err):
|
|
115
133
|
raise FileNotFoundError(
|
|
@@ -315,13 +315,19 @@ def write_pair_records(pairs, append=True):
|
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def count_pairs():
|
|
318
|
-
"""Return the number of stored event pairs.
|
|
318
|
+
"""Return the number of stored event pairs.
|
|
319
|
+
|
|
320
|
+
Uses ``SELECT 1 ... LIMIT 1`` instead of ``COUNT(*)`` because
|
|
321
|
+
the latter performs a full table scan, which is prohibitively
|
|
322
|
+
slow (20+ minutes for 300M rows) on network filesystems such
|
|
323
|
+
as GPFS.
|
|
324
|
+
"""
|
|
319
325
|
conn = get_db_connection(initdb=False)
|
|
320
326
|
try:
|
|
321
327
|
cursor = conn.cursor()
|
|
322
328
|
try:
|
|
323
329
|
row = cursor.execute(
|
|
324
|
-
f'SELECT
|
|
330
|
+
f'SELECT 1 FROM {EVENT_PAIRS_TABLE} LIMIT 1'
|
|
325
331
|
).fetchone()
|
|
326
332
|
except sqlite3.OperationalError as err:
|
|
327
333
|
if _is_missing_pairs_table_error(err):
|
|
@@ -329,7 +335,7 @@ def count_pairs():
|
|
|
329
335
|
raise
|
|
330
336
|
finally:
|
|
331
337
|
conn.close()
|
|
332
|
-
return
|
|
338
|
+
return 1 if row is not None else 0
|
|
333
339
|
|
|
334
340
|
|
|
335
341
|
def read_event_key_rows():
|
|
@@ -343,7 +349,7 @@ def read_event_key_rows():
|
|
|
343
349
|
SELECT event_id, evid
|
|
344
350
|
FROM {EVENT_KEYS_TABLE}
|
|
345
351
|
'''
|
|
346
|
-
)
|
|
352
|
+
)
|
|
347
353
|
except sqlite3.OperationalError as err:
|
|
348
354
|
if _is_missing_pairs_table_error(err):
|
|
349
355
|
return []
|
|
@@ -358,18 +364,25 @@ def read_event_key_rows():
|
|
|
358
364
|
return [(int(row['event_id']), row['evid']) for row in rows]
|
|
359
365
|
|
|
360
366
|
|
|
361
|
-
def
|
|
362
|
-
"""
|
|
367
|
+
def read_packed_pair_ids(event_id_to_idx, nevents):
|
|
368
|
+
"""Stream pair keys from DB and return packed ``uint64`` IDs.
|
|
369
|
+
|
|
370
|
+
Reads ``(event1_id, event2_id)`` rows from the database and
|
|
371
|
+
packs them into a set of canonical ``uint64`` values in a
|
|
372
|
+
single pass, avoiding the intermediate tuple set.
|
|
373
|
+
|
|
374
|
+
:param event_id_to_idx: mapping from ``event_id`` to catalog index
|
|
375
|
+
:param nevents: number of events in the catalog
|
|
376
|
+
:return: set of packed IDs, each computed as
|
|
377
|
+
``min(idx1, idx2) * nevents + max(idx1, idx2)``
|
|
378
|
+
"""
|
|
363
379
|
conn = get_db_connection(initdb=False)
|
|
364
380
|
try:
|
|
365
381
|
cursor = conn.cursor()
|
|
366
382
|
try:
|
|
367
383
|
rows = cursor.execute(
|
|
368
|
-
f''
|
|
369
|
-
|
|
370
|
-
FROM {EVENT_PAIRS_TABLE}
|
|
371
|
-
'''
|
|
372
|
-
).fetchall()
|
|
384
|
+
f'SELECT event1_id, event2_id FROM {EVENT_PAIRS_TABLE}'
|
|
385
|
+
)
|
|
373
386
|
except sqlite3.OperationalError as err:
|
|
374
387
|
if _is_missing_pairs_table_error(err):
|
|
375
388
|
return set()
|
|
@@ -379,12 +392,21 @@ def read_pair_key_ids():
|
|
|
379
392
|
f'Requake version in db file {get_db_path()}'
|
|
380
393
|
) from err
|
|
381
394
|
raise
|
|
395
|
+
get_idx = event_id_to_idx.get
|
|
396
|
+
result = set()
|
|
397
|
+
add_id = result.add
|
|
398
|
+
for event1_id, event2_id in rows:
|
|
399
|
+
idx1 = get_idx(int(event1_id))
|
|
400
|
+
idx2 = get_idx(int(event2_id))
|
|
401
|
+
if idx1 is None or idx2 is None:
|
|
402
|
+
continue
|
|
403
|
+
if idx1 < idx2:
|
|
404
|
+
add_id(idx1 * nevents + idx2)
|
|
405
|
+
else:
|
|
406
|
+
add_id(idx2 * nevents + idx1)
|
|
407
|
+
return result
|
|
382
408
|
finally:
|
|
383
409
|
conn.close()
|
|
384
|
-
return {
|
|
385
|
-
(int(row['event1_id']), int(row['event2_id']))
|
|
386
|
-
for row in rows
|
|
387
|
-
}
|
|
388
410
|
|
|
389
411
|
|
|
390
412
|
def read_pairs(cc_min=None, cc_max=None):
|
|
@@ -151,15 +151,17 @@ class Family(list):
|
|
|
151
151
|
return distance / 1e3
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def _read_families_from_catalog_scan():
|
|
154
|
+
def _read_families_from_catalog_scan(family_numbers=None):
|
|
155
155
|
"""
|
|
156
156
|
Read a list of families from the catalog scan output.
|
|
157
157
|
|
|
158
|
+
:param family_numbers: If given, only read families with these numbers.
|
|
159
|
+
:type family_numbers: iterable of int or None
|
|
158
160
|
:return: List of families.
|
|
159
161
|
:rtype: list of Family
|
|
160
162
|
"""
|
|
161
163
|
from ..database.families import read_families as read_families_from_db
|
|
162
|
-
return read_families_from_db()
|
|
164
|
+
return read_families_from_db(family_numbers=family_numbers)
|
|
163
165
|
|
|
164
166
|
|
|
165
167
|
def _read_families_from_template_scan():
|
|
@@ -175,16 +177,18 @@ def _read_families_from_template_scan():
|
|
|
175
177
|
return read_template_families_from_db()
|
|
176
178
|
|
|
177
179
|
|
|
178
|
-
def read_families():
|
|
180
|
+
def read_families(family_numbers=None):
|
|
179
181
|
"""
|
|
180
182
|
Read families from the catalog scan output or template scan output.
|
|
181
183
|
|
|
184
|
+
:param family_numbers: If given, only read families with these numbers.
|
|
185
|
+
:type family_numbers: iterable of int or None
|
|
182
186
|
:return: List of families.
|
|
183
187
|
:rtype: list of Family
|
|
184
188
|
"""
|
|
185
189
|
if getattr(config.args, 'template', False):
|
|
186
190
|
return _read_families_from_template_scan()
|
|
187
|
-
return _read_families_from_catalog_scan()
|
|
191
|
+
return _read_families_from_catalog_scan(family_numbers=family_numbers)
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
def read_selected_families():
|
|
@@ -197,7 +201,7 @@ def read_selected_families():
|
|
|
197
201
|
:raises FamilyNotFoundError: if no family is found
|
|
198
202
|
"""
|
|
199
203
|
family_numbers = _build_family_number_list()
|
|
200
|
-
families = read_families()
|
|
204
|
+
families = read_families(family_numbers=family_numbers)
|
|
201
205
|
families_selected = []
|
|
202
206
|
for family in families:
|
|
203
207
|
if family.number not in family_numbers:
|