Coverage for /usr/local/lib/python3.11/dist-packages/grond/apps/grond.py: 52%

724 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-06-12 14:01 +0000

1#!/usr/bin/env python 

2 

3from __future__ import print_function, absolute_import 

4 

5import sys 

6import os.path as op 

7import logging 

8from optparse import OptionParser, OptionValueError 

9import grond 

10from io import StringIO 

11 

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.') 

17 

18 

19logger = logging.getLogger('grond.main') 

20km = 1e3 

21 

22 

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' 

34 

35 

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('-', '_') 

41 

42 

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 'forward': 'run forward modelling', 

50 'harvest': 'manually run harvesting', 

51 'cluster': 'run cluster analysis on result ensemble', 

52 'plot': 'plot optimisation result', 

53 'movie': 'visualize optimiser evolution', 

54 'export': 'export results', 

55 'tag': 'add user-defined label to run directories', 

56 'report': 'create result report', 

57 'diff': 'compare two configs or other normalized Grond YAML files', 

58 'qc-polarization': 'check sensor orientations with polarization analysis', 

59 'upgrade-config': 'upgrade config file to the latest version of Grond', 

60 'version': 'print version number of Grond and its main dependencies', 

61} 

62 

63subcommand_usages = { 

64 'init': ( 

65 'init list [options]', 

66 'init <example> [options]', 

67 'init <example> <projectdir> [options]'), 

68 'scenario': 'scenario [options] <projectdir>', 

69 'events': 'events <configfile>', 

70 'check': 'check <configfile> <eventnames> ... [options]', 

71 'go': 'go <configfile> <eventnames> ... [options]', 

72 'forward': ( 

73 'forward <rundir> [options]', 

74 'forward <configfile> <eventnames> ... [options]'), 

75 'harvest': 'harvest <rundir> [options]', 

76 'cluster': ( 

77 'cluster <method> <rundir> [options]', 

78 'cluster <clusteringconfigfile> <rundir> [options]'), 

79 'plot': ( 

80 'plot <plotnames> ( <rundir> | <configfile> <eventname> ) [options]', 

81 'plot all ( <rundir> | <configfile> <eventname> ) [options]', 

82 'plot <plotconfigfile> ( <rundir> | <configfile> <eventname> ) [options]', # noqa 

83 'plot list ( <rundir> | <configfile> <eventname> ) [options]', 

84 'plot config ( <rundir> | <configfile> <eventname> ) [options]'), 

85 'movie': 'movie <rundir> <xpar> <ypar> <filetemplate> [options]', 

86 'export': 'export (best|mean|ensemble|stats) <rundirs> ... [options]', 

87 'tag': ( 

88 'tag add <tag> <rundir>', 

89 'tag remove <tag> <rundir>', 

90 'tag list <rundir>'), 

91 'report': ( 

92 'report <rundir> ... [options]', 

93 'report <configfile> <eventnames> ...'), 

94 'diff': 'diff <left_path> <right_path>', 

95 'qc-polarization': 'qc-polarization <configfile> <eventname> ' 

96 '<target_group_path> [options]', 

97 'upgrade-config': 'upgrade-config <configfile>', 

98 'version': 'version', 

99} 

100 

101subcommands = subcommand_descriptions.keys() 

102 

103program_name = 'grond' 

104 

105usage_tdata = d2u(subcommand_descriptions) 

106usage_tdata['program_name'] = program_name 

107usage_tdata['version_number'] = grond.__version__ 

108 

109 

110usage = '''%(program_name)s <subcommand> [options] [--] <arguments> ... 

111 

112Grond is a probabilistic earthquake source inversion framework. 

113 

114This is Grond version %(version_number)s. 

115 

116Subcommands: 

117 

118 scenario %(scenario)s 

119 init %(init)s 

120 events %(events)s 

121 check %(check)s 

122 go %(go)s 

123 forward %(forward)s 

124 harvest %(harvest)s 

125 cluster %(cluster)s 

126 plot %(plot)s 

127 movie %(movie)s 

128 export %(export)s 

129 tag %(tag)s 

130 report %(report)s 

131 diff %(diff)s 

132 qc-polarization %(qc_polarization)s 

133 upgrade-config %(upgrade_config)s 

134 version %(version)s 

135 

136To get further help and a list of available options for any subcommand run: 

137 

138 %(program_name)s <subcommand> --help 

139 

140What do you want to bust today?! 

141''' % usage_tdata 

142 

143 

144class CLIHints(object): 

145 init = ''' 

146We created a folder structure in {project_dir}. 

147Check out the YAML configuration in {config} and start the optimisation by: 

148 

149 grond go {config} 

150''' 

151 scenario = ''' 

152To start the scenario's optimisation, change to folder 

153 

154 cd {project_dir} 

155 

156Check out the YAML configuration in {config} and start the optimisation by: 

157 

158 grond go {config} 

159''' 

160 report = ''' 

161To open the report in your web browser, run 

162 

163 grond report -s --open {config} 

164''' 

165 check = ''' 

166To start the optimisation, run 

167 

168 grond go {config} 

169''' 

170 go = ''' 

171To look at the results, run 

172 

173 grond report -so {rundir} 

174''' 

175 

176 def __new__(cls, command, **kwargs): 

177 return '{c.BOLD}Hint{c.END}\n'.format(c=Color) +\ 

178 getattr(cls, command).format(**kwargs) 

179 

180 

181def main(args=None): 

182 if not args: 

183 args = sys.argv 

184 

185 args = list(args) 

186 if len(args) < 2: 

187 sys.exit('Usage: %s' % usage) 

188 

189 args.pop(0) 

190 command = args.pop(0) 

191 

192 if command in subcommands: 

193 globals()['command_' + d2u(command)](args) 

194 

195 elif command in ('--help', '-h', 'help'): 

196 if command == 'help' and args: 

197 acommand = args[0] 

198 if acommand in subcommands: 

199 globals()['command_' + acommand](['--help']) 

200 

201 sys.exit('Usage: %s' % usage) 

202 

203 else: 

204 die('No such subcommand: %s' % command) 

205 

206 

207def add_common_options(parser): 

208 parser.add_option( 

209 '--loglevel', 

210 action='store', 

211 dest='loglevel', 

212 type='choice', 

213 choices=('critical', 'error', 'warning', 'info', 'debug'), 

214 help='set logger level to ' 

215 '"critical", "error", "warning", "info", or "debug". ' 

216 'Default is "info".') 

217 

218 parser.add_option( 

219 '--status', dest='status', 

220 type='choice', choices=['state', 'quiet'], 

221 help='status output selection (choices: state, quiet, default: ' 

222 'state)') 

