toshi-hazard-post 0.7.1__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.
- scripts/__init__.py +0 -0
- scripts/calc_time.py +34 -0
- scripts/cli.py +36 -0
- scripts/cli_old.py +213 -0
- scripts/container_task.sh +5 -0
- toshi_hazard_post/__init__.py +6 -0
- toshi_hazard_post/aggregation.py +174 -0
- toshi_hazard_post/aggregation_args.py +215 -0
- toshi_hazard_post/aggregation_calc.py +286 -0
- toshi_hazard_post/aggregation_setup.py +122 -0
- toshi_hazard_post/calculators.py +97 -0
- toshi_hazard_post/constants.py +4 -0
- toshi_hazard_post/data.py +186 -0
- toshi_hazard_post/local_config.py +44 -0
- toshi_hazard_post/logic_tree.py +211 -0
- toshi_hazard_post-0.7.1.dist-info/LICENSE +21 -0
- toshi_hazard_post-0.7.1.dist-info/METADATA +65 -0
- toshi_hazard_post-0.7.1.dist-info/RECORD +20 -0
- toshi_hazard_post-0.7.1.dist-info/WHEEL +4 -0
- toshi_hazard_post-0.7.1.dist-info/entry_points.txt +3 -0
scripts/__init__.py
ADDED
|
File without changes
|
scripts/calc_time.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def main(log_filepath):
|
|
5
|
+
log_filepath = Path(log_filepath)
|
|
6
|
+
|
|
7
|
+
times_jobtable = []
|
|
8
|
+
times_batchtable = []
|
|
9
|
+
times_calc = []
|
|
10
|
+
with log_filepath.open() as f:
|
|
11
|
+
for line in f.readlines():
|
|
12
|
+
if "starting" in line and "calculations with" in line:
|
|
13
|
+
nproc = int(line.split(' ')[-2])
|
|
14
|
+
elif "time to create batch table" in line:
|
|
15
|
+
times_batchtable.append(float(line.split(' ')[-2]))
|
|
16
|
+
elif "time to create job table" in line:
|
|
17
|
+
times_jobtable.append(float(line.split(' ')[-2]))
|
|
18
|
+
elif "time to perform one aggregation" in line:
|
|
19
|
+
times_calc.append(float(line.split(' ')[-2]))
|
|
20
|
+
elif "time to perform aggregations after job setup" in line:
|
|
21
|
+
total_time = float(line.split(' ')[-1])
|
|
22
|
+
|
|
23
|
+
print(f"Number of workers: {nproc}")
|
|
24
|
+
print(f"Total number of partitions used: {len(times_batchtable)}")
|
|
25
|
+
print(f"Total number of aggregate calculations: {len(times_calc)}")
|
|
26
|
+
print(f"Mean Partition Table Creation Time: {sum(times_batchtable)/len(times_batchtable):.02f} seconds")
|
|
27
|
+
print(f"Mean Job Table Creation Time: {sum(times_jobtable)/len(times_jobtable):.02f} seconds")
|
|
28
|
+
print(f"Mean Agg Calc Time: {sum(times_calc)/len(times_calc):.02f} seconds")
|
|
29
|
+
print(f"Total Time: {total_time} seconds")
|
|
30
|
+
print(f"Mean time per calculation (including I/O): {nproc * total_time/len(times_calc):.02f} CPU seconds")
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
filepath = sys.argv[1]
|
|
34
|
+
main(filepath)
|
scripts/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from toshi_hazard_post.aggregation import run_aggregation
|
|
7
|
+
from toshi_hazard_post.aggregation_args import load_input_args
|
|
8
|
+
import toshi_hazard_store
|
|
9
|
+
|
|
10
|
+
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
+
logging.getLogger('toshi_hazard_post').setLevel(logging.INFO)
|
|
12
|
+
# logging.getLogger('toshi_hazard_post.aggregation_calc').setLevel(logging.DEBUG)
|
|
13
|
+
# logging.getLogger('toshi_hazard_post.aggregation').setLevel(logging.DEBUG)
|
|
14
|
+
# logging.getLogger('toshi_hazard_post.aggregation_calc').setLevel(logging.DEBUG)
|
|
15
|
+
# logging.getLogger('toshi_hazard_post.logic_tree').setLevel(logging.DEBUG)
|
|
16
|
+
logging.getLogger('toshi_hazard_store').setLevel(logging.ERROR)
|
|
17
|
+
logging.getLogger('toshi_hazard_post').setLevel(logging.INFO)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def thp():
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@thp.command(name='aggregate', help='aggregate hazard curves')
|
|
26
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
27
|
+
def aggregate(input_file):
|
|
28
|
+
|
|
29
|
+
args = load_input_args(input_file)
|
|
30
|
+
click.echo("Toshi Hazard Post: hazard curve aggregation")
|
|
31
|
+
click.echo("=================================================")
|
|
32
|
+
run_aggregation(args)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
thp() # pragma: no cover
|
scripts/cli_old.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Console script for toshi_hazard_post."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import toml
|
|
9
|
+
from toshi_hazard_store.model import migrate as migrate_ths
|
|
10
|
+
|
|
11
|
+
# from toshi_hazard_post.hazard_aggregation import AggregationConfig, process_aggregation, process_deaggregation
|
|
12
|
+
from toshi_hazard_post.hazard_aggregation.aggregation import process_aggregation
|
|
13
|
+
from toshi_hazard_post.hazard_aggregation.aggregation_config import AggregationConfig
|
|
14
|
+
from toshi_hazard_post.hazard_aggregation.aws_aggregation import distribute_aggregation, push_test_message
|
|
15
|
+
from toshi_hazard_post.hazard_aggregation.aws_deaggregation import distribute_deaggregation
|
|
16
|
+
from toshi_hazard_post.hazard_aggregation.deaggregation import process_deaggregation
|
|
17
|
+
from toshi_hazard_post.hazard_grid.aws_gridded_hazard import distribute_gridded_hazard
|
|
18
|
+
from toshi_hazard_post.hazard_grid.gridded_hazard import calc_gridded_hazard
|
|
19
|
+
from toshi_hazard_post.hazard_grid.misc import get_filter_locations, get_site_lists, migrate
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger()
|
|
22
|
+
logging.basicConfig(level=logging.INFO)
|
|
23
|
+
logging.getLogger('nshm_toshi_client.toshi_client_base').setLevel(logging.INFO)
|
|
24
|
+
logging.getLogger('urllib3').setLevel(logging.INFO)
|
|
25
|
+
logging.getLogger('botocore').setLevel(logging.INFO)
|
|
26
|
+
logging.getLogger('pynamodb').setLevel(logging.INFO)
|
|
27
|
+
logging.getLogger('toshi_hazard_post').setLevel(logging.INFO)
|
|
28
|
+
logging.getLogger('toshi_hazard_store').setLevel(logging.INFO)
|
|
29
|
+
logging.getLogger('gql.transport.requests').setLevel(logging.WARN)
|
|
30
|
+
|
|
31
|
+
formatter = logging.Formatter(fmt='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
32
|
+
screen_handler = logging.StreamHandler(stream=sys.stdout)
|
|
33
|
+
screen_handler.setFormatter(formatter)
|
|
34
|
+
log.addHandler(screen_handler)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group()
|
|
38
|
+
def thp():
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@thp.command(name='build-grid', help='calculate hazard grids used to create maps')
|
|
43
|
+
@click.option('-H', '--hazard_model_ids', help='comma-delimted list of hazard model ids.')
|
|
44
|
+
@click.option('-L', '--site-list', help='A site list ENUM.')
|
|
45
|
+
@click.option('-I', '--imts', help='comma-delimited list of imts.')
|
|
46
|
+
@click.option('-A', '--aggs', help='comma-delimited list of aggs.')
|
|
47
|
+
@click.option('-V', '--vs30s', help='comma-delimited list of vs30s.')
|
|
48
|
+
@click.option('-P', '--poes', help='comma-delimited list of poe_levels.')
|
|
49
|
+
@click.option('-c', '--config', type=click.Path(exists=True)) # help="path to a valid THU configuration file."
|
|
50
|
+
@click.option('-lsl', '--list-site-lists', help='print the list of sites list ENUMs and exit', is_flag=True)
|
|
51
|
+
@click.option('-v', '--verbose', is_flag=True)
|
|
52
|
+
@click.option('-d', '--dry-run', is_flag=True)
|
|
53
|
+
@click.option('-m', '--migrate-tables', is_flag=True)
|
|
54
|
+
@click.option('-f', '--force', is_flag=True)
|
|
55
|
+
@click.option(
|
|
56
|
+
'--mode',
|
|
57
|
+
'-m',
|
|
58
|
+
default='LOCAL',
|
|
59
|
+
type=click.Choice(['AWS_BATCH', 'LOCAL'], case_sensitive=True),
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
'--iter-method',
|
|
63
|
+
'-i',
|
|
64
|
+
default='product',
|
|
65
|
+
type=click.Choice(['product', 'zip'], case_sensitive=True),
|
|
66
|
+
)
|
|
67
|
+
@click.option('-w', '--num-workers', default=4, show_default=True)
|
|
68
|
+
def cli_gridded_hazard(
|
|
69
|
+
hazard_model_ids,
|
|
70
|
+
site_list,
|
|
71
|
+
imts,
|
|
72
|
+
aggs,
|
|
73
|
+
vs30s,
|
|
74
|
+
poes,
|
|
75
|
+
config,
|
|
76
|
+
list_site_lists,
|
|
77
|
+
verbose,
|
|
78
|
+
dry_run,
|
|
79
|
+
migrate_tables,
|
|
80
|
+
force,
|
|
81
|
+
mode,
|
|
82
|
+
iter_method,
|
|
83
|
+
num_workers,
|
|
84
|
+
):
|
|
85
|
+
"""Process gridded hazard for a given set of arguments."""
|
|
86
|
+
|
|
87
|
+
if list_site_lists:
|
|
88
|
+
click.echo("ENUM name\tDetails")
|
|
89
|
+
click.echo("===============\t======================================================================")
|
|
90
|
+
for rg in get_site_lists():
|
|
91
|
+
click.echo(f"{rg.name}\t{rg.value}")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# site_lists = site_lists.split(',') if site_lists else None
|
|
95
|
+
|
|
96
|
+
hazard_model_ids = hazard_model_ids.split(',') if hazard_model_ids else None
|
|
97
|
+
vs30s = vs30s.split(',') if vs30s else None
|
|
98
|
+
imts = imts.split(',') if imts else None
|
|
99
|
+
aggs = aggs.split(',') if aggs else None
|
|
100
|
+
poes = poes.split(',') if poes else None
|
|
101
|
+
filter_sites = None
|
|
102
|
+
|
|
103
|
+
if config:
|
|
104
|
+
conf = toml.load(config)
|
|
105
|
+
if verbose:
|
|
106
|
+
click.echo(f"using settings in {config} for export")
|
|
107
|
+
|
|
108
|
+
site_list = site_list or conf.get('site_list')
|
|
109
|
+
hazard_model_ids = hazard_model_ids or conf.get('hazard_model_ids')
|
|
110
|
+
imts = imts or conf.get('imts')
|
|
111
|
+
vs30s = vs30s or conf.get('vs30s')
|
|
112
|
+
aggs = aggs or conf.get('aggs')
|
|
113
|
+
poes = poes or conf.get('poes')
|
|
114
|
+
filter_sites = filter_sites or conf.get('filter_sites')
|
|
115
|
+
|
|
116
|
+
if verbose:
|
|
117
|
+
click.echo(f"{hazard_model_ids} {imts} {vs30s}")
|
|
118
|
+
|
|
119
|
+
if dry_run:
|
|
120
|
+
click.echo(f"dry-run {site_list} {hazard_model_ids} {imts} {vs30s}")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if migrate_tables:
|
|
124
|
+
click.echo("Ensuring that dynamodb tables are available in target region & stage.")
|
|
125
|
+
migrate()
|
|
126
|
+
|
|
127
|
+
if mode == "LOCAL":
|
|
128
|
+
try:
|
|
129
|
+
click.echo(filter_sites)
|
|
130
|
+
filter_locations = get_filter_locations(filter_sites)
|
|
131
|
+
click.echo(filter_locations)
|
|
132
|
+
calc_gridded_hazard(
|
|
133
|
+
location_grid_id=site_list,
|
|
134
|
+
poe_levels=poes,
|
|
135
|
+
hazard_model_ids=hazard_model_ids,
|
|
136
|
+
vs30s=vs30s,
|
|
137
|
+
imts=imts,
|
|
138
|
+
aggs=aggs,
|
|
139
|
+
num_workers=num_workers,
|
|
140
|
+
force=force,
|
|
141
|
+
filter_locations=filter_locations,
|
|
142
|
+
iter_method=iter_method,
|
|
143
|
+
)
|
|
144
|
+
except Exception as err:
|
|
145
|
+
click.echo(err)
|
|
146
|
+
raise click.UsageError('An error occurred, pls check usage.')
|
|
147
|
+
|
|
148
|
+
# haggs = query_v3.get_hazard_curves(locations, vs30s, hazard_model_ids, imts=imts, aggs=aggs)
|
|
149
|
+
click.echo('Done!')
|
|
150
|
+
elif mode == "AWS_BATCH":
|
|
151
|
+
try:
|
|
152
|
+
click.echo(filter_sites)
|
|
153
|
+
filter_locations = get_filter_locations(filter_sites)
|
|
154
|
+
click.echo(filter_locations)
|
|
155
|
+
distribute_gridded_hazard(
|
|
156
|
+
location_grid_id=site_list,
|
|
157
|
+
poe_levels=poes,
|
|
158
|
+
hazard_model_ids=hazard_model_ids,
|
|
159
|
+
vs30s=vs30s,
|
|
160
|
+
imts=imts,
|
|
161
|
+
aggs=aggs,
|
|
162
|
+
force=force,
|
|
163
|
+
filter_locations=filter_locations,
|
|
164
|
+
iter_method=iter_method,
|
|
165
|
+
)
|
|
166
|
+
except Exception as err:
|
|
167
|
+
click.echo(err)
|
|
168
|
+
raise click.UsageError('An error occurred, pls check usage.')
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@thp.command(name='aggregate', help='aggregate hazard curves or disaggregations')
|
|
172
|
+
@click.option(
|
|
173
|
+
'--mode',
|
|
174
|
+
'-m',
|
|
175
|
+
default=lambda: os.environ.get("NZSHM22_THP_MODE", 'LOCAL'),
|
|
176
|
+
type=click.Choice(['AWS', 'AWS_BATCH', 'LOCAL'], case_sensitive=True),
|
|
177
|
+
)
|
|
178
|
+
@click.option('--deagg', '-d', is_flag=True)
|
|
179
|
+
@click.option('--push-sns-test', '-pt', is_flag=True)
|
|
180
|
+
@click.option('--migrate-tables', '-M', is_flag=True)
|
|
181
|
+
@click.argument('config', type=click.Path(exists=True)) # help="path to a valid THP configuration file."
|
|
182
|
+
def aggregate(config, mode, deagg, push_sns_test, migrate_tables):
|
|
183
|
+
"""Main entrypoint."""
|
|
184
|
+
click.echo("Hazard post-processing pipeline as serverless AWS infrastructure.")
|
|
185
|
+
click.echo(f"mode: {mode}")
|
|
186
|
+
|
|
187
|
+
agconf = AggregationConfig(config)
|
|
188
|
+
# click.echo(agconf)
|
|
189
|
+
# log.info("Doit")
|
|
190
|
+
|
|
191
|
+
if push_sns_test:
|
|
192
|
+
push_test_message()
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if mode == 'LOCAL':
|
|
196
|
+
if deagg:
|
|
197
|
+
process_deaggregation(agconf)
|
|
198
|
+
else:
|
|
199
|
+
process_aggregation(agconf)
|
|
200
|
+
return
|
|
201
|
+
if 'AWS_BATCH' in mode: # TODO: multiple vs30s
|
|
202
|
+
if migrate_tables:
|
|
203
|
+
click.echo("Ensuring that dynamodb tables are available in target region & stage.")
|
|
204
|
+
migrate_ths()
|
|
205
|
+
if deagg:
|
|
206
|
+
distribute_deaggregation(agconf, mode)
|
|
207
|
+
else:
|
|
208
|
+
distribute_aggregation(agconf, mode)
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
thp() # pragma: no cover
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Module for coordinating and launching aggregation jobs."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from concurrent.futures import Executor, ProcessPoolExecutor, as_completed
|
|
8
|
+
from multiprocessing import shared_memory
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Generator, Optional
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pyarrow.orc as orc
|
|
14
|
+
from nzshm_common.location.coded_location import bin_locations
|
|
15
|
+
|
|
16
|
+
import toshi_hazard_post.constants as constants
|
|
17
|
+
from toshi_hazard_post.aggregation_args import AggregationArgs
|
|
18
|
+
from toshi_hazard_post.aggregation_calc import AggSharedArgs, AggTaskArgs, calc_aggregation
|
|
19
|
+
from toshi_hazard_post.aggregation_setup import Site, get_logic_trees, get_sites
|
|
20
|
+
from toshi_hazard_post.data import get_batch_table, get_job_datatable, get_realizations_dataset
|
|
21
|
+
from toshi_hazard_post.local_config import NUM_WORKERS, WORKING_DIR
|
|
22
|
+
from toshi_hazard_post.logic_tree import HazardLogicTree
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
import numpy.typing as npt
|
|
26
|
+
from nzshm_common.location import CodedLocation
|
|
27
|
+
|
|
28
|
+
from toshi_hazard_post.logic_tree import HazardComponentBranch
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
PARTITION_RESOLUTION = 1.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _generate_agg_jobs(
|
|
37
|
+
sites: list[Site],
|
|
38
|
+
imts: list[str],
|
|
39
|
+
compatibility_key: str,
|
|
40
|
+
component_branches: list['HazardComponentBranch'],
|
|
41
|
+
) -> Generator[tuple[int, 'CodedLocation', str, Path], None, None]:
|
|
42
|
+
|
|
43
|
+
gmms_digests = [branch.gmcm_hash_digest for branch in component_branches]
|
|
44
|
+
sources_digests = [branch.source_hash_digest for branch in component_branches]
|
|
45
|
+
n_expected = len(component_branches)
|
|
46
|
+
|
|
47
|
+
# group locations by vs30
|
|
48
|
+
vs30s_unique = set([site.vs30 for site in sites])
|
|
49
|
+
|
|
50
|
+
log.info("creating batches from %s sites and %s vs30s" % (len(sites), len(vs30s_unique)))
|
|
51
|
+
for vs30 in vs30s_unique:
|
|
52
|
+
locations = [site.location for site in sites if site.vs30 == vs30]
|
|
53
|
+
location_bins = bin_locations(locations, PARTITION_RESOLUTION)
|
|
54
|
+
for nloc_0, location_bin in location_bins.items():
|
|
55
|
+
dataset = get_realizations_dataset(vs30, nloc_0)
|
|
56
|
+
log.info("batch %d, %s" % (vs30, nloc_0))
|
|
57
|
+
batch_datatable = get_batch_table(
|
|
58
|
+
dataset, compatibility_key, sources_digests, gmms_digests, vs30, nloc_0, imts
|
|
59
|
+
)
|
|
60
|
+
for location, imt in itertools.product(location_bin.locations, imts):
|
|
61
|
+
job_datatable = get_job_datatable(batch_datatable, location, imt, n_expected)
|
|
62
|
+
filepath = Path(WORKING_DIR) / f"{vs30}_{nloc_0}_{location.downsample(0.001).code}_{imt}_dataset.dat"
|
|
63
|
+
log.debug("writing file %s for agg job %s, %s" % (filepath, location.code, imt))
|
|
64
|
+
t0 = time.perf_counter()
|
|
65
|
+
orc.write_table(job_datatable, filepath, compression='snappy')
|
|
66
|
+
t1 = time.perf_counter()
|
|
67
|
+
log.debug("time to write data: %0.5f seconds" % (t1 - t0))
|
|
68
|
+
yield vs30, location, imt, filepath
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run_aggregation(args: AggregationArgs, pool_executor: Optional[Executor] = None) -> None:
|
|
72
|
+
"""Main entry point for running aggregation caculations.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
args: the aggregation configuration
|
|
76
|
+
pool_executor: the Executor (e.g. ProcessPoolExecutor) to use for launching the parallel tasks. If
|
|
77
|
+
none given, ProcessPoolExecutor(max_workers=NUM_WORKERS) is used
|
|
78
|
+
"""
|
|
79
|
+
time0 = time.perf_counter()
|
|
80
|
+
# get the sites
|
|
81
|
+
log.info("getting sites . . .")
|
|
82
|
+
sites = get_sites(args.site_params.locations_file, args.site_params.locations, args.site_params.vs30s)
|
|
83
|
+
srm_logic_tree, gmcm_logic_tree = get_logic_trees(
|
|
84
|
+
args.hazard_model.nshm_model_version,
|
|
85
|
+
args.hazard_model.srm_logic_tree,
|
|
86
|
+
args.hazard_model.gmcm_logic_tree,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# create the logic tree objects and build the full logic tree
|
|
90
|
+
log.info("getting logic trees . . . ")
|
|
91
|
+
logic_tree = HazardLogicTree(srm_logic_tree, gmcm_logic_tree)
|
|
92
|
+
component_branches = logic_tree.component_branches
|
|
93
|
+
|
|
94
|
+
if not args.debug.restart:
|
|
95
|
+
log.info("calculating weights and branch hash table . . . ")
|
|
96
|
+
tic = time.perf_counter()
|
|
97
|
+
weights = logic_tree.weights
|
|
98
|
+
branch_hash_table: list | 'npt.NDArray' = logic_tree.branch_hash_table
|
|
99
|
+
toc = time.perf_counter()
|
|
100
|
+
|
|
101
|
+
log.info('time to build weight array and hash table %0.2f seconds' % (toc - tic))
|
|
102
|
+
log.info("Size of weight array: {}MB".format(weights.nbytes >> 20))
|
|
103
|
+
log.info("Size of hash table: {}MB".format(sys.getsizeof(branch_hash_table) >> 20))
|
|
104
|
+
else:
|
|
105
|
+
branch_hash_table = np.load(args.debug.restart[0])
|
|
106
|
+
weights = np.load(args.debug.restart[1])
|
|
107
|
+
|
|
108
|
+
agg_types = [a.value for a in args.calculation.agg_types]
|
|
109
|
+
imts = [i.value for i in args.calculation.imts]
|
|
110
|
+
|
|
111
|
+
weights_shm = shared_memory.SharedMemory(name=constants.WEIGHTS_SHM_NAME, create=True, size=weights.nbytes)
|
|
112
|
+
branch_hash_table = np.array(branch_hash_table)
|
|
113
|
+
branch_hash_table_shm = shared_memory.SharedMemory(
|
|
114
|
+
name=constants.BRANCH_HASH_TABLE_SHM_NAME, create=True, size=branch_hash_table.nbytes
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
bht: 'npt.NDArray' = np.ndarray(
|
|
118
|
+
branch_hash_table.shape, dtype=branch_hash_table.dtype, buffer=branch_hash_table_shm.buf
|
|
119
|
+
)
|
|
120
|
+
bht[:] = branch_hash_table[:]
|
|
121
|
+
wgt: 'npt.NDArray' = np.ndarray(weights.shape, dtype=weights.dtype, buffer=weights_shm.buf)
|
|
122
|
+
wgt[:] = weights[:]
|
|
123
|
+
|
|
124
|
+
shared_args = AggSharedArgs(
|
|
125
|
+
weights_shape=weights.shape,
|
|
126
|
+
branch_hash_table_shape=branch_hash_table.shape,
|
|
127
|
+
agg_types=agg_types,
|
|
128
|
+
hazard_model_id=args.general.hazard_model_id,
|
|
129
|
+
compatibility_key=args.general.compatibility_key,
|
|
130
|
+
skip_save=args.debug.skip_save,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
time_parallel_start = time.perf_counter()
|
|
134
|
+
num_jobs = 0
|
|
135
|
+
log.info("starting %d calculations with %d workers" % (len(sites) * len(imts), NUM_WORKERS))
|
|
136
|
+
|
|
137
|
+
futures = {}
|
|
138
|
+
# ds1 = get_realizations_dataset()
|
|
139
|
+
pool_executor = pool_executor or ProcessPoolExecutor(max_workers=NUM_WORKERS)
|
|
140
|
+
with pool_executor as executor:
|
|
141
|
+
for vs30, location, imt, filepath in _generate_agg_jobs(
|
|
142
|
+
sites,
|
|
143
|
+
imts,
|
|
144
|
+
args.general.compatibility_key,
|
|
145
|
+
component_branches,
|
|
146
|
+
# ds1,
|
|
147
|
+
):
|
|
148
|
+
task_args = AggTaskArgs(
|
|
149
|
+
location=location,
|
|
150
|
+
vs30=vs30,
|
|
151
|
+
imt=imt,
|
|
152
|
+
table_filepath=filepath,
|
|
153
|
+
)
|
|
154
|
+
future = executor.submit(calc_aggregation, task_args, shared_args)
|
|
155
|
+
futures[future] = task_args
|
|
156
|
+
num_jobs += 1
|
|
157
|
+
|
|
158
|
+
num_failed = 0
|
|
159
|
+
for future in as_completed(futures.keys()):
|
|
160
|
+
if exception := future.exception():
|
|
161
|
+
num_failed += 1
|
|
162
|
+
log.error("Exception encountered for task args %s: %s" % (futures[future], repr(exception)))
|
|
163
|
+
|
|
164
|
+
time_parallel_end = time.perf_counter()
|
|
165
|
+
branch_hash_table_shm.close()
|
|
166
|
+
branch_hash_table_shm.unlink()
|
|
167
|
+
weights_shm.close()
|
|
168
|
+
weights_shm.unlink()
|
|
169
|
+
|
|
170
|
+
time1 = time.perf_counter()
|
|
171
|
+
log.info("total time: processed %d calculations in %0.3f seconds" % (num_jobs, time1 - time0))
|
|
172
|
+
log.info("time to perform aggregations after job setup %0.3f" % (time_parallel_end - time_parallel_start))
|
|
173
|
+
|
|
174
|
+
print(f"THERE ARE {num_failed} FAILED JOBS . . . ")
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""This module defines pydantic classes and functions for the aggregation calculation configuration.
|
|
2
|
+
|
|
3
|
+
Members:
|
|
4
|
+
AggregationArgs: pydantic class for aggregation configuration arguments.
|
|
5
|
+
load_input_args: function to return an AggregationArgs object built from a toml file.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import csv
|
|
9
|
+
from collections import namedtuple
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional, Union
|
|
12
|
+
|
|
13
|
+
import tomlkit
|
|
14
|
+
from nzshm_model import all_model_versions
|
|
15
|
+
from pydantic import AfterValidator, BaseModel, FilePath, PositiveInt, ValidationInfo, field_validator, model_validator
|
|
16
|
+
from toshi_hazard_store.model.constraints import AggregationEnum, IntensityMeasureTypeEnum
|
|
17
|
+
from toshi_hazard_store.scripts.ths_import import chc_manager
|
|
18
|
+
from typing_extensions import Annotated, Self
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_input_args(filepath: Union[str, Path]) -> 'AggregationArgs':
|
|
22
|
+
"""Load the input arguments of a hazard aggregation calculation.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
filepath: the path to the configuration toml file.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The aggregation arguments.
|
|
29
|
+
"""
|
|
30
|
+
config = tomlkit.parse(Path(filepath).read_text()).unwrap()
|
|
31
|
+
config['filepath'] = Path(filepath)
|
|
32
|
+
return AggregationArgs(**config)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_path(path: Union[Path, str], reference_filepath: Union[Path, str]) -> str:
|
|
36
|
+
path = Path(path)
|
|
37
|
+
if not path.is_absolute():
|
|
38
|
+
return str(Path(reference_filepath).parent / path)
|
|
39
|
+
return str(path)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_model_version(value: str) -> str:
|
|
43
|
+
if value not in all_model_versions():
|
|
44
|
+
raise ValueError("must specify valid nshm_model_version ({})".format(all_model_versions()))
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_compat_calc_id(compat_calc_id: str) -> str:
|
|
49
|
+
try:
|
|
50
|
+
chc_manager.load(compat_calc_id)
|
|
51
|
+
except FileNotFoundError:
|
|
52
|
+
raise ValueError("Compatible Hazard Calculation with unique ID {value} does not exist.")
|
|
53
|
+
|
|
54
|
+
return compat_calc_id
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GeneralArgs(BaseModel):
|
|
58
|
+
"""The general parameters of a hazard model.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
compatibility_key: the toshi-hazard-store compatibility key
|
|
62
|
+
hazard_model_id: the name of the hazard model to use when storing result.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
compatibility_key: Annotated[str, AfterValidator(_is_compat_calc_id)]
|
|
66
|
+
hazard_model_id: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class HazardModelArgs(BaseModel):
|
|
70
|
+
"""The PSHA hazard model parameters.
|
|
71
|
+
|
|
72
|
+
Logic tree filepaths will override nshm_model_version logic trees.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
nshm_model_version: the nzshm-model model version.
|
|
76
|
+
srm_logic_tree: the source logic tree filepath.
|
|
77
|
+
gmcm_logic_tree: the ground motion logic tree filepath.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
nshm_model_version: Annotated[Optional[str], AfterValidator(_is_model_version)] = None
|
|
82
|
+
srm_logic_tree: Optional[FilePath] = None
|
|
83
|
+
gmcm_logic_tree: Optional[FilePath] = None
|
|
84
|
+
|
|
85
|
+
@model_validator(mode='after')
|
|
86
|
+
def _check_logic_trees(self) -> Self:
|
|
87
|
+
if not self.nshm_model_version and not (self.srm_logic_tree and self.gmcm_logic_tree):
|
|
88
|
+
raise ValueError(
|
|
89
|
+
"""if nshm_model_version not specified, must provide both
|
|
90
|
+
gmcm_logic_tree and srm_logic_tree"""
|
|
91
|
+
)
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SiteArgs(BaseModel):
|
|
96
|
+
"""Site parameters.
|
|
97
|
+
|
|
98
|
+
Restrictions:
|
|
99
|
+
- Must provide one of locations or locations_file but not both.
|
|
100
|
+
- vs30s must be provided as an attribute or in the locations file but not both.
|
|
101
|
+
- if vs30s are provided, they are treated as uniform for all sites.
|
|
102
|
+
|
|
103
|
+
Attributes:
|
|
104
|
+
vs30s:
|
|
105
|
+
locations:
|
|
106
|
+
locations_file:
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
vs30s: Optional[list[PositiveInt]] = None
|
|
110
|
+
locations: Optional[list[str]] = None
|
|
111
|
+
locations_file: Optional[FilePath] = None
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _has_vs30(filepath: Path):
|
|
115
|
+
with filepath.open() as lf:
|
|
116
|
+
header = lf.readline()
|
|
117
|
+
if "vs30" in header:
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
@field_validator('locations_file', mode='after')
|
|
122
|
+
@classmethod
|
|
123
|
+
def _check_file_vs30s(cls, value: FilePath) -> FilePath:
|
|
124
|
+
with value.open() as loc_file:
|
|
125
|
+
site_reader = csv.reader(loc_file)
|
|
126
|
+
Site = namedtuple("Site", next(site_reader), rename=True) # type:ignore
|
|
127
|
+
if 'vs30' in Site._fields:
|
|
128
|
+
for row in site_reader:
|
|
129
|
+
site = Site(*row)
|
|
130
|
+
try:
|
|
131
|
+
vs30 = int(site.vs30) # type:ignore
|
|
132
|
+
assert vs30 > 0
|
|
133
|
+
except ValueError:
|
|
134
|
+
raise ValueError("not all vs30 values are valid {}".format(row))
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
@model_validator(mode='after')
|
|
138
|
+
def _check_locations(self) -> Self:
|
|
139
|
+
if self.locations_file and self.locations:
|
|
140
|
+
raise ValueError("cannot specify both locations and locations_file")
|
|
141
|
+
|
|
142
|
+
if (not self.locations) and (not self.locations_file):
|
|
143
|
+
raise ValueError("must specify locations or locations_file")
|
|
144
|
+
|
|
145
|
+
file_has_vs30 = self.locations_file and self._has_vs30(self.locations_file)
|
|
146
|
+
if file_has_vs30 and self.vs30s:
|
|
147
|
+
raise ValueError("cannot specify both uniform and site-specific vs30s")
|
|
148
|
+
elif not file_has_vs30 and not self.vs30s:
|
|
149
|
+
raise ValueError("locations file must have vs30 column if uniform vs30s not provided")
|
|
150
|
+
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class CalculationArgs(BaseModel):
|
|
155
|
+
"""The calculation parameters.
|
|
156
|
+
|
|
157
|
+
Attributes:
|
|
158
|
+
imts: the intensity measure types.
|
|
159
|
+
agg_types: the aggregation types.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
imts: list[IntensityMeasureTypeEnum] = [e for e in IntensityMeasureTypeEnum]
|
|
163
|
+
agg_types: list[AggregationEnum] = [e for e in AggregationEnum]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DebugArgs(BaseModel):
|
|
167
|
+
"""The debugging parameters.
|
|
168
|
+
|
|
169
|
+
Attributes:
|
|
170
|
+
skip_save: set to True to skip saving the aggregations.
|
|
171
|
+
restart: tuple of the paths to branch_hash_table and weights numpy files used to quickly start a
|
|
172
|
+
calculation without needing to rebuild the logic tree.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
skip_save: bool = False
|
|
176
|
+
restart: Optional[tuple[FilePath, FilePath]] = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class AggregationArgs(BaseModel):
|
|
180
|
+
"""The arguments needed to setup a suite of aggregation calculations.
|
|
181
|
+
|
|
182
|
+
Attributes:
|
|
183
|
+
filepath: the location of the config file, used to resolve relative paths.
|
|
184
|
+
general: the general arguments.
|
|
185
|
+
hazard_model: the arguments for the hazard model specification.
|
|
186
|
+
site_params: the site parameters.
|
|
187
|
+
calculation: the calculation parameters.
|
|
188
|
+
debug: the debugging parameters.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
filepath: FilePath
|
|
192
|
+
general: GeneralArgs
|
|
193
|
+
hazard_model: HazardModelArgs
|
|
194
|
+
site_params: SiteArgs
|
|
195
|
+
calculation: CalculationArgs
|
|
196
|
+
debug: DebugArgs = DebugArgs()
|
|
197
|
+
|
|
198
|
+
# resolve absolute paths (relative to input file) for optional logic tree and config fields
|
|
199
|
+
@field_validator('hazard_model', mode='before')
|
|
200
|
+
@classmethod
|
|
201
|
+
def _absolute_model_paths(cls, data: Any, info: ValidationInfo) -> Any:
|
|
202
|
+
if isinstance(data, dict):
|
|
203
|
+
for key in ["srm_logic_tree", "gmcm_logic_tree"]:
|
|
204
|
+
if data.get(key):
|
|
205
|
+
data[key] = _resolve_path(data[key], info.data["filepath"])
|
|
206
|
+
return data
|
|
207
|
+
|
|
208
|
+
# resolve absolute paths (relative to input file) for optional site file
|
|
209
|
+
@field_validator('site_params', mode='before')
|
|
210
|
+
@classmethod
|
|
211
|
+
def _absolute_site_path(cls, data: Any, info: ValidationInfo) -> Any:
|
|
212
|
+
if isinstance(data, dict):
|
|
213
|
+
if data.get("locations_file"):
|
|
214
|
+
data["locations_file"] = _resolve_path(data["locations_file"], info.data["filepath"])
|
|
215
|
+
return data
|