Coverage for /usr/local/lib/python3.11/dist-packages/grond/apps/grond.py: 51%
742 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
1#!/usr/bin/env python
3from __future__ import print_function, absolute_import
5import sys
6import os.path as op
7import logging
8from optparse import OptionParser, OptionValueError
9import grond
10from io import StringIO
12try:
13 from pyrocko import util, marker
14except ImportError:
15 print('Pyrocko is required for Grond!'
16 'Go to https://pyrocko.org/ for installation instructions.')
19logger = logging.getLogger('grond.main')
20km = 1e3
23class Color:
24 PURPLE = '\033[95m'
25 CYAN = '\033[96m'
26 DARKCYAN = '\033[36m'
27 BLUE = '\033[94m'
28 GREEN = '\033[92m'
29 YELLOW = '\033[93m'
30 RED = '\033[91m'
31 BOLD = '\033[1m'
32 UNDERLINE = '\033[4m'
33 END = '\033[0m'
36def d2u(d):
37 if isinstance(d, dict):
38 return dict((k.replace('-', '_'), v) for (k, v) in d.items())
39 else:
40 return d.replace('-', '_')
43subcommand_descriptions = {
44 'init': 'initialise new project structure or print configuration',
45 'scenario': 'create a forward-modelled scenario project',
46 'events': 'print available event names for given configuration',
47 'check': 'check data and configuration',
48 'go': 'run Grond optimisation',
49 'continue': 'continue a Grond optimisation in case of an'
50 ' interruption or more iterations are needed',
51 'forward': 'run forward modelling',
52 'harvest': 'manually run harvesting',
53 'cluster': 'run cluster analysis on result ensemble',
54 'plot': 'plot optimisation result',
55 'movie': 'visualize optimiser evolution',
56 'export': 'export results',
57 'tag': 'add user-defined label to run directories',
58 'report': 'create result report',
59 'diff': 'compare two configs or other normalized Grond YAML files',
60 'qc-polarization': 'check sensor orientations with polarization analysis',
61 'upgrade-config': 'upgrade config file to the latest version of Grond',
62 'version': 'print version number of Grond and its main dependencies',
63}
65subcommand_usages = {
66 'init': (
67 'init list [options]',
68 'init <example> [options]',
69 'init <example> <projectdir> [options]'),
70 'scenario': 'scenario [options] <projectdir>',
71 'events': 'events <configfile>',
72 'check': 'check <configfile> <eventnames> ... [options]',
73 'go': 'go <configfile> <eventnames> ... [options]',
74 'continue': 'continue <configfile> <eventnames> ... [options]',
75 'forward': (
76 'forward <rundir> [options]',
77 'forward <configfile> <eventnames> ... [options]'),
78 'harvest': 'harvest <rundir> [options]',
79 'cluster': (
80 'cluster <method> <rundir> [options]',
81 'cluster <clusteringconfigfile> <rundir> [options]'),
82 'plot': (
83 'plot <plotnames> ( <rundir> | <configfile> <eventname> ) [options]',
84 'plot all ( <rundir> | <configfile> <eventname> ) [options]',
85 'plot <plotconfigfile> ( <rundir> | <configfile> <eventname> ) [options]', # noqa
86 'plot list ( <rundir> | <configfile> <eventname> ) [options]',
87 'plot config ( <rundir> | <configfile> <eventname> ) [options]'),
88 'movie': 'movie <rundir> <xpar> <ypar> <filetemplate> [options]',
89 'export': 'export (best|mean|ensemble|stats) <rundirs> ... [options]',
90 'tag': (
91 'tag add <tag> <rundir>',
92 'tag remove <tag> <rundir>',
93 'tag list <rundir>'),
94 'report': (
95 'report <rundir> ... [options]',
96 'report <configfile> <eventnames> ...'),
97 'diff': 'diff <left_path> <right_path>',
98 'qc-polarization': 'qc-polarization <configfile> <eventname> '
99 '<target_group_path> [options]',
100 'upgrade-config': 'upgrade-config <configfile>',
101 'version': 'version',
102}
104subcommands = subcommand_descriptions.keys()
106program_name = 'grond'
108usage_tdata = d2u(subcommand_descriptions)
109usage_tdata['program_name'] = program_name
110usage_tdata['version_number'] = grond.__version__
113usage = '''%(program_name)s <subcommand> [options] [--] <arguments> ...
115Grond is a probabilistic earthquake source inversion framework.
117This is Grond version %(version_number)s.
119Subcommands:
121 scenario %(scenario)s
122 init %(init)s
123 events %(events)s
124 check %(check)s
125 go %(go)s
126 continue %(continue)s
127 forward %(forward)s
128 harvest %(harvest)s
129 cluster %(cluster)s
130 plot %(plot)s
131 movie %(movie)s
132 export %(export)s
133 tag %(tag)s
134 report %(report)s
135 diff %(diff)s
136 qc-polarization %(qc_polarization)s
137 upgrade-config %(upgrade_config)s
138 version %(version)s
140To get further help and a list of available options for any subcommand run:
142 %(program_name)s <subcommand> --help
144What do you want to bust today?!
145''' % usage_tdata
148class CLIHints(object):
149 init = '''
150We created a folder structure in {project_dir}.
151Check out the YAML configuration in {config} and start the optimisation by:
153 grond go {config}
154'''
155 scenario = '''
156To start the scenario's optimisation, change to folder
158 cd {project_dir}
160Check out the YAML configuration in {config} and start the optimisation by:
162 grond go {config}
163'''
164 report = '''
165To open the report in your web browser, run
167 grond report -s --open {config}
168'''
169 check = '''
170To start the optimisation, run
172 grond go {config}
173'''
174 go = '''
175To look at the results, run
177 grond report -so {rundir}
178'''
180 def __new__(cls, command, **kwargs):
181 return '{c.BOLD}Hint{c.END}\n'.format(c=Color) +\
182 getattr(cls, command).format(**kwargs)
185def main(args=None):
186 if not args:
187 args = sys.argv
189 args = list(args)
190 if len(args) < 2:
191 sys.exit('Usage: %s' % usage)
193 args.pop(0)
194 command = args.pop(0)
196 if command in subcommands:
197 globals()['command_' + d2u(command)](args)
199 elif command in ('--help', '-h', 'help'):
200 if command == 'help' and args:
201 acommand = args[0]
202 if acommand in subcommands:
203 globals()['command_' + acommand](['--help'])
205 sys.exit('Usage: %s' % usage)
207 else:
208 die('No such subcommand: %s' % command)
211def add_common_options(parser):
212 parser.add_option(
213 '--loglevel',
214 action='store',
215 dest='loglevel',
216 type='choice',
217 choices=('critical', 'error', 'warning', 'info', 'debug'),
218 help='set logger level to '
219 '"critical", "error", "warning", "info", or "debug". '
220 'Default is "info".')
222 parser.add_option(
223 '--status', dest='status',
224 type='choice', choices=['state', 'quiet'],
225 help='status output selection (choices: state, quiet, default: '
226 'state)')
228 parser.add_option(
229 '--parallel', dest='nparallel', type=int,
230 help='set number of events to process in parallel, '
231 'if set to more than one, --status=quiet is implied.')
233 parser.add_option(
234 '--threads', dest='nthreads', type=int,
235 help='set number of threads per process (default: 1). '
236 'Set to 0 to use all available cores.')
238 parser.add_option(
239 '--docs',
240 dest='rst_docs',
241 action='store_true')
244def print_docs(command, parser):
246 from optparse import IndentedHelpFormatter
248 class DocsFormatter(IndentedHelpFormatter):
250 def format_heading(self, heading):
251 return '%s\n%s\n\n' % (heading, '.'*len(heading))
253 def format_usage(self, usage):
254 lines = usage.splitlines()
255 return self.format_heading('Usage') + \
256 '.. code-block:: none\n\n%s' % '\n'.join(
257 ' '+line.strip() for line in lines)
259 def format_option(self, option):
260 if not option.help:
261 return ''
263 result = []
264 opts = self.option_strings[option]
265 result.append('\n.. describe:: %s\n\n' % opts)
267 help_text = self.expand_default(option)
268 result.append(' %s\n\n' % help_text)
270 return ''.join(result)
272 parser.formatter = DocsFormatter()
273 parser.formatter.set_parser(parser)
275 def format_help(parser):
276 formatter = parser.formatter
277 result = []
279 result.append(parser.format_description(formatter) + "\n")
281 if parser.usage:
282 result.append(parser.get_usage() + "\n")
284 result.append('\n')
286 result.append(parser.format_option_help(formatter))
288 result.append('\n')
290 result.append(parser.format_epilog(formatter))
291 return "".join(result)
293 print(command)
294 print('-' * len(command))
295 print()
296 print('.. program:: %s' % program_name)
297 print()
298 print('.. option:: %s' % command)
299 print()
300 print(format_help(parser))
303def process_common_options(command, parser, options):
304 from grond.config import get_global_config
306 gconf = get_global_config()
307 gconf.override_with_cli_arguments(options)
309 util.setup_logging(program_name, gconf.loglevel)
310 if options.rst_docs:
311 print_docs(command, parser)
312 exit(0)
315def cl_parse(command, args, setup=None, details=None):
316 usage = subcommand_usages[command]
317 descr = subcommand_descriptions[command]
319 if isinstance(usage, str):
320 usage = [usage]
322 susage = '%s %s' % (program_name, usage[0])
323 for s in usage[1:]:
324 susage += '\n%s%s %s' % (' '*7, program_name, s)
326 description = descr[0].upper() + descr[1:] + '.'
328 if details:
329 description = description + '\n\n%s' % details
331 parser = OptionParser(usage=susage, description=description)
333 if setup:
334 setup(parser)
336 add_common_options(parser)
337 (options, args) = parser.parse_args(args)
338 process_common_options(command, parser, options)
339 return parser, options, args
342def die(message, err='', prelude=''):
343 if prelude:
344 prelude = prelude + '\n'
346 if err:
347 err = '\n' + err
349 sys.exit('%s%s failed: %s%s' % (prelude, program_name, message, err))
352def help_and_die(parser, message):
353 sio = StringIO()
354 parser.print_help(sio)
355 die(message, prelude=sio.getvalue())
358def multiple_choice(option, opt_str, value, parser, choices):
359 options = value.split(',')
360 for opt in options:
361 if opt not in choices:
362 raise OptionValueError('Invalid option %s - valid options are: %s'
363 % (opt, ', '.join(choices)))
364 setattr(parser.values, option.dest, options)
367def magnitude_range(option, opt_str, value, parser):
368 mag_range = value.split('-')
369 if len(mag_range) != 2:
370 raise OptionValueError(
371 'Invalid magnitude %s - valid range is e.g. 6-7.' % value)
372 try:
373 mag_range = tuple(map(float, mag_range))
374 except ValueError:
375 raise OptionValueError('Magnitudes must be numbers.')
377 if mag_range[0] > mag_range[1]:
378 raise OptionValueError('Minimum magnitude must be larger than'
379 ' maximum magnitude.')
380 setattr(parser.values, option.dest, mag_range)
383def command_scenario(args):
385 STORE_STATIC = 'crust2_ib_static'
386 STORE_WAVEFORMS = 'crust2_ib'
388 def setup(parser):
389 parser.add_option(
390 '--targets', action='callback', dest='targets', type=str,
391 callback=multiple_choice, callback_kwargs={
392 'choices': ('waveforms', 'gnss', 'insar')
393 },
394 default='waveforms',
395 help='forward modelling targets for the scenario. Select from:'
396 ' waveforms, gnss and insar. Multiple selection by '
397 '--targets=waveforms,gnss,insar'
398 '(default: --targets=%default)')
399 parser.add_option(
400 '--problem', dest='problem', default='cmt',
401 type='choice', choices=['cmt', 'rectangular', 'dynamic_rupture'],
402 help='problem to generate: \'dc\' (double couple), '
403 '\'rectangular\' (rectangular finite fault) or '
404 '\'dynamic_rupture\' for a dynamic rupture scenario.'
405 ' (default: \'%default\')')
406 parser.add_option(
407 '--magnitude-range', dest='magnitude_range', type=str,
408 action='callback', callback=magnitude_range, default=[6.0, 7.0],
409 help='Magnitude range min_mag-max_mag (default: %default)')
410 parser.add_option(
411 '--nstations', dest='nstations', type=int, default=20,
412 help='number of seismic stations to create (default: %default)')
413 parser.add_option(
414 '--gnss_nstations', dest='gnss_nstations', type=int, default=20,
415 help='number of GNSS campaign stations to create'
416 ' (default: %default)')
417 parser.add_option(
418 '--nevents', dest='nevents', type=int, default=1,
419 help='number of events to create (default: %default)')
420 parser.add_option(
421 '--lat', dest='lat', type=float, default=41.0,
422 help='center latitude of the scenario (default: %default)')
423 parser.add_option(
424 '--lon', dest='lon', type=float, default=33.3,
425 help='center latitude of the scenario (default: %default)')
426 parser.add_option(
427 '--radius', dest='radius', type=float, default=100.,
428 help='radius of the scenario in [km] (default: %default)')
429 parser.add_option(
430 '--source-radius', dest='source_radius', type=float, default=10.,
431 help='radius of the source area in [km] (default: %default)')
432 parser.add_option(
433 '--stations-paths', dest='stations_paths', type=str, default=None,
434 help='paths to a Pyrocko station file, seperated by \',\''
435 '(default: %default)')
436 parser.add_option(
437 '--stationxml-paths', dest='stationxml_paths', type=str,
438 default=None,
439 help='paths to StationXML files, seperated by \',\''
440 '(default: %default)')
441 parser.add_option(
442 '--gf-waveforms', dest='store_waveforms', type=str,
443 default=STORE_WAVEFORMS,
444 help='Green\'s function store for waveform modelling, '
445 '(default: %default)')
446 parser.add_option(
447 '--gf-static', dest='store_statics', type=str,
448 default=STORE_STATIC,
449 help='Green\'s function store for static modelling, '
450 '(default: %default)')
451 parser.add_option(
452 '--force', dest='force', action='store_true',
453 help='overwrite existing project folder.')
454 parser.add_option(
455 '--gf-store-superdirs',
456 dest='gf_store_superdirs',
457 help='Comma-separated list of directories containing GF stores')
458 parser.add_option(
459 '--no-map',
460 dest='make_map',
461 default=True,
462 action='store_false',
463 help='suppress generation of map')
464 parser.add_option(
465 '--rebuild',
466 dest='rebuild', action='store_true', default=False,
467 help='Rebuild a manually configured grond scenario')
469 parser, options, args = cl_parse('scenario', args, setup)
471 gf_store_superdirs = None
472 if options.gf_store_superdirs:
473 gf_store_superdirs = options.gf_store_superdirs.split(',')
475 if len(args) == 1:
476 project_dir = args[0]
477 else:
478 parser.print_help()
479 sys.exit(1)
481 from grond import scenario as grond_scenario
483 try:
484 scenario = grond_scenario.GrondScenario(
485 project_dir,
486 center_lat=options.lat, center_lon=options.lon,
487 radius=options.radius*km)
489 scenario.rebuild = options.rebuild
490 if options.rebuild:
491 options.force = True
493 if 'waveforms' in options.targets:
494 if options.stationxml_paths:
495 options.stationxml_paths = [
496 op.abspath(path) for path in
497 options.stationxml_paths.split(',')]
499 if options.stations_paths:
500 options.stations_paths = [
501 op.abspath(path) for path in
502 options.stations_paths.split(',')]
504 obs = grond_scenario.WaveformObservation(
505 nstations=options.nstations,
506 store_id=options.store_waveforms,
507 stations_paths=options.stations_paths,
508 stationxml_paths=options.stationxml_paths)
509 scenario.add_observation(obs)
511 if 'insar' in options.targets:
512 obs = grond_scenario.InSARObservation(
513 store_id=options.store_statics)
514 scenario.add_observation(obs)
516 if 'gnss' in options.targets:
517 obs = grond_scenario.GNSSCampaignObservation(
518 nstations=options.gnss_nstations,
519 store_id=options.store_statics)
520 scenario.add_observation(obs)
522 src_args = dict(
523 nevents=options.nevents,
524 magnitude_min=options.magnitude_range[0],
525 magnitude_max=options.magnitude_range[1])
526 if options.problem == 'cmt':
527 problem = grond_scenario.DCSourceProblem(**src_args)
528 elif options.problem == 'rectangular':
529 problem = grond_scenario.RectangularSourceProblem(**src_args)
530 elif options.problem == 'dynamic_rupture':
531 problem = grond_scenario.PseudoDynamicRuptureProblem(**src_args)
532 scenario.set_problem(problem)
534 scenario.build(
535 force=options.force,
536 interactive=True,
537 gf_store_superdirs=gf_store_superdirs,
538 make_map=options.make_map)
540 logger.info(CLIHints('scenario',
541 config=scenario.get_grond_config_path(),
542 project_dir=project_dir))
544 except grond.GrondError as e:
545 die(str(e))
548def command_init(args):
550 from .cmd_init import GrondInit
552 grond_init = GrondInit()
554 def print_section(entries):
555 if len(entries) == 0:
556 return '\tNone available.'
558 padding = max([len(n) for n in entries.keys()])
559 rstr = []
560 lcat = None
561 for name, desc in entries.items():
563 cat = name.split('_')[0]
564 if lcat is not None and lcat != cat:
565 rstr.append('')
566 lcat = cat
568 rstr.append(' {c.BOLD}{name:<{padding}}{c.END} : {desc}'.format(
569 name=name, desc=desc, padding=padding, c=Color))
570 return '\n'.join(rstr)
572 help_text = '''Available configuration examples for Grond.
574{c.BOLD}Example Projects{c.END}
576 Deploy a full project structure into a directory.
578 usage: grond init <example> <projectdir>
580 where <example> is any of the following:
582{examples_list}
584{c.BOLD}Config Sections{c.END}
586 Print out configuration snippets for various components.
588 usage: grond init <section>
590 where <section> is any of the following:
592{sections_list}
593'''.format(c=Color,
594 examples_list=print_section(grond_init.get_examples()),
595 sections_list=print_section(grond_init.get_sections()))
597 def setup(parser):
598 parser.add_option(
599 '--force', dest='force', action='store_true')
601 parser, options, args = cl_parse(
602 'init', args, setup,
603 'Use grond init list to show available examples.')
605 if len(args) not in (1, 2):
606 help_and_die(parser, '1 or 2 arguments required')
608 if args[0] == 'list':
609 print(help_text)
610 return
612 if args[0].startswith('example_'):
613 if len(args) == 1:
614 config = grond_init.get_content_example(args[0])
615 if not config:
616 help_and_die(parser, 'Unknown example: %s' % args[0])
618 sys.stdout.write(config+'\n\n')
620 logger.info('Hint: To create a project, use: grond init <example> '
621 '<projectdir>')
623 elif op.exists(op.abspath(args[1])) and not options.force:
624 help_and_die(
625 parser,
626 'Directory "%s" already exists! Use --force to overwrite.'
627 % args[1])
628 else:
629 try:
630 grond_init.init_example(args[0], args[1], force=options.force)
631 except OSError as e:
632 print(str(e))
634 else:
635 sec = grond_init.get_content_snippet(args[0])
636 if not sec:
637 help_and_die(parser, 'Unknown snippet: %s' % args[0])
639 sys.stdout.write(sec)
642def command_init_old(args):
644 from . import cmd_init as init
646 def setup(parser):
647 parser.add_option(
648 '--targets', action='callback', dest='targets', type=str,
649 callback=multiple_choice, callback_kwargs={
650 'choices': ('waveforms', 'gnss', 'insar', 'all')
651 },
652 default='waveforms',
653 help='select from:'
654 ' waveforms, gnss and insar. '
655 '(default: --targets=%default,'
656 ' multiple selection by --targets=waveforms,gnss,insar)')
657 parser.add_option(
658 '--problem', dest='problem',
659 type='choice', choices=['cmt', 'rectangular'],
660 help='problem to generate: \'dc\' (double couple)'
661 ' or\'rectangular\' (rectangular finite fault)'
662 ' (default: \'%default\')')
663 parser.add_option(
664 '--force', dest='force', action='store_true',
665 help='overwrite existing project folder')
667 parser, options, args = cl_parse('init', args, setup)
669 try:
670 project = init.GrondProject()
672 if 'all' in options.targets:
673 targets = ['waveforms', 'gnss', 'insar']
674 else:
675 targets = options.targets
677 if not options.problem:
678 if 'insar' in targets or 'gnss' in targets:
679 problem = 'rectangular'
680 else:
681 problem = 'cmt'
682 else:
683 problem = options.problem
685 if problem == 'rectangular':
686 project.set_rectangular_source()
687 elif problem == 'cmt':
688 project.set_cmt_source()
690 if 'waveforms' in targets:
691 project.add_waveforms()
693 if 'insar' in targets:
694 project.add_insar()
696 if 'gnss' in targets:
697 project.add_gnss()
699 if len(args) == 1:
700 project_dir = args[0]
701 project.build(project_dir, options.force)
702 logger.info(CLIHints(
703 'init', project_dir=project_dir,
704 config=op.join(project_dir, 'config', 'config.gronf')))
705 else:
706 sys.stdout.write(project.dump())
708 except grond.GrondError as e:
709 die(str(e))
712def command_events(args):
713 def setup(parser):
714 pass
716 parser, options, args = cl_parse('events', args, setup)
717 if len(args) != 1:
718 help_and_die(parser, 'missing arguments')
720 config_path = args[0]
721 try:
722 config = grond.read_config(config_path)
724 for event_name in grond.get_event_names(config):
725 print(event_name)
727 except grond.GrondError as e:
728 die(str(e))
731def command_check(args):
733 from grond.environment import Environment
735 def setup(parser):
736 parser.add_option(
737 '--target-ids', dest='target_string_ids', metavar='TARGET_IDS',
738 help='process only selected targets. TARGET_IDS is a '
739 'comma-separated list of target IDs. Target IDs have the '
740 'form SUPERGROUP.GROUP.NETWORK.STATION.LOCATION.CHANNEL.')
742 parser.add_option(
743 '--waveforms', dest='show_waveforms', action='store_true',
744 help='show raw, restituted, projected, and processed waveforms')
746 parser.add_option(
747 '--nrandom', dest='n_random_synthetics', metavar='N', type=int,
748 default=10,
749 help='set number of random synthetics to forward model (default: '
750 '10). If set to zero, create synthetics for the reference '
751 'solution.')
753 parser.add_option(
754 '--save-stations-used', dest='stations_used_path',
755 metavar='FILENAME',
756 help='aggregate all stations used by the setup into a file')
758 parser, options, args = cl_parse('check', args, setup)
759 if len(args) < 1:
760 help_and_die(parser, 'missing arguments')
762 try:
763 env = Environment(args)
764 config = env.get_config()
766 target_string_ids = None
767 if options.target_string_ids:
768 target_string_ids = options.target_string_ids.split(',')
770 grond.check(
771 config,
772 event_names=env.get_selected_event_names(),
773 target_string_ids=target_string_ids,
774 show_waveforms=options.show_waveforms,
775 n_random_synthetics=options.n_random_synthetics,
776 stations_used_path=options.stations_used_path)
778 logger.info(CLIHints('check', config=env.get_config_path()))
780 except grond.GrondError as e:
781 die(str(e))
784def command_go(args):
786 from grond.environment import Environment
788 def setup(parser):
789 parser.add_option(
790 '--force', dest='force', action='store_true',
791 help='overwrite existing run directory')
792 parser.add_option(
793 '--preserve', dest='preserve', action='store_true',
794 help='preserve old rundir')
796 parser, options, args = cl_parse('go', args, setup)
798 try:
799 env = Environment(args)
801 grond.go(
802 env,
803 force=options.force,
804 preserve=options.preserve)
806 if len(env.get_selected_event_names()) == 1:
807 logger.info(CLIHints(
808 'go', rundir=env.get_rundir_path()))
810 except grond.GrondError as e:
811 die(str(e))
814def command_continue(args):
816 from grond.environment import Environment
818 def setup(parser):
819 parser.add_option(
820 '--no-preserve', dest='no_preserve', action='store_true',
821 help='do not preserve old rundir')
822 parser.add_option(
823 '--status', dest='status', default='state',
824 type='choice', choices=['state', 'quiet'],
825 help='status output selection (choices: state, quiet, default: '
826 'state)')
827 parser.add_option(
828 '--parallel', dest='nparallel', type=int, default=1,
829 help='set number of events to process in parallel, '
830 'if set to more than one, --status=quiet is implied.')
831 parser.add_option(
832 '--threads', dest='nthreads', type=int, default=1,
833 help='set number of threads per process (default: 1). '
834 'Set to 0 to use all available cores.')
836 parser, options, args = cl_parse('continue', args, setup)
838 try:
839 env = Environment(args)
841 status = options.status
842 if options.nparallel != 1:
843 status = 'quiet'
845 grond.continue_run(
846 env,
847 preserve=~bool(options.no_preserve),
848 status=status,
849 nparallel=options.nparallel,
850 nthreads=options.nthreads)
852 except grond.GrondError as e:
853 die(str(e))
856def command_forward(args):
858 from grond.environment import Environment
860 def setup(parser):
861 parser.add_option(
862 '--show', dest='show', metavar='WHAT',
863 default='filtered',
864 choices=('filtered', 'processed'),
865 help='select whether to show only "filtered" or fully "processed" '
866 '(i.e. tapered) waveforms (default "%default").')
868 parser, options, args = cl_parse('forward', args, setup)
869 if len(args) < 1:
870 help_and_die(parser, 'missing arguments')
872 try:
873 env = Environment(args)
874 grond.forward(env, show=options.show)
875 except grond.GrondError as e:
876 die(str(e))
879def command_harvest(args):
880 def setup(parser):
881 parser.add_option(
882 '--force', dest='force', action='store_true',
883 help='overwrite existing harvest directory')
884 parser.add_option(
885 '--neach', dest='neach', type=int, default=10,
886 help='take NEACH best samples from each chain (default: %default)')
887 parser.add_option(
888 '--weed', dest='weed', type=int, default=0,
889 help='weed out bootstrap samples with bad global performance. '
890 '0: no weeding (default), '
891 '1: only bootstrap chains where all NEACH best samples '
892 'global misfit is less than the global average misfit of all '
893 'NEACH best in all chains plus one standard deviation are '
894 'included in the harvest ensemble, '
895 '2: same as 1 but additionally individual samples are '
896 'removed if their global misfit is greater than the global '
897 'average misfit of all NEACH best in all chains, '
898 '3: harvesting is done on the global chain only, bootstrap '
899 'chains are excluded')
900 parser.add_option(
901 '--export-fits', dest='export_fits', default='',
902 help='additionally export details about the fit of individual '
903 'targets. "best" - export fits of best model, "mean" - '
904 'export fits of ensemble mean model, "ensemble" - export '
905 'fits of all models in harvest ensemble.')
907 parser, options, args = cl_parse('harvest', args, setup)
908 if len(args) < 1:
909 help_and_die(parser, 'no rundir')
911 export_fits = []
912 if options.export_fits.strip():
913 export_fits = [x.strip() for x in options.export_fits.split(',')]
915 for run_path in args:
916 try:
917 grond.harvest(
918 run_path,
919 force=options.force,
920 nbest=options.neach,
921 weed=options.weed,
922 export_fits=export_fits)
924 except grond.DirectoryAlreadyExists as e:
925 die(str(e) + '\n Use --force to overwrite.')
927 except grond.GrondError as e:
928 die(str(e))
931def command_cluster(args):
932 from grond import Clustering
933 from grond.clustering import metrics, methods, read_config, write_config
935 def setup(parser):
936 parser.add_option(
937 '--metric', dest='metric', metavar='METRIC',
938 default='kagan_angle',
939 choices=metrics.metrics,
940 help='metric to measure model distances. Choices: [%s]. Default: '
941 'kagan_angle' % ', '.join(metrics.metrics))
943 parser.add_option(
944 '--write-config',
945 dest='write_config',
946 metavar='FILE',
947 help='write configuration (or default configuration) to FILE')
949 method = args[0] if args else ''
950 try:
951 parser, options, args = cl_parse(
952 'cluster', args[1:], setup=Clustering.cli_setup(method, setup),
953 details='Available clustering methods: [%s]. Use '
954 '"grond cluster <method> --help" to get list of method '
955 'dependent options.' % ', '.join(methods))
957 if method not in Clustering.name_to_class and not op.exists(method):
958 help_and_die(
959 parser,
960 'no such clustering method: %s' % method if method else
961 'no clustering method specified')
963 if op.exists(method):
964 clustering = read_config(method)
965 else:
966 clustering = Clustering.cli_instantiate(method, options)
968 if options.write_config:
969 write_config(clustering, options.write_config)
970 else:
971 if len(args) != 1:
972 help_and_die(parser, 'no rundir')
973 run_path, = args
975 grond.cluster(run_path, clustering, metric=options.metric)
977 except grond.GrondError as e:
978 die(str(e))
981def command_plot(args):
983 def setup(parser):
984 parser.add_option(
985 '--show', dest='show', action='store_true',
986 help='show plot for interactive inspection')
988 details = ''
990 parser, options, args = cl_parse('plot', args, setup, details)
992 if not options.show:
993 import matplotlib
994 matplotlib.use('Agg')
996 from grond.environment import Environment
998 if len(args) not in (1, 2, 3):
999 help_and_die(parser, '1, 2 or 3 arguments required')
1001 if len(args) > 1:
1002 env = Environment(args[1:])
1003 else:
1004 env = None
1006 from grond import plot
1007 if args[0] == 'list':
1009 def get_doc_title(doc):
1010 for ln in doc.split('\n'):
1011 ln = ln.strip()
1012 if ln != '':
1013 ln = ln.strip('.')
1014 return ln
1015 return 'Undocumented.'
1017 if env:
1018 plot_classes = env.get_plot_classes()
1019 else:
1020 plot_classes = plot.get_all_plot_classes()
1022 plot_names, plot_doc = zip(*[(pc.name, pc.__doc__)
1023 for pc in plot_classes])
1025 plot_descs = [get_doc_title(doc) for doc in plot_doc]
1026 left_spaces = max([len(pn) for pn in plot_names])
1028 for name, desc in zip(plot_names, plot_descs):
1029 print('{name:<{ls}} - {desc}'.format(
1030 ls=left_spaces, name=name, desc=desc))
1032 elif args[0] == 'config':
1033 plot_config_collection = plot.get_plot_config_collection(env)
1034 print(plot_config_collection)
1036 elif args[0] == 'all':
1037 if env is None:
1038 help_and_die(parser, 'two or three arguments required')
1039 plot_names = plot.get_plot_names(env)
1040 plot.make_plots(env, plot_names=plot_names, show=options.show)
1042 elif op.exists(args[0]):
1043 if env is None:
1044 help_and_die(parser, 'two or three arguments required')
1045 plots = plot.PlotConfigCollection.load(args[0])
1046 plot.make_plots(env, plots, show=options.show)
1048 else:
1049 if env is None:
1050 help_and_die(parser, 'two or three arguments required')
1051 plot_names = [name.strip() for name in args[0].split(',')]
1052 plot.make_plots(env, plot_names=plot_names, show=options.show)
1055def command_movie(args):
1057 import matplotlib
1058 matplotlib.use('Agg')
1060 def setup(parser):
1061 pass
1063 parser, options, args = cl_parse('movie', args, setup)
1065 if len(args) != 4:
1066 help_and_die(parser, 'four arguments required')
1068 run_path, xpar_name, ypar_name, movie_filename_template = args
1070 from grond import plot
1072 movie_filename = movie_filename_template % {
1073 'xpar': xpar_name,
1074 'ypar': ypar_name}
1076 try:
1077 plot.make_movie(run_path, xpar_name, ypar_name, movie_filename)
1079 except grond.GrondError as e:
1080 die(str(e))
1083def command_export(args):
1085 def setup(parser):
1086 parser.add_option(
1087 '--type', dest='type', metavar='TYPE',
1088 choices=('event', 'event-yaml', 'source', 'vector'),
1089 help='select type of objects to be exported. Choices: '
1090 '"event" (default), "event-yaml", "source", "vector".')
1092 parser.add_option(
1093 '--parameters', dest='parameters', metavar='PLIST',
1094 help='select parameters to be exported. PLIST is a '
1095 'comma-separated list where each entry has the form '
1096 '"<parameter>[.<measure>]". Available measures: "best", '
1097 '"mean", "std", "minimum", "percentile16", "median", '
1098 '"percentile84", "maximum".')
1100 parser.add_option(
1101 '--selection', dest='selection', metavar='EXPRESSION',
1102 help='only export data for runs which match EXPRESSION. '
1103 'Example expression: "tags_contains:excellent,good"')
1105 parser.add_option(
1106 '--output', dest='filename', metavar='FILE',
1107 help='write output to FILE')
1109 parser.add_option(
1110 '--effective-lat-lon', dest='effective_lat_lon',
1111 action='store_true',
1112 help='convert north_shift/east_shift offsets to true lat/lon '
1113 'coordinates (when outputting event objects).')
1115 parser, options, args = cl_parse('export', args, setup)
1116 if len(args) < 2:
1117 help_and_die(parser, 'arguments required')
1119 what = args[0]
1121 dirnames = args[1:]
1123 what_choices = ('best', 'mean', 'ensemble', 'stats')
1125 if what not in what_choices:
1126 help_and_die(
1127 parser,
1128 'invalid choice: %s (choose from %s)' % (
1129 repr(what), ', '.join(repr(x) for x in what_choices)))
1131 if options.parameters:
1132 pnames = options.parameters.split(',')
1133 else:
1134 pnames = None
1136 try:
1137 grond.export(
1138 what,
1139 dirnames,
1140 filename=options.filename,
1141 type=options.type,
1142 pnames=pnames,
1143 selection=options.selection,
1144 effective_lat_lon=options.effective_lat_lon)
1146 except grond.GrondError as e:
1147 die(str(e))
1150def command_tag(args):
1152 def setup(parser):
1153 parser.add_option(
1154 '-d', '--dir-names',
1155 dest='show_dirnames',
1156 action='store_true',
1157 help='show directory names instead of run names')
1159 parser, options, args = cl_parse('tag', args, setup)
1160 if len(args) < 2:
1161 help_and_die(parser, 'two or more arguments required')
1163 action = args.pop(0)
1165 if action not in ('add', 'remove', 'list'):
1166 help_and_die(parser, 'invalid action: %s' % action)
1168 if action in ('add', 'remove'):
1169 if len(args) < 2:
1170 help_and_die(parser, 'three or more arguments required')
1172 tag = args.pop(0)
1174 rundirs = args
1176 if action == 'list':
1177 rundirs = args
1179 from grond.environment import Environment
1181 errors = False
1182 for rundir in rundirs:
1183 try:
1184 env = Environment([rundir])
1185 if options.show_dirnames:
1186 name = rundir
1187 else:
1188 name = env.get_problem().name
1190 info = env.get_run_info()
1191 if action == 'add':
1192 info.add_tag(tag)
1193 env.set_run_info(info)
1194 elif action == 'remove':
1195 info.remove_tag(tag)
1196 env.set_run_info(info)
1197 elif action == 'list':
1198 print('%-60s : %s' % (
1199 name,
1200 ', '.join(info.tags)))
1202 except grond.GrondError as e:
1203 errors = True
1204 logger.error(e)
1206 if errors:
1207 die('Errors occurred, see log messages above.')
1210def make_report(env_args, event_name, conf, update_without_plotting):
1211 from grond.environment import Environment
1212 from grond.report import report
1213 try:
1214 env = Environment(env_args)
1215 if event_name:
1216 env.set_current_event_name(event_name)
1218 report(
1219 env, conf,
1220 update_without_plotting=update_without_plotting,
1221 make_index=False,
1222 make_archive=False)
1224 return True
1226 except grond.GrondError as e:
1227 logger.error(str(e))
1228 return False
1231def command_report(args):
1233 import matplotlib
1234 matplotlib.use('Agg')
1236 from pyrocko import parimap
1238 from grond.environment import Environment
1239 from grond.report import \
1240 report_index, report_archive, serve_ip, serve_report, read_config, \
1241 write_config, ReportConfig
1243 def setup(parser):
1244 parser.add_option(
1245 '--index-only',
1246 dest='index_only',
1247 action='store_true',
1248 help='create index only')
1249 parser.add_option(
1250 '--serve', '-s',
1251 dest='serve',
1252 action='store_true',
1253 help='start http service')
1254 parser.add_option(
1255 '--serve-external', '-S',
1256 dest='serve_external',
1257 action='store_true',
1258 help='shortcut for --serve --host=default --fixed-port')
1259 parser.add_option(
1260 '--host',
1261 dest='host',
1262 default='localhost',
1263 help='<ip> to start the http server on. Special values for '
1264 '<ip>: "*" binds to all available interfaces, "default" '
1265 'to default external interface, "localhost" to "127.0.0.1".')
1266 parser.add_option(
1267 '--port',
1268 dest='port',
1269 type=int,
1270 default=8383,
1271 help='set default http server port. Will count up if port is '
1272 'already in use unless --fixed-port is given.')
1273 parser.add_option(
1274 '--fixed-port',
1275 dest='fixed_port',
1276 action='store_true',
1277 help='fail if port is already in use')
1278 parser.add_option(
1279 '--open', '-o',
1280 dest='open',
1281 action='store_true',
1282 help='open report in browser')
1283 parser.add_option(
1284 '--config',
1285 dest='config',
1286 metavar='FILE',
1287 help='report configuration file to use')
1288 parser.add_option(
1289 '--write-config',
1290 dest='write_config',
1291 metavar='FILE',
1292 help='write configuration (or default configuration) to FILE')
1293 parser.add_option(
1294 '--update-without-plotting',
1295 dest='update_without_plotting',
1296 action='store_true',
1297 help='quick-and-dirty update parameter files without plotting')
1298 parser.add_option(
1299 '--no-archive',
1300 dest='no_archive',
1301 action='store_true',
1302 help='don\'t create archive file.')
1304 parser, options, args = cl_parse('report', args, setup)
1306 s_conf = ''
1307 if options.config:
1308 try:
1309 conf = read_config(options.config)
1310 except grond.GrondError as e:
1311 die(str(e))
1313 s_conf = ' --config="%s"' % options.config
1314 else:
1315 from grond import plot
1316 conf = ReportConfig(
1317 plot_config_collection=plot.get_plot_config_collection())
1318 conf.set_basepath('.')
1320 if options.write_config:
1321 try:
1322 write_config(conf, options.write_config)
1323 sys.exit(0)
1325 except grond.GrondError as e:
1326 die(str(e))
1328 # commandline options that can override config values
1329 if options.no_archive:
1330 conf.make_archive = False
1332 if len(args) == 1 and op.exists(op.join(args[0], 'index.html')):
1333 conf.report_base_path = conf.rel_path(args[0])
1334 s_conf = ' %s' % args[0]
1335 args = []
1337 report_base_path = conf.expand_path(conf.report_base_path)
1339 if options.index_only:
1340 report_index(conf)
1341 report_archive(conf)
1342 args = []
1344 entries_generated = False
1346 payload = []
1347 if args and all(op.isdir(rundir) for rundir in args):
1348 rundirs = args
1349 all_failed = True
1350 for rundir in rundirs:
1351 payload.append((
1352 [rundir], None, conf, options.update_without_plotting))
1354 elif args:
1355 try:
1356 env = Environment(args)
1357 for event_name in env.get_selected_event_names():
1358 payload.append((
1359 args, event_name, conf, options.update_without_plotting))
1361 except grond.GrondError as e:
1362 die(str(e))
1364 if payload:
1365 entries_generated = []
1366 for result in parimap.parimap(
1367 make_report, *zip(*payload), nprocs=options.nparallel):
1369 entries_generated.append(result)
1371 all_failed = not any(entries_generated)
1372 entries_generated = any(entries_generated)
1374 if all_failed:
1375 die('no report entries generated')
1377 report_index(conf)
1378 report_archive(conf)
1380 if options.serve or options.serve_external:
1381 if options.serve_external:
1382 host = 'default'
1383 else:
1384 host = options.host
1386 addr = serve_ip(host), options.port
1388 serve_report(
1389 addr,
1390 report_config=conf,
1391 fixed_port=options.fixed_port or options.serve_external,
1392 open=options.open)
1394 elif options.open:
1395 import webbrowser
1396 url = 'file://%s/index.html' % op.abspath(report_base_path)
1397 webbrowser.open(url)
1399 else:
1400 if not entries_generated and not options.index_only:
1401 logger.info('Nothing to do, see: grond report --help')
1403 if entries_generated and not (options.serve or options.serve_external):
1404 logger.info(CLIHints('report', config=s_conf))
1407def command_qc_polarization(args):
1409 def setup(parser):
1410 parser.add_option(
1411 '--time-factor-pre', dest='time_factor_pre', type=float,
1412 metavar='NUMBER',
1413 default=0.5,
1414 help='set duration to extract before synthetic P phase arrival, '
1415 'relative to 1/fmin. fmin is taken from the selected target '
1416 'group in the config file (default=%default)')
1417 parser.add_option(
1418 '--time-factor-post', dest='time_factor_post', type=float,
1419 metavar='NUMBER',
1420 default=0.5,
1421 help='set duration to extract after synthetic P phase arrival, '
1422 'relative to 1/fmin. fmin is taken from the selected target '
1423 'group in the config file (default=%default)')
1424 parser.add_option(
1425 '--distance-min', dest='distance_min', type=float,
1426 metavar='NUMBER',
1427 help='minimum event-station distance [m]')
1428 parser.add_option(
1429 '--distance-max', dest='distance_max', type=float,
1430 metavar='NUMBER',
1431 help='maximum event-station distance [m]')
1432 parser.add_option(
1433 '--depth-min', dest='depth_min', type=float,
1434 metavar='NUMBER',
1435 help='minimum station depth [m]')
1436 parser.add_option(
1437 '--depth-max', dest='depth_max', type=float,
1438 metavar='NUMBER',
1439 help='maximum station depth [m]')
1440 parser.add_option(
1441 '--picks', dest='picks_filename',
1442 metavar='FILENAME',
1443 help='add file with P picks in Snuffler marker format')
1444 parser.add_option(
1445 '--save', dest='output_filename',
1446 metavar='FILENAME.FORMAT',
1447 help='save output to file FILENAME.FORMAT')
1448 parser.add_option(
1449 '--dpi', dest='output_dpi', type=float, default=120.,
1450 metavar='NUMBER',
1451 help='DPI setting for raster formats (default=120)')
1453 parser, options, args = cl_parse('qc-polarization', args, setup)
1454 if len(args) != 3:
1455 help_and_die(parser, 'missing arguments')
1457 if options.output_filename:
1458 import matplotlib
1459 matplotlib.use('Agg')
1461 import grond.qc
1463 config_path, event_name, target_group_path = args
1465 try:
1466 config = grond.read_config(config_path)
1467 except grond.GrondError as e:
1468 die(str(e))
1470 ds = config.get_dataset(event_name)
1472 engine = config.engine_config.get_engine()
1474 nsl_to_time = None
1475 if options.picks_filename:
1476 markers = marker.load_markers(options.picks_filename)
1477 marker.associate_phases_to_events(markers)
1479 nsl_to_time = {}
1480 for m in markers:
1481 if isinstance(m, marker.PhaseMarker):
1482 ev = m.get_event()
1483 if ev is not None and ev.name == event_name:
1484 nsl_to_time[m.one_nslc()[:3]] = m.tmin
1486 if not nsl_to_time:
1487 help_and_die(
1488 parser,
1489 'no markers associated with event "%s" found in file "%s"' % (
1490 event_name, options.picks_filename))
1492 target_group_paths_avail = []
1493 for target_group in config.target_groups:
1494 name = target_group.path
1495 if name == target_group_path:
1496 imc = target_group.misfit_config
1497 fmin = imc.fmin
1498 fmax = imc.fmax
1499 ffactor = imc.ffactor
1501 store = engine.get_store(target_group.store_id)
1502 timing = '{cake:P|cake:p|cake:P\\|cake:p\\}'
1504 grond.qc.polarization(
1505 ds, store, timing, fmin=fmin, fmax=fmax, ffactor=ffactor,
1506 time_factor_pre=options.time_factor_pre,
1507 time_factor_post=options.time_factor_post,
1508 distance_min=options.distance_min,
1509 distance_max=options.distance_max,
1510 depth_min=options.depth_min,
1511 depth_max=options.depth_max,
1512 nsl_to_time=nsl_to_time,
1513 output_filename=options.output_filename,
1514 output_dpi=options.output_dpi)
1516 return
1518 target_group_paths_avail.append(name)
1520 die('no target group with path "%s" found. Available: %s' % (
1521 target_group_path, ', '.join(target_group_paths_avail)))
1524def command_upgrade_config(args):
1525 def setup(parser):
1526 parser.add_option(
1527 '--diff', dest='diff', action='store_true',
1528 help='create diff between normalized old and new versions')
1530 parser, options, args = cl_parse('upgrade-config', args, setup)
1531 if len(args) != 1:
1532 help_and_die(parser, 'missing argument <configfile>')
1534 from grond import upgrade
1535 upgrade.upgrade_config_file(args[0], diff=options.diff)
1538def command_diff(args):
1539 def setup(parser):
1540 pass
1542 parser, options, args = cl_parse('diff', args, setup)
1543 if len(args) != 2:
1544 help_and_die(parser, 'requires exactly two arguments')
1546 from grond.config import diff_configs
1547 diff_configs(*args)
1550def command_version(args):
1551 def setup(parser):
1552 parser.add_option(
1553 '--short', dest='short', action='store_true',
1554 help='only print Grond\'s version number')
1555 parser.add_option(
1556 '--failsafe', dest='failsafe', action='store_true',
1557 help='do not get irritated when some dependencies are missing')
1559 parser, options, args = cl_parse('version', args, setup)
1561 if options.short:
1562 print(grond.__version__)
1563 return
1565 elif not options.failsafe:
1566 from grond import info
1567 print(info.version_info())
1568 return
1570 print("grond: %s" % grond.__version__)
1572 try:
1573 import pyrocko
1574 print('pyrocko: %s' % pyrocko.long_version)
1575 except ImportError:
1576 print('pyrocko: N/A')
1578 try:
1579 import numpy
1580 print('numpy: %s' % numpy.__version__)
1581 except ImportError:
1582 print('numpy: N/A')
1584 try:
1585 import scipy
1586 print('scipy: %s' % scipy.__version__)
1587 except ImportError:
1588 print('scipy: N/A')
1590 try:
1591 import matplotlib
1592 print('matplotlib: %s' % matplotlib.__version__)
1593 except ImportError:
1594 print('matplotlib: N/A')
1596 try:
1597 from pyrocko.gui.qt_compat import Qt
1598 print('PyQt: %s' % Qt.PYQT_VERSION_STR)
1599 print('Qt: %s' % Qt.QT_VERSION_STR)
1600 except ImportError:
1601 print('PyQt: N/A')
1602 print('Qt: N/A')
1604 import sys
1605 print('python: %s.%s.%s' % sys.version_info[:3])
1607 if not options.failsafe:
1608 die('fell back to failsafe version printing')
1611if __name__ == '__main__':
1612 main()