223 

224 parser.add_option( 

225 '--parallel', dest='nparallel', type=int, 

226 help='set number of events to process in parallel, ' 

227 'if set to more than one, --status=quiet is implied.') 

228 

229 parser.add_option( 

230 '--threads', dest='nthreads', type=int, 

231 help='set number of threads per process (default: 1). ' 

232 'Set to 0 to use all available cores.') 

233 

234 parser.add_option( 

235 '--docs', 

236 dest='rst_docs', 

237 action='store_true') 

238 

239 

240def print_docs(command, parser): 

241 

242 from optparse import IndentedHelpFormatter 

243 

244 class DocsFormatter(IndentedHelpFormatter): 

245 

246 def format_heading(self, heading): 

247 return '%s\n%s\n\n' % (heading, '.'*len(heading)) 

248 

249 def format_usage(self, usage): 

250 lines = usage.splitlines() 

251 return self.format_heading('Usage') + \ 

252 '.. code-block:: none\n\n%s' % '\n'.join( 

253 ' '+line.strip() for line in lines) 

254 

255 def format_option(self, option): 

256 if not option.help: 

257 return '' 

258 

259 result = [] 

260 opts = self.option_strings[option] 

261 result.append('\n.. describe:: %s\n\n' % opts) 

262 

263 help_text = self.expand_default(option) 

264 result.append(' %s\n\n' % help_text) 

265 

266 return ''.join(result) 

267 

268 parser.formatter = DocsFormatter() 

269 parser.formatter.set_parser(parser) 

270 

271 def format_help(parser): 

272 formatter = parser.formatter 

273 result = [] 

274 

275 result.append(parser.format_description(formatter) + "\n") 

276 

277 if parser.usage: 

278 result.append(parser.get_usage() + "\n") 

279 

280 result.append('\n') 

281 

282 result.append(parser.format_option_help(formatter)) 

283 

284 result.append('\n') 

285 

286 result.append(parser.format_epilog(formatter)) 

287 return "".join(result) 

288 

289 print(command) 

290 print('-' * len(command)) 

291 print() 

292 print('.. program:: %s' % program_name) 

293 print() 

294 print('.. option:: %s' % command) 

295 print() 

296 print(format_help(parser)) 

297 

298 

299def process_common_options(command, parser, options): 

300 from grond.config import get_global_config 

301 

302 gconf = get_global_config() 

303 gconf.override_with_cli_arguments(options) 

304 

305 util.setup_logging(program_name, gconf.loglevel) 

306 if options.rst_docs: 

307 print_docs(command, parser) 

308 exit(0) 

309 

310 

311def cl_parse(command, args, setup=None, details=None): 

312 usage = subcommand_usages[command] 

313 descr = subcommand_descriptions[command] 

314 

315 if isinstance(usage, str): 

316 usage = [usage] 

317 

318 susage = '%s %s' % (program_name, usage[0]) 

319 for s in usage[1:]: 

320 susage += '\n%s%s %s' % (' '*7, program_name, s) 

321 

322 description = descr[0].upper() + descr[1:] + '.' 

323 

324 if details: 

325 description = description + '\n\n%s' % details 

326 

327 parser = OptionParser(usage=susage, description=description) 

328 

329 if setup: 

330 setup(parser) 

331 

332 add_common_options(parser) 

333 (options, args) = parser.parse_args(args) 

334 process_common_options(command, parser, options) 

335 return parser, options, args 

336 

337 

338def die(message, err='', prelude=''): 

339 if prelude: 

340 prelude = prelude + '\n' 

341 

342 if err: 

343 err = '\n' + err 

344 

345 sys.exit('%s%s failed: %s%s' % (prelude, program_name, message, err)) 

346 

347 

348def help_and_die(parser, message): 

349 sio = StringIO() 

350 parser.print_help(sio) 

351 die(message, prelude=sio.getvalue()) 

352 

353 

354def multiple_choice(option, opt_str, value, parser, choices): 

355 options = value.split(',') 

356 for opt in options: 

357 if opt not in choices: 

358 raise OptionValueError('Invalid option %s - valid options are: %s' 

359 % (opt, ', '.join(choices))) 

360 setattr(parser.values, option.dest, options) 

361 

362 

363def magnitude_range(option, opt_str, value, parser): 

364 mag_range = value.split('-') 

365 if len(mag_range) != 2: 

366 raise OptionValueError( 

367 'Invalid magnitude %s - valid range is e.g. 6-7.' % value) 

368 try: 

369 mag_range = tuple(map(float, mag_range)) 

370 except ValueError: 

371 raise OptionValueError('Magnitudes must be numbers.') 

372 

373 if mag_range[0] > mag_range[1]: 

374 raise OptionValueError('Minimum magnitude must be larger than' 

375 ' maximum magnitude.') 

376 setattr(parser.values, option.dest, mag_range) 

377 

378 

379def command_scenario(args): 

380 

381 STORE_STATIC = 'crust2_ib_static' 

382 STORE_WAVEFORMS = 'crust2_ib' 

383 

384 def setup(parser): 

385 parser.add_option( 

386 '--targets', action='callback', dest='targets', type=str, 

387 callback=multiple_choice, callback_kwargs={ 

388 'choices': ('waveforms', 'gnss', 'insar') 

389 }, 

390 default='waveforms', 

391 help='forward modelling targets for the scenario. Select from:' 

392 ' waveforms, gnss and insar. ' 

393 '(default: --targets=%default,' 

394 ' multiple selection by --targets=waveforms,gnss,insar)') 

395 parser.add_option( 

396 '--problem', dest='problem', default='cmt', 

397 type='choice', choices=['cmt', 'rectangular'], 

398 help='problem to generate: \'dc\' (double couple)' 

399 ' or \'rectangular\' (rectangular finite fault)' 

400 ' (default: \'%default\')') 

401 parser.add_option( 

402 '--magnitude-range', dest='magnitude_range', type=str, 

403 action='callback', callback=magnitude_range, default=[6.0, 7.0], 

404 help='Magnitude range min_mag-max_mag (default: %default)') 

405 parser.add_option( 

406 '--nstations', dest='nstations', type=int, default=20, 

407 help='number of seismic stations to create (default: %default)') 

408 parser.add_option( 

409 '--gnss_nstations', dest='gnss_nstations', type=int, default=20, 

410 help='number of GNSS campaign stations to create' 

411 ' (default: %default)') 

412 parser.add_option( 

413 '--nevents', dest='nevents', type=int, default=1, 

414 help='number of events to create (default: %default)') 

