Coverage for /usr/local/lib/python3.11/dist-packages/grond/scenario.py: 90%
192 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-26 16:25 +0000
1import logging
2import os
3import shutil
4import os.path as op
6from pyrocko import gf, scenario, util, guts
7from pyrocko.gui import marker as pmarker
8import math
10import grond
13DEFAULT_STATIC_STORE = 'ak135_static'
14DEFAULT_WAVEFORM_STORE = 'global2s'
16logger = logging.getLogger('grond.scenario')
17km = 1e3
20class GrondScenario(object):
22 def __init__(self, project_dir,
23 center_lat=23., center_lon=52., radius=230*km,
24 problem=None, observations=None):
26 if observations is None:
27 observations = []
29 self.project_dir = project_dir
30 self.data_dir = op.join('data', 'scenario')
32 self.center_lat = center_lat
33 self.center_lon = center_lon
34 self.radius = radius
36 self.problem = problem
37 self.observations = observations
39 self.rebuild = False
40 self._scenario = None
42 def get_gf_stores_dir(self):
43 return op.join(self.project_dir, 'gf_stores')
45 def create_project_dir(self, force=False):
46 prj_dir = self.project_dir
48 if op.exists(prj_dir) and not force:
49 raise grond.GrondError(
50 'Directory "%s" already exists! Use --force to overwrite'
51 % prj_dir)
52 elif op.exists(prj_dir) and force:
53 logger.info('Overwriting directory %s.', prj_dir)
54 shutil.rmtree(prj_dir)
56 util.ensuredir(prj_dir)
58 @property
59 def stores_wanted(self):
60 target_generators = self.get_scenario().target_generators
61 return set([t.store_id for t in target_generators])
63 def symlink_gfstores(self, engine):
64 logger.info('Symlinking Green\'s function stores...')
66 for store_id in self.stores_wanted:
67 store = engine.get_store(store_id)
68 dtarget = op.join(self.get_gf_stores_dir(), store_id)
69 if not op.exists(dtarget):
70 os.symlink(store.store_dir, dtarget)
72 def add_observation(self, observation):
73 logger.info('Adding %s.' % observation.__class__.__name__)
74 self.observations.append(observation)
76 def set_problem(self, problem):
77 self.problem = problem
79 def get_dataset_config(self):
80 events_path = op.join(self.data_dir, 'events.txt')
82 dataset_config = grond.DatasetConfig(
83 events_path=events_path)
85 for obs in self.observations:
86 obs.update_dataset_config(dataset_config, self.data_dir)
87 return dataset_config
89 def set_config(self, filename):
90 self.config_fn = filename
92 def get_scenario(self):
93 if not self._scenario:
94 if self.rebuild:
95 scenario_file = op.join(self.project_dir, 'data', 'scenario',
96 'scenario.yml')
97 self._scenario = guts.load(filename=scenario_file)
99 else:
100 if not self.observations:
101 raise AttributeError('No observations set,'
102 ' use .add_observation(Observation)')
103 if not self.problem:
104 raise AttributeError('No Source Problem set,'
105 ' use .set_problem(Problem).')
107 self._scenario = scenario.ScenarioGenerator(
108 center_lat=self.center_lat,
109 center_lon=self.center_lon,
110 radius=self.radius,
111 target_generators=[obs.get_scenario_target_generator()
112 for obs in self.observations],
113 source_generator= self.problem.get_scenario_source_generator()) # noqa
115 return self._scenario
117 def create_scenario(
118 self,
119 force=False,
120 interactive=True,
121 gf_store_superdirs=None,
122 make_map=True):
124 logger.info('Creating scenario...')
126 scenario = self.get_scenario()
127 self.create_project_dir(force)
128 util.ensuredir(self.get_gf_stores_dir())
130 data_dir = op.join(self.project_dir, self.data_dir)
131 util.ensuredir(data_dir)
133 scenario.dump(filename=op.join(data_dir, 'scenario.yml'))
135 if gf_store_superdirs is None:
136 engine1 = gf.LocalEngine(
137 use_config=True,
138 store_superdirs=[self.get_gf_stores_dir()])
139 else:
140 engine1 = gf.LocalEngine(
141 use_config=False,
142 store_superdirs=gf_store_superdirs)
144 scenario.init_modelling(engine=engine1)
146 scenario.ensure_gfstores(
147 interactive=interactive)
148 self.symlink_gfstores(engine1)
150 engine2 = gf.LocalEngine(
151 use_config=False,
152 store_superdirs=[self.get_gf_stores_dir()])
154 scenario.init_modelling(engine=engine2)
156 scenario.dump_data(path=data_dir)
157 if make_map:
158 scenario.make_map(op.join(self.project_dir, 'scenario_map.pdf'))
160 shutil.move(op.join(data_dir, 'sources.yml'),
161 op.join(data_dir, 'scenario_sources.yml'))
163 markers = scenario.get_onsets()
164 marker_path = op.join(data_dir, 'picks', 'picks.markers')
165 if markers:
166 util.ensuredirs(marker_path)
167 pmarker.save_markers(markers, marker_path)
169 def get_grond_config(self):
170 engine_config = grond.EngineConfig(
171 gf_stores_from_pyrocko_config=False,
172 gf_store_superdirs=['gf_stores'])
174 optimiser_config = grond.HighScoreOptimiserConfig()
176 config = grond.Config(
177 rundir_template=op.join('runs', '${problem_name}.grun'),
178 dataset_config=self.get_dataset_config(),
179 target_groups=[obs.get_grond_target_group()
180 for obs in self.observations],
181 problem_config=self.problem.get_grond_problem_config(),
182 optimiser_config=optimiser_config,
183 engine_config=engine_config)
185 config.set_basepath(self.project_dir)
186 return config
188 def get_grond_config_path(self):
189 return op.join('config', 'scenario.gronf')
191 def create_grond_files(self):
192 logger.info('Creating Grond configuration for %s.'
193 % ' and '.join([obs.name for obs in self.observations]))
195 config_path = op.join(self.project_dir, self.get_grond_config_path())
196 util.ensuredirs(config_path)
197 grond.write_config(self.get_grond_config(), config_path)
199 def build(
200 self,
201 force=False,
202 interactive=False,
203 gf_store_superdirs=None,
204 make_map=True):
206 logger.info('Building scenario...')
208 self.create_scenario(
209 force=force,
210 interactive=interactive,
211 gf_store_superdirs=gf_store_superdirs,
212 make_map=make_map)
214 self.create_grond_files()
217class Observation(object):
219 name = 'Observation Prototype'
221 def __init__(self, store_id, *args, **kwargs):
222 self.store_id = store_id
224 def update_dataset_config(self, dataset_config, data_dir):
225 return dataset_config
227 def get_scenario_target_generator(self):
228 pass
230 def get_grond_target_group(self):
231 pass
234class WaveformObservation(Observation):
236 name = 'seismic waveforms'
238 def __init__(self, store_id=DEFAULT_WAVEFORM_STORE, nstations=25,
239 stationxml_paths=None, stations_paths=None):
240 self.nstations = nstations
241 self.store_id = store_id
242 self.stationxml_paths = stationxml_paths
243 self.stations_paths = stations_paths
245 def update_dataset_config(self, dataset_config, data_dir):
246 ds = dataset_config
247 ds.waveform_paths = [op.join(data_dir, 'waveforms')]
248 ds.stations_stationxml_paths = [
249 op.join(data_dir, 'meta', 'waveform_response.xml')]
250 ds.responses_stationxml_paths = [
251 op.join(data_dir, 'meta', 'waveform_response.xml')]
252 return ds
254 def get_scenario_target_generator(self):
255 if self.stationxml_paths or self.stations_paths:
256 station_generator = scenario.station.ImportStationGenerator(
257 stations_paths=self.stations_paths,
258 stations_stationxml_paths=self.stationxml_paths)
259 else:
260 station_generator = scenario.station.RandomStationGenerator(
261 nstations=self.nstations)
262 return scenario.targets.WaveformGenerator(
263 station_generators=[station_generator],
264 store_id=self.store_id,
265 tabulated_phases_from_store=True,
266 tabulated_phases_noise_scale=0.3,
267 seismogram_quantity='displacement')
269 def get_grond_target_group(self):
270 return grond.WaveformTargetGroup(
271 normalisation_family='time_domain',
272 path='all',
273 distance_min=10*km,
274 distance_max=1000*km,
275 channels=['Z', 'R', 'T'],
276 interpolation='multilinear',
277 store_id=self.store_id,
278 misfit_config=grond.WaveformMisfitConfig(
279 tmin='vel_surface:5.5',
280 tmax='vel_surface:3.0',
281 fmin=0.01,
282 fmax=0.1))
285class InSARObservation(Observation):
287 name = 'InSAR displacement'
289 def __init__(self, store_id=DEFAULT_STATIC_STORE):
290 self.store_id = store_id
292 def update_dataset_config(self, dataset_config, data_dir):
293 dataset_config.kite_scene_paths = [op.join(data_dir, 'insar')]
294 return dataset_config
296 def get_scenario_target_generator(self):
297 return scenario.targets.InSARGenerator(
298 mask_water=False,
299 store_id=self.store_id)
301 def get_grond_target_group(self):
302 logger.info('''Hint:
304Use kite's `spool` to configure the InSAR scene's quadtree and covariance!
305''')
306 return grond.SatelliteTargetGroup(
307 normalisation_family='insar_target',
308 path='all',
309 interpolation='multilinear',
310 store_id=self.store_id,
311 kite_scenes=['*all'],
312 misfit_config=grond.SatelliteMisfitConfig(
313 optimise_orbital_ramp=True,
314 ranges={
315 'offset': '-0.5 .. 0.5',
316 'ramp_north': '-1e-4 .. 1e-4',
317 'ramp_east': '-1e-4 .. 1e-4'
318 })
319 )
322class GNSSCampaignObservation(Observation):
324 name = 'GNSS campaign'
326 def __init__(self, store_id=DEFAULT_STATIC_STORE, nstations=25):
327 self.store_id = store_id
328 self.nstations = nstations
330 def update_dataset_config(self, dataset_config, data_dir):
331 dataset_config.gnss_campaign_paths = [op.join(data_dir, 'gnss')]
332 return dataset_config
334 def get_scenario_target_generator(self):
335 return scenario.targets.GNSSCampaignGenerator(
336 station_generators=[scenario.targets.RandomStationGenerator(
337 nstations=self.nstations)],
338 store_id=self.store_id)
340 def get_grond_target_group(self):
341 return grond.GNSSCampaignTargetGroup(
342 gnss_campaigns=['*all'],
343 normalisation_family='gnss_target',
344 path='all',
345 interpolation='multilinear',
346 store_id=self.store_id,
347 misfit_config=grond.GNSSCampaignMisfitConfig()
348 )
351class SourceProblem(object):
353 def __init__(self, magnitude_min=6., magnitude_max=7., radius=10*km,
354 nevents=1):
355 self.magnitude_min = magnitude_min
356 self.magnitude_max = magnitude_max
357 self.radius = radius
358 self.nevents = nevents
360 def get_scenario_source_generator(self):
361 return
363 def get_grond_problem_config(self):
364 return
367class DCSourceProblem(SourceProblem):
369 def get_scenario_source_generator(self):
370 return scenario.sources.DCSourceGenerator(
371 magnitude_min=self.magnitude_min,
372 magnitude_max=self.magnitude_max,
373 radius=self.radius,
374 depth_min=5*km,
375 depth_max=20*km,
376 nevents=self.nevents)
378 def get_grond_problem_config(self):
379 s2 = math.sqrt(2)
380 return grond.CMTProblemConfig(
381 name_template='cmt_${event_name}',
382 distance_min=2.*km,
383 mt_type='full',
384 ranges=dict(
385 time=gf.Range(-5.0, 5.0, relative='add'),
386 north_shift=gf.Range(-15*km, 15*km),
387 east_shift=gf.Range(-15*km, 15*km),
388 depth=gf.Range(5*km, 20*km),
389 magnitude=gf.Range(self.magnitude_min, self.magnitude_max),
390 rmnn=gf.Range(-1.0, 1.0),
391 rmee=gf.Range(-1.0, 1.0),
392 rmdd=gf.Range(-1.0, 1.0),
393 rmne=gf.Range(-s2, s2),
394 rmnd=gf.Range(-s2, s2),
395 rmed=gf.Range(-s2, s2),
396 duration=gf.Range(1.0, 15.0))
397 )
400class RectangularSourceProblem(SourceProblem):
402 def get_scenario_source_generator(self):
403 return scenario.sources.RectangularSourceGenerator(
404 magnitude_min=self.magnitude_min,
405 magnitude_max=self.magnitude_max,
406 depth_min=5*km,
407 depth_max=15*km,
408 radius=self.radius,
409 nevents=self.nevents)
411 def get_grond_problem_config(self):
412 return grond.RectangularProblemConfig(
413 name_template='rect_source_${event_name}',
414 decimation_factor=1,
415 ranges=dict(
416 north_shift=gf.Range(-15*km, 15*km),
417 east_shift=gf.Range(-15*km, 15*km),
418 depth=gf.Range(5*km, 15*km),
419 length=gf.Range(20*km, 40*km),
420 width=gf.Range(5*km, 10*km),
421 dip=gf.Range(0, 90),
422 strike=gf.Range(0, 180),
423 rake=gf.Range(0, 90),
424 slip=gf.Range(.5, 10),
425 nucleation_x=gf.Range(-1., 1.),
426 nucleation_y=gf.Range(-1., 1.),
427 time=gf.Range(-5.0, 5.0, relative='add'))
428 )
431class PseudoDynamicRuptureProblem(SourceProblem):
433 def get_scenario_source_generator(self):
434 return scenario.sources.PseudoDynamicRuptureGenerator(
435 magnitude_min=self.magnitude_min,
436 magnitude_max=self.magnitude_max,
437 depth_min=5*km,
438 depth_max=15*km,
439 nx=8,
440 ny=5,
441 nevents=self.nevents)
443 def get_grond_problem_config(self):
444 return grond.DynamicRuptureProblemConfig(
445 name_template='dynamic_rupture_${event_name}',
446 decimation_factor=6,
447 nx=8,
448 ny=5,
449 ranges=dict(
450 north_shift=gf.Range(-15*km, 15*km),
451 east_shift=gf.Range(-15*km, 15*km),
452 depth=gf.Range(5*km, 15*km),
453 length=gf.Range(20*km, 40*km),
454 width=gf.Range(5*km, 10*km),
455 dip=gf.Range(0, 90),
456 strike=gf.Range(0, 180),
457 rake=gf.Range(0, 90),
458 slip=gf.Range(.5, 10),
459 nucleation_x=gf.Range(-1., 1.),
460 nucleation_y=gf.Range(-1., 1.),
461 time=gf.Range(-5.0, 5.0, relative='add'))
462 )