415 parser.add_option( 

416 '--lat', dest='lat', type=float, default=41.0, 

417 help='center latitude of the scenario (default: %default)') 

418 parser.add_option( 

419 '--lon', dest='lon', type=float, default=33.3, 

420 help='center latitude of the scenario (default: %default)') 

421 parser.add_option( 

422 '--radius', dest='radius', type=float, default=100., 

423 help='radius of the scenario in [km] (default: %default)') 

424 parser.add_option( 

425 '--source-radius', dest='source_radius', type=float, default=10., 

426 help='radius of the source area in [km] (default: %default)') 

427 parser.add_option( 

428 '--stations-paths', dest='stations_paths', type=str, default=None, 

429 help='paths to a Pyrocko station file, seperated by \',\'' 

430 '(default: %default)') 

431 parser.add_option( 

432 '--stationxml-paths', dest='stationxml_paths', type=str, 

433 default=None, 

434 help='paths to StationXML files, seperated by \',\'' 

435 '(default: %default)') 

436 parser.add_option( 

437 '--gf-waveforms', dest='store_waveforms', type=str, 

438 default=STORE_WAVEFORMS, 

439 help='Green\'s function store for waveform modelling, ' 

440 '(default: %default)') 

441 parser.add_option( 

442 '--gf-static', dest='store_statics', type=str, 

443 default=STORE_STATIC, 

444 help='Green\'s function store for static modelling, ' 

445 '(default: %default)') 

446 parser.add_option( 

447 '--force', dest='force', action='store_true', 

448 help='overwrite existing project folder.') 

449 parser.add_option( 

450 '--gf-store-superdirs', 

451 dest='gf_store_superdirs', 

452 help='Comma-separated list of directories containing GF stores') 

453 parser.add_option( 

454 '--no-map', 

455 dest='make_map', 

456 default=True, 

457 action='store_false', 

458 help='suppress generation of map') 

459 parser.add_option( 

460 '--rebuild', 

461 dest='rebuild', action='store_true', default=False, 

462 help='Rebuild a manually configured grond scenario') 

463 

464 parser, options, args = cl_parse('scenario', args, setup) 

465 

466 gf_store_superdirs = None 

467 if options.gf_store_superdirs: 

468 gf_store_superdirs = options.gf_store_superdirs.split(',') 

469 

470 if len(args) == 1: 

471 project_dir = args[0] 

472 else: 

473 parser.print_help() 

474 sys.exit(1) 

475 

476 from grond import scenario as grond_scenario 

477 

478 try: 

479 scenario = grond_scenario.GrondScenario( 

480 project_dir, 

481 center_lat=options.lat, center_lon=options.lon, 

482 radius=options.radius*km) 

483 

484 scenario.rebuild = options.rebuild 

485 if options.rebuild: 

486 options.force = True 

487 

488 if 'waveforms' in options.targets: 

489 if options.stationxml_paths: 

490 options.stationxml_paths = [ 

491 op.abspath(path) for path in 

492 options.stationxml_paths.split(',')] 

493 

494 if options.stations_paths: 

495 options.stations_paths = [ 

496 op.abspath(path) for path in 

497 options.stations_paths.split(',')] 

498 

499 obs = grond_scenario.WaveformObservation( 

500 nstations=options.nstations, 

501 store_id=options.store_waveforms, 

502 stations_paths=options.stations_paths, 

503 stationxml_paths=options.stationxml_paths) 

504 scenario.add_observation(obs) 

505 

506 if 'insar' in options.targets: 

507 obs = grond_scenario.InSARObservation( 

508 store_id=options.store_statics) 

509 scenario.add_observation(obs) 

510 

511 if 'gnss' in options.targets: 

512 obs = grond_scenario.GNSSCampaignObservation( 

513 nstations=options.gnss_nstations, 

514 store_id=options.store_statics) 

515 scenario.add_observation(obs) 

516 

517 if options.problem == 'cmt': 

518 problem = grond_scenario.DCSourceProblem( 

519 nevents=options.nevents, 

520 radius=options.source_radius*km, 

521 magnitude_min=options.magnitude_range[0], 

522 magnitude_max=options.magnitude_range[1]) 

523 elif options.problem == 'rectangular': 

524 problem = grond_scenario.RectangularSourceProblem( 

525 nevents=options.nevents) 

526 scenario.set_problem(problem) 

527 

528 scenario.build( 

529 force=options.force, 

530 interactive=True, 

531 gf_store_superdirs=gf_store_superdirs, 

532 make_map=options.make_map) 

533 

534 logger.info(CLIHints('scenario', 

535 config=scenario.get_grond_config_path(), 

536 project_dir=project_dir)) 

537 

538 except grond.GrondError as e: 

539 die(str(e)) 

540 

541 

542def command_init(args): 

543 

544 from .cmd_init import GrondInit 

545 

546 grond_init = GrondInit() 

547 

548 def print_section(entries): 

549 if len(entries) == 0: 

550 return '\tNone available.' 

551 

552 padding = max([len(n) for n in entries.keys()]) 

553 rstr = [] 

554 lcat = None 

555 for name, desc in entries.items(): 

556 

557 cat = name.split('_')[0] 

558 if lcat is not None and lcat != cat: 

559 rstr.append('') 

560 lcat = cat 

561 

562 rstr.append(' {c.BOLD}{name:<{padding}}{c.END} : {desc}'.format( 

563 name=name, desc=desc, padding=padding, c=Color)) 

564 return '\n'.join(rstr) 

565 

566 help_text = '''Available configuration examples for Grond. 

567 

568{c.BOLD}Example Projects{c.END} 

569 

570 Deploy a full project structure into a directory. 

571 

572 usage: grond init <example> <projectdir> 

573 

574 where <example> is any of the following: 

575 

576{examples_list} 

577 

578{c.BOLD}Config Sections{c.END} 

579 

580 Print out configuration snippets for various components. 

581 

582 usage: grond init <section> 

583 

584 where <section> is any of the following: 

585 

586{sections_list} 

587'''.format(c=Color, 

588 examples_list=print_section(grond_init.get_examples()), 

589 sections_list=print_section(grond_init.get_sections())) 

590 

591 def setup(parser): 

592 parser.add_option( 

593 '--force', dest='force', action='store_true') 

594 

595 parser, options, args = cl_parse( 

596 'init', args, setup, 

597 'Use grond init list to show available examples.') 

598 

599 if len(args) not in (1, 2): 

600 help_and_die(parser, '1 or 2 arguments required') 

601 

602 if args[0] == 'list': 

603 print(help_text) 

604 return 

605 

606 if args[0].startswith('example_'): 

607 if len(args) == 1: 

608 config = grond_init.get_content_example(args[0]) 

609 if not config: 

610 help_and_die(parser, 'Unknown example: %s' % args[0]) 

611 

612 sys.stdout.write(config+'\n\n') 

613 

614 logger.info('Hint: To create a project, use: grond init <example> ' 

615 '<projectdir>') 

616 

617 elif op.exists(op.abspath(args[1])) and not options.force: 

618 help_and_die( 

619 parser, 

620 'Directory "%s" already exists! Use --force to overwrite.' 

621 % args[1]) 

622 else: 

623 try: 

624 grond_init.init_example(args[0], args[1], force=options.force) 

625 except OSError as e: 

626 print(str(e)) 

627 

628 else: 

629 sec = grond_init.get_content_snippet(args[0]) 

630 if not sec: 

631 help_and_die(parser, 'Unknown snippet: %s' % args[0]) 

632 

633 sys.stdout.write(sec) 

634 

635 

636def command_init_old(args): 

637 

638 from . import cmd_init as init 

639 

640 def setup(parser): 

641 parser.add_option( 

642 '--targets', action='callback', dest='targets', type=str, 

643 callback=multiple_choice, callback_kwargs={ 

644 'choices': ('waveforms', 'gnss', 'insar', 'all') 

645 }, 

646 default='waveforms', 

647 help='select from:' 

648 ' waveforms, gnss and insar. ' 

649 '(default: --targets=%default,' 

650 ' multiple selection by --targets=waveforms,gnss,insar)') 

651 parser.add_option( 

652 '--problem', dest='problem', 

653 type='choice', choices=['cmt', 'rectangular'], 

654 help='problem to generate: \'dc\' (double couple)' 

655 ' or\'rectangular\' (rectangular finite fault)' 

656 ' (default: \'%default\')') 

657 parser.add_option( 

658 '--force', dest='force', action='store_true', 

659 help='overwrite existing project folder') 

660 

661 parser, options, args = cl_parse('init', args, setup) 

662 

663 try: 

664 project = init.GrondProject() 

665 

666 if 'all' in options.targets: 

667 targets = ['waveforms', 'gnss', 'insar'] 

668 else: 

669 targets = options.targets 

670 

671 if not options.problem: 

672 if 'insar' in targets or 'gnss' in targets: 

673 problem = 'rectangular' 

674 else: 

675 problem = 'cmt' 

676 else: 

677 problem = options.problem 

678 

679 if problem == 'rectangular': 

680 project.set_rectangular_source() 

681 elif problem == 'cmt': 

682 project.set_cmt_source() 

683 

684 if 'waveforms' in targets: 

685 project.add_waveforms() 

686 

687 if 'insar' in targets: 

688 project.add_insar() 

689 

690 if 'gnss' in targets: 

691 project.add_gnss() 

692 

693 if len(args) == 1: 

694 project_dir = args[0] 

695 project.build(project_dir, options.force) 

696 logger.info(CLIHints( 

697 'init', project_dir=project_dir, 

698 config=op.join(project_dir, 'config', 'config.gronf'))) 

699 else: 

700 sys.stdout.write(project.dump()) 

701 

702 except grond.GrondError as e: 

703 die(str(e)) 

704 

705 

706def command_events(args): 

707 def setup(parser): 

708 pass 

709 

710 parser, options, args = cl_parse('events', args, setup) 

711 if len(args) != 1: 

712 help_and_die(parser, 'missing arguments') 

713 

714 config_path = args[0] 

715 try: 

716 config = grond.read_config(config_path) 

717 

718 for event_name in grond.get_event_names(config): 

719 print(event_name) 

720 

721 except grond.GrondError as e: 

722 die(str(e)) 

723 

724 

725def command_check(args): 

726 

727 from grond.environment import Environment 

728 

729 def setup(parser): 

730 parser.add_option( 

731 '--target-ids', dest='target_string_ids', metavar='TARGET_IDS', 

732 help='process only selected targets. TARGET_IDS is a ' 

733 'comma-separated list of target IDs. Target IDs have the ' 

734 'form SUPERGROUP.GROUP.NETWORK.STATION.LOCATION.CHANNEL.') 

735 

736 parser.add_option( 

737 '--waveforms', dest='show_waveforms', action='store_true', 

738 help='show raw, restituted, projected, and processed waveforms') 

739 

740 parser.add_option( 

741 '--nrandom', dest='n_random_synthetics', metavar='N', type=int, 

742 default=10, 

743 help='set number of random synthetics to forward model (default: ' 

744 '10). If set to zero, create synthetics for the reference ' 

745 'solution.') 

746 

747 parser.add_option( 

748 '--save-stations-used', dest='stations_used_path', 

749 metavar='FILENAME', 

750 help='aggregate all stations used by the setup into a file') 

751 

752 parser, options, args = cl_parse('check', args, setup) 

753 if len(args) < 1: 

754 help_and_die(parser, 'missing arguments') 

755 

756 try: 

757 env = Environment(args) 

758 config = env.get_config() 

759 

760 target_string_ids = None 

761 if options.target_string_ids: 

762 target_string_ids = options.target_string_ids.split(',') 

763 

764 grond.check( 

765 config, 

766 event_names=env.get_selected_event_names(), 

767 target_string_ids=target_string_ids, 

768 show_waveforms=options.show_waveforms, 

769 n_random_synthetics=options.n_random_synthetics, 

770 stations_used_path=options.stations_used_path) 

771 

772 logger.info(CLIHints('check', config=env.get_config_path())) 

773 

774 except grond.GrondError as e: 

775 die(str(e)) 

776 

777 

778def command_go(args): 

779 

780 from grond.environment import Environment 

781 

782 def setup(parser): 

783 parser.add_option( 

784 '--force', dest='force', action='store_true', 

785 help='overwrite existing run directory') 

786 parser.add_option( 

787 '--preserve', dest='preserve', action='store_true', 

788 help='preserve old rundir') 

789 

790 parser, options, args = cl_parse('go', args, setup) 

791 

792 try: 

793 env = Environment(args) 

794 

795 grond.go( 

796 env, 

797 force=options.force, 

798 preserve=options.preserve) 

799 

800 if len(env.get_selected_event_names()) == 1: 

801 logger.info(CLIHints( 

802 'go', rundir=env.get_rundir_path())) 

803 

804 except grond.GrondError as e: 

805 die(str(e)) 

806 

807 

808def command_forward(args): 

809 

810 from grond.environment import Environment 

811 

812 def setup(parser): 

813 parser.add_option( 

814 '--show', dest='show', metavar='WHAT', 

815 default='filtered', 

816 choices=('filtered', 'processed'), 

817 help='select whether to show only "filtered" or fully "processed" ' 

818 '(i.e. tapered) waveforms (default "%default").') 

819 

820 parser, options, args = cl_parse('forward', args, setup) 

821 if len(args) < 1: 

822 help_and_die(parser, 'missing arguments') 

823 

824 try: 

825 env = Environment(args) 

826 grond.forward(env, show=options.show) 

827 except grond.GrondError as e: 

828 die(str(e)) 

829 

830 

831def command_harvest(args): 

832 def setup(parser): 

833 parser.add_option( 

834 '--force', dest='force', action='store_true', 

835 help='overwrite existing harvest directory') 

836 parser.add_option( 

837 '--neach', dest='neach', type=int, default=10, 

838 help='take NEACH best samples from each chain (default: %default)') 

839 parser.add_option( 

840 '--weed', dest='weed', type=int, default=0, 

841 help='weed out bootstrap samples with bad global performance. ' 

842 '0: no weeding (default), ' 

843 '1: only bootstrap chains where all NEACH best samples ' 

844 'global misfit is less than the global average misfit of all ' 

845 'NEACH best in all chains plus one standard deviation are ' 

846 'included in the harvest ensemble, ' 

847 '2: same as 1 but additionally individual samples are ' 

848 'removed if their global misfit is greater than the global ' 

849 'average misfit of all NEACH best in all chains, ' 

850 '3: harvesting is done on the global chain only, bootstrap ' 

851 'chains are excluded') 

852 parser.add_option( 

853 '--export-fits', dest='export_fits', default='', 

854 help='additionally export details about the fit of individual ' 

855 'targets. "best" - export fits of best model, "mean" - ' 

856 'export fits of ensemble mean model, "ensemble" - export ' 

857 'fits of all models in harvest ensemble.') 

858 

859 parser, options, args = cl_parse('harvest', args, setup) 

860 if len(args) < 1: 

861 help_and_die(parser, 'no rundir') 

862 

863 export_fits = [] 

864 if options.export_fits.strip(): 

865 export_fits = [x.strip() for x in options.export_fits.split(',')] 

866 

867 for run_path in args: 

868 try: 

869 grond.harvest( 

870 run_path, 

871 force=options.force, 

872 nbest=options.neach, 

873 weed=options.weed, 

874 export_fits=export_fits) 

875 

876 except grond.DirectoryAlreadyExists as e: 

877 die(str(e) + '\n Use --force to overwrite.') 

878 

879 except grond.GrondError as e: 

880 die(str(e)) 

881 

882 

883def command_cluster(args): 

884 from grond import Clustering 

885 from grond.clustering import metrics, methods, read_config, write_config 

886 

887 def setup(parser): 

888 parser.add_option( 

889 '--metric', dest='metric', metavar='METRIC', 

890 default='kagan_angle', 

891 choices=metrics.metrics, 

892 help='metric to measure model distances. Choices: [%s]. Default: ' 

893 'kagan_angle' % ', '.join(metrics.metrics)) 

894 

895 parser.add_option( 

896 '--write-config', 

897 dest='write_config', 

898 metavar='FILE', 

899 help='write configuration (or default configuration) to FILE') 

900 

901 method = args[0] if args else '' 

902 try: 

903 parser, options, args = cl_parse( 

904 'cluster', args[1:], setup=Clustering.cli_setup(method, setup), 

905 details='Available clustering methods: [%s]. Use ' 

906 '"grond cluster <method> --help" to get list of method ' 

907 'dependent options.' % ', '.join(methods)) 

908 

909 if method not in Clustering.name_to_class and not op.exists(method): 

910 help_and_die( 

911 parser, 

912 'no such clustering method: %s' % method if method else 

913 'no clustering method specified') 

914 

915 if op.exists(method): 

916 clustering = read_config(method) 

917 else: 

918 clustering = Clustering.cli_instantiate(method, options) 

919 

920 if options.write_config: 

921 write_config(clustering, options.write_config) 

922 else: 

923 if len(args) != 1: 

924 help_and_die(parser, 'no rundir') 

925 run_path, = args 

926 

927 grond.cluster(run_path, clustering, metric=options.metric) 

928 

929 except grond.GrondError as e: 

930 die(str(e)) 

931 

932 

933def command_plot(args): 

934 

935 def setup(parser): 

936 parser.add_option( 

937 '--show', dest='show', action='store_true', 

938 help='show plot for interactive inspection') 

939 

940 details = '' 

941 

942 parser, options, args = cl_parse('plot', args, setup, details) 

943 

944 if not options.show: 

945 import matplotlib 

946 matplotlib.use('Agg') 

947 

948 from grond.environment import Environment 

949 

950 if len(args) not in (1, 2, 3): 

951 help_and_die(parser, '1, 2 or 3 arguments required') 

952 

953 if len(args) > 1: 

954 env = Environment(args[1:]) 

955 else: 

956 env = None 

957 

958 from grond import plot 

959 if args[0] == 'list': 

960 

961 def get_doc_title(doc): 

962 for ln in doc.split('\n'): 

963 ln = ln.strip() 

964 if ln != '': 

965 ln = ln.strip('.') 

966 return ln 

967 return 'Undocumented.' 

968 

969 if env: 

970 plot_classes = env.get_plot_classes() 

971 else: 

972 plot_classes = plot.get_all_plot_classes() 

973 

974 plot_names, plot_doc = zip(*[(pc.name, pc.__doc__) 

975 for pc in plot_classes]) 

976 

977 plot_descs = [get_doc_title(doc) for doc in plot_doc] 

978 left_spaces = max([len(pn) for pn in plot_names]) 

979 

980 for name, desc in zip(plot_names, plot_descs): 

981 print('{name:<{ls}} - {desc}'.format( 

982 ls=left_spaces, name=name, desc=desc)) 

983 

984 elif args[0] == 'config': 

985 plot_config_collection = plot.get_plot_config_collection(env) 

986 print(plot_config_collection) 

987 

988 elif args[0] == 'all': 

989 if env is None: 

990 help_and_die(parser, 'two or three arguments required') 

991 plot_names = plot.get_plot_names(env) 

992 plot.make_plots(env, plot_names=plot_names, show=options.show) 

993 

994 elif op.exists(args[0]): 

995 if env is None: 

996 help_and_die(parser, 'two or three arguments required') 

997 plots = plot.PlotConfigCollection.load(args[0]) 

998 plot.make_plots(env, plots, show=options.show) 

999 

1000 else: 

1001 if env is None: 

1002 help_and_die(parser, 'two or three arguments required') 

1003 plot_names = [name.strip() for name in args[0].split(',')] 

1004 plot.make_plots(env, plot_names=plot_names, show=options.show) 

1005 

1006 

1007def command_movie(args): 

1008 

1009 import matplotlib 

1010 matplotlib.use('Agg') 

1011 

1012 def setup(parser): 

1013 pass 

1014 

1015 parser, options, args = cl_parse('movie', args, setup) 

1016 

1017 if len(args) != 4: 

1018 help_and_die(parser, 'four arguments required') 

1019 

1020 run_path, xpar_name, ypar_name, movie_filename_template = args 

1021 

1022 from grond import plot 

1023 

1024 movie_filename = movie_filename_template % { 

1025 'xpar': xpar_name, 

1026 'ypar': ypar_name} 

1027 

1028 try: 

1029 plot.make_movie(run_path, xpar_name, ypar_name, movie_filename) 

1030 

1031 except grond.GrondError as e: 

1032 die(str(e)) 

1033 

1034 

1035def command_export(args): 

1036 

1037 def setup(parser): 

1038 parser.add_option( 

1039 '--type', dest='type', metavar='TYPE', 

1040 choices=('event', 'event-yaml', 'source', 'vector'), 

1041 help='select type of objects to be exported. Choices: ' 

1042 '"event" (default), "event-yaml", "source", "vector".') 

1043 

1044 parser.add_option( 

1045 '--parameters', dest='parameters', metavar='PLIST', 

1046 help='select parameters to be exported. PLIST is a ' 

1047 'comma-separated list where each entry has the form ' 

1048 '"<parameter>[.<measure>]". Available measures: "best", ' 

1049 '"mean", "std", "minimum", "percentile16", "median", ' 

1050 '"percentile84", "maximum".') 

1051 

1052 parser.add_option( 

1053 '--selection', dest='selection', metavar='EXPRESSION', 

1054 help='only export data for runs which match EXPRESSION. ' 

1055 'Example expression: "tags_contains:excellent,good"') 

1056 

1057 parser.add_option( 

1058 '--output', dest='filename', metavar='FILE', 

1059 help='write output to FILE') 

1060 

1061 parser.add_option( 

1062 '--effective-lat-lon', dest='effective_lat_lon', 

1063 action='store_true', 

1064 help='convert north_shift/east_shift offsets to true lat/lon ' 

1065 'coordinates (when outputting event objects).') 

1066 

1067 parser, options, args = cl_parse('export', args, setup) 

1068 if len(args) < 2: 

1069 help_and_die(parser, 'arguments required') 

1070 

1071 what = args[0] 

1072 

1073 dirnames = args[1:] 

1074 

1075 what_choices = ('best', 'mean', 'ensemble', 'stats') 

1076 

1077 if what not in what_choices: 

1078 help_and_die( 

1079 parser, 

1080 'invalid choice: %s (choose from %s)' % ( 

1081 repr(what), ', '.join(repr(x) for x in what_choices))) 

1082 

1083 if options.parameters: 

1084 pnames = options.parameters.split(',') 

1085 else: 

1086 pnames = None 

1087 

1088 try: 

1089 grond.export( 

1090 what, 

1091 dirnames, 

1092 filename=options.filename, 

1093 type=options.type, 

1094 pnames=pnames, 

1095 selection=options.selection, 

1096 effective_lat_lon=options.effective_lat_lon) 

1097 

1098 except grond.GrondError as e: 

1099 die(str(e)) 

1100 

1101 

1102def command_tag(args): 

1103 

1104 def setup(parser): 

1105 parser.add_option( 

1106 '-d', '--dir-names', 

1107 dest='show_dirnames', 

1108 action='store_true', 

1109 help='show directory names instead of run names') 

1110 

1111 parser, options, args = cl_parse('tag', args, setup) 

1112 if len(args) < 2: 

1113 help_and_die(parser, 'two or more arguments required') 

1114 

1115 action = args.pop(0) 

1116 

1117 if action not in ('add', 'remove', 'list'): 

1118 help_and_die(parser, 'invalid action: %s' % action) 

1119 

1120 if action in ('add', 'remove'): 

1121 if len(args) < 2: 

1122 help_and_die(parser, 'three or more arguments required') 

1123 

1124 tag = args.pop(0) 

1125 

1126 rundirs = args 

1127 

1128 if action == 'list': 

1129 rundirs = args 

1130 

1131 from grond.environment import Environment 

1132 

1133 errors = False 

1134 for rundir in rundirs: 

1135 try: 

1136 env = Environment([rundir]) 

1137 if options.show_dirnames: 

1138 name = rundir 

1139 else: 

1140 name = env.get_problem().name 

1141 

1142 info = env.get_run_info() 

1143 if action == 'add': 

1144 info.add_tag(tag) 

1145 env.set_run_info(info) 

1146 elif action == 'remove': 

1147 info.remove_tag(tag) 

1148 env.set_run_info(info) 

1149 elif action == 'list': 

1150 print('%-60s : %s' % ( 

1151 name, 

1152 ', '.join(info.tags))) 

1153 

1154 except grond.GrondError as e: 

1155 errors = True 

1156 logger.error(e) 

1157 

1158 if errors: 

1159 die('Errors occurred, see log messages above.') 

1160 

1161 

1162def make_report(env_args, event_name, conf, update_without_plotting): 

1163 from grond.environment import Environment 

1164 from grond.report import report 

1165 try: 

1166 env = Environment(env_args) 

1167 if event_name: 

1168 env.set_current_event_name(event_name) 

1169 

1170 report( 

1171 env, conf, 

1172 update_without_plotting=update_without_plotting, 

1173 make_index=False, 

1174 make_archive=False) 

1175 

1176 return True 

1177 

1178 except grond.GrondError as e: 

1179 logger.error(str(e)) 

1180 return False 

1181 

1182 

1183def command_report(args): 

1184 

1185 import matplotlib 

1186 matplotlib.use('Agg') 

1187 

1188 from pyrocko import parimap 

1189 

1190 from grond.environment import Environment 

1191 from grond.report import \ 

1192 report_index, report_archive, serve_ip, serve_report, read_config, \ 

1193 write_config, ReportConfig 

1194 

1195 def setup(parser): 

1196 parser.add_option( 

1197 '--index-only', 

1198 dest='index_only', 

1199 action='store_true', 

1200 help='create index only') 

1201 parser.add_option( 

1202 '--serve', '-s', 

1203 dest='serve', 

1204 action='store_true', 

1205 help='start http service') 

1206 parser.add_option( 

1207 '--serve-external', '-S', 

1208 dest='serve_external', 

1209 action='store_true', 

1210 help='shortcut for --serve --host=default --fixed-port') 

1211 parser.add_option( 

1212 '--host', 

1213 dest='host', 

1214 default='localhost', 

1215 help='<ip> to start the http server on. Special values for ' 

1216 '<ip>: "*" binds to all available interfaces, "default" ' 

1217 'to default external interface, "localhost" to "127.0.0.1".') 

1218 parser.add_option( 

1219 '--port', 

1220 dest='port', 

1221 type=int, 

1222 default=8383, 

1223 help='set default http server port. Will count up if port is ' 

1224 'already in use unless --fixed-port is given.') 

1225 parser.add_option( 

1226 '--fixed-port', 

1227 dest='fixed_port', 

1228 action='store_true', 

1229 help='fail if port is already in use') 

1230 parser.add_option( 

1231 '--open', '-o', 

1232 dest='open', 

1233 action='store_true', 

1234 help='open report in browser') 

1235 parser.add_option( 

1236 '--config', 

1237 dest='config', 

1238 metavar='FILE', 

1239 help='report configuration file to use') 

1240 parser.add_option( 

1241 '--write-config', 

1242 dest='write_config', 

1243 metavar='FILE', 

1244 help='write configuration (or default configuration) to FILE') 

1245 parser.add_option( 

1246 '--update-without-plotting', 

1247 dest='update_without_plotting', 

1248 action='store_true', 

1249 help='quick-and-dirty update parameter files without plotting') 

1250 parser.add_option( 

1251 '--archive', 

1252 dest='make_archive', 

1253 default=None, 

1254 action='store_true', 

1255 help='create archive file, even if `make_archive` in the report ' 

1256 'configuration file is set to `false`. The config file ' 

1257 'default creates no archive.') 

1258 parser.add_option( 

1259 '--no-archive', 

1260 dest='make_archive', 

1261 default=None, 

1262 action='store_false', 

1263 help='do not create archive file, even if `make_archive` in the ' 

1264 'report configuration file is set to `true`. The config file ' 

1265 'default creates no archive.') 

1266 

1267 parser, options, args = cl_parse('report', args, setup) 

1268 

1269 s_conf = '' 

1270 if options.config: 

1271 try: 

1272 conf = read_config(options.config) 

1273 except grond.GrondError as e: 

1274 die(str(e)) 

1275 

1276 s_conf = ' --config="%s"' % options.config 

1277 else: 

1278 from grond import plot 

1279 conf = ReportConfig( 

1280 plot_config_collection=plot.get_plot_config_collection()) 

1281 conf.set_basepath('.') 

1282 

1283 if options.write_config: 

1284 try: 

1285 write_config(conf, options.write_config) 

1286 sys.exit(0) 

1287 

1288 except grond.GrondError as e: 

1289 die(str(e)) 

1290 

1291 # commandline options that can override config values 

1292 if options.make_archive is not None: 

1293 conf.make_archive = options.make_archive 

1294 

1295 if len(args) == 1 and op.exists(op.join(args[0], 'index.html')): 

1296 conf.report_base_path = conf.rel_path(args[0]) 

1297 s_conf = ' %s' % args[0] 

1298 args = [] 

1299 

1300 report_base_path = conf.expand_path(conf.report_base_path) 

1301 

1302 if options.index_only: 

1303 report_index(conf) 

1304 report_archive(conf) 

1305 args = [] 

1306 

1307 entries_generated = False 

1308 

1309 payload = [] 

1310 if args and all(op.isdir(rundir) for rundir in args): 

1311 rundirs = args 

1312 all_failed = True 

1313 for rundir in rundirs: 

1314 payload.append(( 

1315 [rundir], None, conf, options.update_without_plotting)) 

1316 

1317 elif args: 

1318 try: 

1319 env = Environment(args) 

1320 for event_name in env.get_selected_event_names(): 

1321 payload.append(( 

1322 args, event_name, conf, options.update_without_plotting)) 

1323 

1324 except grond.GrondError as e: 

1325 die(str(e)) 

1326 

1327 if payload: 

1328 entries_generated = [] 

1329 for result in parimap.parimap( 

1330 make_report, *zip(*payload), nprocs=options.nparallel): 

1331 

1332 entries_generated.append(result) 

1333 

1334 all_failed = not any(entries_generated) 

1335 entries_generated = any(entries_generated) 

1336 

1337 if all_failed: 

1338 die('no report entries generated') 

1339 

1340 report_index(conf) 

1341 report_archive(conf) 

1342 

1343 if options.serve or options.serve_external: 

1344 if options.serve_external: 

1345 host = 'default' 

1346 else: 

1347 host = options.host 

1348 

1349 addr = serve_ip(host), options.port 

1350 

1351 serve_report( 

1352 addr, 

1353 report_config=conf, 

1354 fixed_port=options.fixed_port or options.serve_external, 

1355 open=options.open) 

1356 

1357 elif options.open: 

1358 import webbrowser 

1359 url = 'file://%s/index.html' % op.abspath(report_base_path) 

1360 webbrowser.open(url) 

1361 

1362 else: 

1363 if not entries_generated and not options.index_only: 

1364 logger.info('Nothing to do, see: grond report --help') 

1365 

1366 if entries_generated and not (options.serve or options.serve_external): 

1367 logger.info(CLIHints('report', config=s_conf)) 

1368 

1369 

1370def command_qc_polarization(args): 

1371 

1372 def setup(parser): 

1373 parser.add_option( 

1374 '--time-factor-pre', dest='time_factor_pre', type=float, 

1375 metavar='NUMBER', 

1376 default=0.5, 

1377 help='set duration to extract before synthetic P phase arrival, ' 

1378 'relative to 1/fmin. fmin is taken from the selected target ' 

1379 'group in the config file (default=%default)') 

1380 parser.add_option( 

1381 '--time-factor-post', dest='time_factor_post', type=float, 

1382 metavar='NUMBER', 

1383 default=0.5, 

1384 help='set duration to extract after synthetic P phase arrival, ' 

1385 'relative to 1/fmin. fmin is taken from the selected target ' 

1386 'group in the config file (default=%default)') 

1387 parser.add_option( 

1388 '--distance-min', dest='distance_min', type=float, 

1389 metavar='NUMBER', 

1390 help='minimum event-station distance [m]') 

1391 parser.add_option( 

1392 '--distance-max', dest='distance_max', type=float, 

1393 metavar='NUMBER', 

1394 help='maximum event-station distance [m]') 

1395 parser.add_option( 

1396 '--depth-min', dest='depth_min', type=float, 

1397 metavar='NUMBER', 

1398 help='minimum station depth [m]') 

1399 parser.add_option( 

1400 '--depth-max', dest='depth_max', type=float, 

1401 metavar='NUMBER', 

1402 help='maximum station depth [m]') 

1403 parser.add_option( 

1404 '--picks', dest='picks_filename', 

1405 metavar='FILENAME', 

1406 help='add file with P picks in Snuffler marker format') 

1407 parser.add_option( 

1408 '--save', dest='output_filename', 

1409 metavar='FILENAME.FORMAT', 

1410 help='save output to file FILENAME.FORMAT') 

1411 parser.add_option( 

1412 '--dpi', dest='output_dpi', type=float, default=120., 

1413 metavar='NUMBER', 

1414 help='DPI setting for raster formats (default=120)') 

1415 

1416 parser, options, args = cl_parse('qc-polarization', args, setup) 

1417 if len(args) != 3: 

1418 help_and_die(parser, 'missing arguments') 

1419 

1420 if options.output_filename: 

1421 import matplotlib 

1422 matplotlib.use('Agg') 

1423 

1424 import grond.qc 

1425 

1426 config_path, event_name, target_group_path = args 

1427 

1428 try: 

1429 config = grond.read_config(config_path) 

1430 except grond.GrondError as e: 

1431 die(str(e)) 

1432 

1433 ds = config.get_dataset(event_name) 

1434 

1435 engine = config.engine_config.get_engine() 

1436 

1437 nsl_to_time = None 

1438 if options.picks_filename: 

1439 markers = marker.load_markers(options.picks_filename) 

1440 marker.associate_phases_to_events(markers) 

1441 

1442 nsl_to_time = {} 

1443 for m in markers: 

1444 if isinstance(m, marker.PhaseMarker): 

1445 ev = m.get_event() 

1446 if ev is not None and ev.name == event_name: 

1447 nsl_to_time[m.one_nslc()[:3]] = m.tmin 

1448 

1449 if not nsl_to_time: 

1450 help_and_die( 

1451 parser, 

1452 'no markers associated with event "%s" found in file "%s"' % ( 

1453 event_name, options.picks_filename)) 

1454 

1455 target_group_paths_avail = [] 

1456 for target_group in config.target_groups: 

1457 name = target_group.path 

1458 if name == target_group_path: 

1459 imc = target_group.misfit_config 

1460 fmin = imc.fmin 

1461 fmax = imc.fmax 

1462 ffactor = imc.ffactor 

1463 

1464 store = engine.get_store(target_group.store_id) 

1465 timing = '{cake:P|cake:p|cake:P\\|cake:p\\}' 

1466 

1467 grond.qc.polarization( 

1468 ds, store, timing, fmin=fmin, fmax=fmax, ffactor=ffactor, 

1469 time_factor_pre=options.time_factor_pre, 

1470 time_factor_post=options.time_factor_post, 

1471 distance_min=options.distance_min, 

1472 distance_max=options.distance_max, 

1473 depth_min=options.depth_min, 

1474 depth_max=options.depth_max, 

1475 nsl_to_time=nsl_to_time, 

1476 output_filename=options.output_filename, 

1477 output_dpi=options.output_dpi) 

1478 

1479 return 

1480 

1481 target_group_paths_avail.append(name) 

1482 

1483 die('no target group with path "%s" found. Available: %s' % ( 

1484 target_group_path, ', '.join(target_group_paths_avail))) 

1485 

1486 

1487def command_upgrade_config(args): 

1488 def setup(parser): 

1489 parser.add_option( 

1490 '--diff', dest='diff', action='store_true', 

1491 help='create diff between normalized old and new versions') 

1492 

1493 parser, options, args = cl_parse('upgrade-config', args, setup) 

1494 if len(args) != 1: 

1495 help_and_die(parser, 'missing argument <configfile>') 

1496 

1497 from grond import upgrade 

1498 upgrade.upgrade_config_file(args[0], diff=options.diff) 

1499 

1500 

1501def command_diff(args): 

1502 def setup(parser): 

1503 pass 

1504 

1505 parser, options, args = cl_parse('diff', args, setup) 

1506 if len(args) != 2: 

1507 help_and_die(parser, 'requires exactly two arguments') 

1508 

1509 from grond.config import diff_configs 

1510 diff_configs(*args) 

1511 

1512 

1513def command_version(args): 

1514 def setup(parser): 

1515 parser.add_option( 

1516 '--short', dest='short', action='store_true', 

1517 help='only print Grond\'s version number') 

1518 parser.add_option( 

1519 '--failsafe', dest='failsafe', action='store_true', 

1520 help='do not get irritated when some dependencies are missing') 

1521 

1522 parser, options, args = cl_parse('version', args, setup) 

1523 

1524 if options.short: 

1525 print(grond.__version__) 

1526 return 

1527 

1528 elif not options.failsafe: 

1529 from grond import info 

1530 print(info.version_info()) 

1531 return 

1532 

1533 print("grond: %s" % grond.__version__) 

1534 

1535 try: 

1536 import pyrocko 

1537 print('pyrocko: %s' % pyrocko.long_version) 

1538 except ImportError: 

1539 print('pyrocko: N/A') 

1540 

1541 try: 

1542 import numpy 

1543 print('numpy: %s' % numpy.__version__) 

1544 except ImportError: 

1545 print('numpy: N/A') 

1546 

1547 try: 

1548 import scipy 

1549 print('scipy: %s' % scipy.__version__) 

1550 except ImportError: 

1551 print('scipy: N/A') 

1552 

1553 try: 

1554 import matplotlib 

1555 print('matplotlib: %s' % matplotlib.__version__) 

1556 except ImportError: 

1557 print('matplotlib: N/A') 

1558 

1559 try: 

1560 from pyrocko.gui.qt_compat import Qt 

1561 print('PyQt: %s' % Qt.PYQT_VERSION_STR) 

1562 print('Qt: %s' % Qt.QT_VERSION_STR) 

1563 except ImportError: 

1564 print('PyQt: N/A') 

1565 print('Qt: N/A') 

1566 

1567 import sys 

1568 print('python: %s.%s.%s' % sys.version_info[:3]) 

1569 

1570 if not options.failsafe: 

1571 die('fell back to failsafe version printing') 

1572 

1573 

1574if __name__ == '__main__': 

1575 main()