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 default='info', 

215 help='set logger level to ' 

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

217 'Default is "%default".') 

218 

219 parser.add_option( 

220 '--docs', 

221 dest='rst_docs', 

222 action='store_true') 

223 

224 

225def print_docs(command, parser): 

226 

227 from optparse import IndentedHelpFormatter 

228 

229 class DocsFormatter(IndentedHelpFormatter): 

230 

231 def format_heading(self, heading): 

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

233 

234 def format_usage(self, usage): 

235 lines = usage.splitlines() 

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

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

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

239 

240 def format_option(self, option): 

241 if not option.help: 

242 return '' 

243 

244 result = [] 

245 opts = self.option_strings[option] 

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

247 

248 help_text = self.expand_default(option) 

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

250 

251 return ''.join(result) 

252 

253 parser.formatter = DocsFormatter() 

254 parser.formatter.set_parser(parser) 

255 

256 def format_help(parser): 

257 formatter = parser.formatter 

258 result = [] 

259 

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

261 

262 if parser.usage: 

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

264 

265 result.append('\n') 

266 

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

268 

269 result.append('\n') 

270 

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

272 return "".join(result) 

273 

274 print(command) 

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

276 print() 

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

278 print() 

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

280 print() 

281 print(format_help(parser)) 

282 

283 

284def process_common_options(command, parser, options): 

285 util.setup_logging(program_name, options.loglevel) 

286 if options.rst_docs: 

287 print_docs(command, parser) 

288 exit(0) 

289 

290 

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

292 usage = subcommand_usages[command] 

293 descr = subcommand_descriptions[command] 

294 

295 if isinstance(usage, str): 

296 usage = [usage] 

297 

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

299 for s in usage[1:]: 

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

301 

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

303 

304 if details: 

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

306 

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

308 

309 if setup: 

310 setup(parser) 

311 

312 add_common_options(parser) 

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

314 process_common_options(command, parser, options) 

315 return parser, options, args 

316 

317 

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

319 if prelude: 

320 prelude = prelude + '\n' 

321 

322 if err: 

323 err = '\n' + err 

324 

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

326 

327 

328def help_and_die(parser, message): 

329 sio = StringIO() 

330 parser.print_help(sio) 

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

332 

333 

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

335 options = value.split(',') 

336 for opt in options: 

337 if opt not in choices: 

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

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

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

341 

342 

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

344 mag_range = value.split('-') 

345 if len(mag_range) != 2: 

346 raise OptionValueError( 

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

348 try: 

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

350 except ValueError: 

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

352 

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

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

355 ' maximum magnitude.') 

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

357 

358 

359def command_scenario(args): 

360 

361 STORE_STATIC = 'crust2_ib_static' 

362 STORE_WAVEFORMS = 'crust2_ib' 

363 

364 def setup(parser): 

365 parser.add_option( 

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

367 callback=multiple_choice, callback_kwargs={ 

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

369 }, 

370 default='waveforms', 

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

372 ' waveforms, gnss and insar. ' 

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

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

375 parser.add_option( 

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

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

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

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

380 ' (default: \'%default\')') 

381 parser.add_option( 

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

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

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

385 parser.add_option( 

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

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

388 parser.add_option( 

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

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

391 ' (default: %default)') 

392 parser.add_option( 

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

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

395 parser.add_option( 

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

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

398 parser.add_option( 

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

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

401 parser.add_option( 

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

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

404 parser.add_option( 

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

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

407 parser.add_option( 

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

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

410 '(default: %default)') 

411 parser.add_option( 

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

413 default=None, 

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

415 '(default: %default)') 

416 parser.add_option( 

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

418 default=STORE_WAVEFORMS, 

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

420 '(default: %default)') 

421 parser.add_option( 

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

423 default=STORE_STATIC, 

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

425 '(default: %default)') 

426 parser.add_option( 

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

428 help='overwrite existing project folder.') 

429 parser.add_option( 

430 '--gf-store-superdirs', 

431 dest='gf_store_superdirs', 

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

433 parser.add_option( 

434 '--no-map', 

435 dest='make_map', 

436 default=True, 

437 action='store_false', 

438 help='suppress generation of map') 

439 parser.add_option( 

440 '--rebuild', 

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

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

443 

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

445 

446 gf_store_superdirs = None 

447 if options.gf_store_superdirs: 

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

449 

450 if len(args) == 1: 

451 project_dir = args[0] 

452 else: 

453 parser.print_help() 

454 sys.exit(1) 

455 

456 from grond import scenario as grond_scenario 

457 

458 try: 

459 scenario = grond_scenario.GrondScenario( 

460 project_dir, 

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

462 radius=options.radius*km) 

463 

464 scenario.rebuild = options.rebuild 

465 if options.rebuild: 

466 options.force = True 

467 

468 if 'waveforms' in options.targets: 

469 if options.stationxml_paths: 

470 options.stationxml_paths = [ 

471 op.abspath(path) for path in 

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

473 

474 if options.stations_paths: 

475 options.stations_paths = [ 

476 op.abspath(path) for path in 

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

478 

479 obs = grond_scenario.WaveformObservation( 

480 nstations=options.nstations, 

481 store_id=options.store_waveforms, 

482 stations_paths=options.stations_paths, 

483 stationxml_paths=options.stationxml_paths) 

484 scenario.add_observation(obs) 

485 

486 if 'insar' in options.targets: 

487 obs = grond_scenario.InSARObservation( 

488 store_id=options.store_statics) 

489 scenario.add_observation(obs) 

490 

491 if 'gnss' in options.targets: 

492 obs = grond_scenario.GNSSCampaignObservation( 

493 nstations=options.gnss_nstations, 

494 store_id=options.store_statics) 

495 scenario.add_observation(obs) 

496 

497 if options.problem == 'cmt': 

498 problem = grond_scenario.DCSourceProblem( 

499 nevents=options.nevents, 

500 radius=options.source_radius*km, 

501 magnitude_min=options.magnitude_range[0], 

502 magnitude_max=options.magnitude_range[1]) 

503 elif options.problem == 'rectangular': 

504 problem = grond_scenario.RectangularSourceProblem( 

505 nevents=options.nevents) 

506 scenario.set_problem(problem) 

507 

508 scenario.build( 

509 force=options.force, 

510 interactive=True, 

511 gf_store_superdirs=gf_store_superdirs, 

512 make_map=options.make_map) 

513 

514 logger.info(CLIHints('scenario', 

515 config=scenario.get_grond_config_path(), 

516 project_dir=project_dir)) 

517 

518 except grond.GrondError as e: 

519 die(str(e)) 

520 

521 

522def command_init(args): 

523 

524 from .cmd_init import GrondInit 

525 

526 grond_init = GrondInit() 

527 

528 def print_section(entries): 

529 if len(entries) == 0: 

530 return '\tNone available.' 

531 

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

533 rstr = [] 

534 lcat = None 

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

536 

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

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

539 rstr.append('') 

540 lcat = cat 

541 

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

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

544 return '\n'.join(rstr) 

545 

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

547 

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

549 

550 Deploy a full project structure into a directory. 

551 

552 usage: grond init <example> <projectdir> 

553 

554 where <example> is any of the following: 

555 

556{examples_list} 

557 

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

559 

560 Print out configuration snippets for various components. 

561 

562 usage: grond init <section> 

563 

564 where <section> is any of the following: 

565 

566{sections_list} 

567'''.format(c=Color, 

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

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

570 

571 def setup(parser): 

572 parser.add_option( 

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

574 

575 parser, options, args = cl_parse( 

576 'init', args, setup, 

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

578 

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

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

581 

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

583 print(help_text) 

584 return 

585 

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

587 if len(args) == 1: 

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

589 if not config: 

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

591 

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

593 

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

595 '<projectdir>') 

596 

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

598 help_and_die( 

599 parser, 

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

601 % args[1]) 

602 else: 

603 try: 

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

605 except OSError as e: 

606 print(str(e)) 

607 

608 else: 

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

610 if not sec: 

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

612 

613 sys.stdout.write(sec) 

614 

615 

616def command_init_old(args): 

617 

618 from . import cmd_init as init 

619 

620 def setup(parser): 

621 parser.add_option( 

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

623 callback=multiple_choice, callback_kwargs={ 

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

625 }, 

626 default='waveforms', 

627 help='select from:' 

628 ' waveforms, gnss and insar. ' 

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

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

631 parser.add_option( 

632 '--problem', dest='problem', 

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

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

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

636 ' (default: \'%default\')') 

637 parser.add_option( 

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

639 help='overwrite existing project folder') 

640 

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

642 

643 try: 

644 project = init.GrondProject() 

645 

646 if 'all' in options.targets: 

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

648 else: 

649 targets = options.targets 

650 

651 if not options.problem: 

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

653 problem = 'rectangular' 

654 else: 

655 problem = 'cmt' 

656 else: 

657 problem = options.problem 

658 

659 if problem == 'rectangular': 

660 project.set_rectangular_source() 

661 elif problem == 'cmt': 

662 project.set_cmt_source() 

663 

664 if 'waveforms' in targets: 

665 project.add_waveforms() 

666 

667 if 'insar' in targets: 

668 project.add_insar() 

669 

670 if 'gnss' in targets: 

671 project.add_gnss() 

672 

673 if len(args) == 1: 

674 project_dir = args[0] 

675 project.build(project_dir, options.force) 

676 logger.info(CLIHints( 

677 'init', project_dir=project_dir, 

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

679 else: 

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

681 

682 except grond.GrondError as e: 

683 die(str(e)) 

684 

685 

686def command_events(args): 

687 def setup(parser): 

688 pass 

689 

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

691 if len(args) != 1: 

692 help_and_die(parser, 'missing arguments') 

693 

694 config_path = args[0] 

695 try: 

696 config = grond.read_config(config_path) 

697 

698 for event_name in grond.get_event_names(config): 

699 print(event_name) 

700 

701 except grond.GrondError as e: 

702 die(str(e)) 

703 

704 

705def command_check(args): 

706 

707 from grond.environment import Environment 

708 

709 def setup(parser): 

710 parser.add_option( 

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

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

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

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

715 

716 parser.add_option( 

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

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

719 

720 parser.add_option( 

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

722 default=10, 

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

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

725 'solution.') 

726 

727 parser.add_option( 

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

729 metavar='FILENAME', 

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

731 

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

733 if len(args) < 1: 

734 help_and_die(parser, 'missing arguments') 

735 

736 try: 

737 env = Environment(args) 

738 config = env.get_config() 

739 

740 target_string_ids = None 

741 if options.target_string_ids: 

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

743 

744 grond.check( 

745 config, 

746 event_names=env.get_selected_event_names(), 

747 target_string_ids=target_string_ids, 

748 show_waveforms=options.show_waveforms, 

749 n_random_synthetics=options.n_random_synthetics, 

750 stations_used_path=options.stations_used_path) 

751 

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

753 

754 except grond.GrondError as e: 

755 die(str(e)) 

756 

757 

758def command_go(args): 

759 

760 from grond.environment import Environment 

761 

762 def setup(parser): 

763 parser.add_option( 

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

765 help='overwrite existing run directory') 

766 parser.add_option( 

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

768 help='preserve old rundir') 

769 parser.add_option( 

770 '--status', dest='status', default='state', 

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

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

773 'state)') 

774 parser.add_option( 

775 '--parallel', dest='nparallel', type=int, default=1, 

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

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

778 parser.add_option( 

779 '--threads', dest='nthreads', type=int, default=1, 

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

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

782 

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

784 

785 try: 

786 env = Environment(args) 

787 

788 status = options.status 

789 if options.nparallel != 1: 

790 status = 'quiet' 

791 

792 grond.go( 

793 env, 

794 force=options.force, 

795 preserve=options.preserve, 

796 status=status, 

797 nparallel=options.nparallel, 

798 nthreads=options.nthreads) 

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

800 logger.info(CLIHints( 

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

802 

803 except grond.GrondError as e: 

804 die(str(e)) 

805 

806 

807def command_forward(args): 

808 

809 from grond.environment import Environment 

810 

811 def setup(parser): 

812 parser.add_option( 

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

814 default='filtered', 

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

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

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

818 

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

820 if len(args) < 1: 

821 help_and_die(parser, 'missing arguments') 

822 

823 try: 

824 env = Environment(args) 

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

826 except grond.GrondError as e: 

827 die(str(e)) 

828 

829 

830def command_harvest(args): 

831 def setup(parser): 

832 parser.add_option( 

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

834 help='overwrite existing harvest directory') 

835 parser.add_option( 

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

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

838 parser.add_option( 

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

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

841 '0: no weeding (default), ' 

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

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

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

845 'included in the harvest ensemble, ' 

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

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

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

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

850 'chains are excluded') 

851 parser.add_option( 

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

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

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

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

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

857 

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

859 if len(args) < 1: 

860 help_and_die(parser, 'no rundir') 

861 

862 export_fits = [] 

863 if options.export_fits.strip(): 

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

865 

866 for run_path in args: 

867 try: 

868 grond.harvest( 

869 run_path, 

870 force=options.force, 

871 nbest=options.neach, 

872 weed=options.weed, 

873 export_fits=export_fits) 

874 

875 except grond.DirectoryAlreadyExists as e: 

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

877 

878 except grond.GrondError as e: 

879 die(str(e)) 

880 

881 

882def command_cluster(args): 

883 from grond import Clustering 

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

885 

886 def setup(parser): 

887 parser.add_option( 

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

889 default='kagan_angle', 

890 choices=metrics.metrics, 

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

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

893 

894 parser.add_option( 

895 '--write-config', 

896 dest='write_config', 

897 metavar='FILE', 

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

899 

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

901 try: 

902 parser, options, args = cl_parse( 

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

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

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

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

907 

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

909 help_and_die( 

910 parser, 

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

912 'no clustering method specified') 

913 

914 if op.exists(method): 

915 clustering = read_config(method) 

916 else: 

917 clustering = Clustering.cli_instantiate(method, options) 

918 

919 if options.write_config: 

920 write_config(clustering, options.write_config) 

921 else: 

922 if len(args) != 1: 

923 help_and_die(parser, 'no rundir') 

924 run_path, = args 

925 

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

927 

928 except grond.GrondError as e: 

929 die(str(e)) 

930 

931 

932def command_plot(args): 

933 

934 def setup(parser): 

935 parser.add_option( 

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

937 help='show plot for interactive inspection') 

938 

939 details = '' 

940 

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

942 

943 if not options.show: 

944 import matplotlib 

945 matplotlib.use('Agg') 

946 

947 from grond.environment import Environment 

948 

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

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

951 

952 if len(args) > 1: 

953 env = Environment(args[1:]) 

954 else: 

955 env = None 

956 

957 from grond import plot 

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

959 

960 def get_doc_title(doc): 

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

962 ln = ln.strip() 

963 if ln != '': 

964 ln = ln.strip('.') 

965 return ln 

966 return 'Undocumented.' 

967 

968 if env: 

969 plot_classes = env.get_plot_classes() 

970 else: 

971 plot_classes = plot.get_all_plot_classes() 

972 

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

974 for pc in plot_classes]) 

975 

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

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

978 

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

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

981 ls=left_spaces, name=name, desc=desc)) 

982 

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

984 plot_config_collection = plot.get_plot_config_collection(env) 

985 print(plot_config_collection) 

986 

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

988 if env is None: 

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

990 plot_names = plot.get_plot_names(env) 

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

992 

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

994 if env is None: 

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

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

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

998 

999 else: 

1000 if env is None: 

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

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

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

1004 

1005 

1006def command_movie(args): 

1007 

1008 import matplotlib 

1009 matplotlib.use('Agg') 

1010 

1011 def setup(parser): 

1012 pass 

1013 

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

1015 

1016 if len(args) != 4: 

1017 help_and_die(parser, 'four arguments required') 

1018 

1019 run_path, xpar_name, ypar_name, movie_filename_template = args 

1020 

1021 from grond import plot 

1022 

1023 movie_filename = movie_filename_template % { 

1024 'xpar': xpar_name, 

1025 'ypar': ypar_name} 

1026 

1027 try: 

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

1029 

1030 except grond.GrondError as e: 

1031 die(str(e)) 

1032 

1033 

1034def command_export(args): 

1035 

1036 def setup(parser): 

1037 parser.add_option( 

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

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

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

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

1042 

1043 parser.add_option( 

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

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

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

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

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

1049 '"percentile84", "maximum".') 

1050 

1051 parser.add_option( 

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

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

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

1055 

1056 parser.add_option( 

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

1058 help='write output to FILE') 

1059 

1060 parser.add_option( 

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

1062 action='store_true', 

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

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

1065 

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

1067 if len(args) < 2: 

1068 help_and_die(parser, 'arguments required') 

1069 

1070 what = args[0] 

1071 

1072 dirnames = args[1:] 

1073 

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

1075 

1076 if what not in what_choices: 

1077 help_and_die( 

1078 parser, 

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

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

1081 

1082 if options.parameters: 

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

1084 else: 

1085 pnames = None 

1086 

1087 try: 

1088 grond.export( 

1089 what, 

1090 dirnames, 

1091 filename=options.filename, 

1092 type=options.type, 

1093 pnames=pnames, 

1094 selection=options.selection, 

1095 effective_lat_lon=options.effective_lat_lon) 

1096 

1097 except grond.GrondError as e: 

1098 die(str(e)) 

1099 

1100 

1101def command_tag(args): 

1102 

1103 def setup(parser): 

1104 parser.add_option( 

1105 '-d', '--dir-names', 

1106 dest='show_dirnames', 

1107 action='store_true', 

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

1109 

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

1111 if len(args) < 2: 

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

1113 

1114 action = args.pop(0) 

1115 

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

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

1118 

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

1120 if len(args) < 2: 

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

1122 

1123 tag = args.pop(0) 

1124 

1125 rundirs = args 

1126 

1127 if action == 'list': 

1128 rundirs = args 

1129 

1130 from grond.environment import Environment 

1131 

1132 errors = False 

1133 for rundir in rundirs: 

1134 try: 

1135 env = Environment([rundir]) 

1136 if options.show_dirnames: 

1137 name = rundir 

1138 else: 

1139 name = env.get_problem().name 

1140 

1141 info = env.get_run_info() 

1142 if action == 'add': 

1143 info.add_tag(tag) 

1144 env.set_run_info(info) 

1145 elif action == 'remove': 

1146 info.remove_tag(tag) 

1147 env.set_run_info(info) 

1148 elif action == 'list': 

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

1150 name, 

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

1152 

1153 except grond.GrondError as e: 

1154 errors = True 

1155 logger.error(e) 

1156 

1157 if errors: 

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

1159 

1160 

1161def make_report(env_args, event_name, conf, update_without_plotting, nthreads): 

1162 from grond.environment import Environment 

1163 from grond.report import report 

1164 try: 

1165 env = Environment(env_args) 

1166 if event_name: 

1167 env.set_current_event_name(event_name) 

1168 

1169 report( 

1170 env, conf, 

1171 update_without_plotting=update_without_plotting, 

1172 make_index=False, 

1173 make_archive=False, 

1174 nthreads=nthreads) 

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 '--parallel', dest='nparallel', type=int, default=1, 

1252 help='set number of runs to process in parallel, ' 

1253 'If set to more than one, --status=quiet is implied.') 

1254 parser.add_option( 

1255 '--threads', dest='nthreads', type=int, default=1, 

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

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

1258 parser.add_option( 

1259 '--no-archive', 

1260 dest='no_archive', 

1261 action='store_true', 

1262 help='don\'t create archive file.') 

1263 

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

1265 

1266 s_conf = '' 

1267 if options.config: 

1268 try: 

1269 conf = read_config(options.config) 

1270 except grond.GrondError as e: 

1271 die(str(e)) 

1272 

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

1274 else: 

1275 from grond import plot 

1276 conf = ReportConfig( 

1277 plot_config_collection=plot.get_plot_config_collection()) 

1278 conf.set_basepath('.') 

1279 

1280 if options.write_config: 

1281 try: 

1282 write_config(conf, options.write_config) 

1283 sys.exit(0) 

1284 

1285 except grond.GrondError as e: 

1286 die(str(e)) 

1287 

1288 # commandline options that can override config values 

1289 if options.no_archive: 

1290 conf.make_archive = False 

1291 

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

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

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

1295 args = [] 

1296 

1297 report_base_path = conf.expand_path(conf.report_base_path) 

1298 

1299 if options.index_only: 

1300 report_index(conf) 

1301 report_archive(conf) 

1302 args = [] 

1303 

1304 entries_generated = False 

1305 

1306 payload = [] 

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

1308 rundirs = args 

1309 all_failed = True 

1310 for rundir in rundirs: 

1311 payload.append(( 

1312 [rundir], None, conf, options.update_without_plotting, 

1313 options.nthreads)) 

1314 

1315 elif args: 

1316 try: 

1317 env = Environment(args) 

1318 for event_name in env.get_selected_event_names(): 

1319 payload.append(( 

1320 args, event_name, conf, options.update_without_plotting, 

1321 options.nthreads)) 

1322 

1323 except grond.GrondError as e: 

1324 die(str(e)) 

1325 

1326 if payload: 

1327 entries_generated = [] 

1328 for result in parimap.parimap( 

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

1330 

1331 entries_generated.append(result) 

1332 

1333 all_failed = not any(entries_generated) 

1334 entries_generated = any(entries_generated) 

1335 

1336 if all_failed: 

1337 die('no report entries generated') 

1338 

1339 report_index(conf) 

1340 report_archive(conf) 

1341 

1342 if options.serve or options.serve_external: 

1343 if options.serve_external: 

1344 host = 'default' 

1345 else: 

1346 host = options.host 

1347 

1348 addr = serve_ip(host), options.port 

1349 

1350 serve_report( 

1351 addr, 

1352 report_config=conf, 

1353 fixed_port=options.fixed_port or options.serve_external, 

1354 open=options.open) 

1355 

1356 elif options.open: 

1357 import webbrowser 

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

1359 webbrowser.open(url) 

1360 

1361 else: 

1362 if not entries_generated and not options.index_only: 

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

1364 

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

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

1367 

1368 

1369def command_qc_polarization(args): 

1370 

1371 def setup(parser): 

1372 parser.add_option( 

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

1374 metavar='NUMBER', 

1375 default=0.5, 

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

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

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

1379 parser.add_option( 

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

1381 metavar='NUMBER', 

1382 default=0.5, 

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

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

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

1386 parser.add_option( 

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

1388 metavar='NUMBER', 

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

1390 parser.add_option( 

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

1392 metavar='NUMBER', 

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

1394 parser.add_option( 

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

1396 metavar='NUMBER', 

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

1398 parser.add_option( 

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

1400 metavar='NUMBER', 

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

1402 parser.add_option( 

1403 '--picks', dest='picks_filename', 

1404 metavar='FILENAME', 

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

1406 parser.add_option( 

1407 '--save', dest='output_filename', 

1408 metavar='FILENAME.FORMAT', 

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

1410 parser.add_option( 

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

1412 metavar='NUMBER', 

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

1414 

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

1416 if len(args) != 3: 

1417 help_and_die(parser, 'missing arguments') 

1418 

1419 if options.output_filename: 

1420 import matplotlib 

1421 matplotlib.use('Agg') 

1422 

1423 import grond.qc 

1424 

1425 config_path, event_name, target_group_path = args 

1426 

1427 try: 

1428 config = grond.read_config(config_path) 

1429 except grond.GrondError as e: 

1430 die(str(e)) 

1431 

1432 ds = config.get_dataset(event_name) 

1433 

1434 engine = config.engine_config.get_engine() 

1435 

1436 nsl_to_time = None 

1437 if options.picks_filename: 

1438 markers = marker.load_markers(options.picks_filename) 

1439 marker.associate_phases_to_events(markers) 

1440 

1441 nsl_to_time = {} 

1442 for m in markers: 

1443 if isinstance(m, marker.PhaseMarker): 

1444 ev = m.get_event() 

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

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

1447 

1448 if not nsl_to_time: 

1449 help_and_die( 

1450 parser, 

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

1452 event_name, options.picks_filename)) 

1453 

1454 target_group_paths_avail = [] 

1455 for target_group in config.target_groups: 

1456 name = target_group.path 

1457 if name == target_group_path: 

1458 imc = target_group.misfit_config 

1459 fmin = imc.fmin 

1460 fmax = imc.fmax 

1461 ffactor = imc.ffactor 

1462 

1463 store = engine.get_store(target_group.store_id) 

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

1465 

1466 grond.qc.polarization( 

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

1468 time_factor_pre=options.time_factor_pre, 

1469 time_factor_post=options.time_factor_post, 

1470 distance_min=options.distance_min, 

1471 distance_max=options.distance_max, 

1472 depth_min=options.depth_min, 

1473 depth_max=options.depth_max, 

1474 nsl_to_time=nsl_to_time, 

1475 output_filename=options.output_filename, 

1476 output_dpi=options.output_dpi) 

1477 

1478 return 

1479 

1480 target_group_paths_avail.append(name) 

1481 

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

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

1484 

1485 

1486def command_upgrade_config(args): 

1487 def setup(parser): 

1488 parser.add_option( 

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

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

1491 

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

1493 if len(args) != 1: 

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

1495 

1496 from grond import upgrade 

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

1498 

1499 

1500def command_diff(args): 

1501 def setup(parser): 

1502 pass 

1503 

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

1505 if len(args) != 2: 

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

1507 

1508 from grond.config import diff_configs 

1509 diff_configs(*args) 

1510 

1511 

1512def command_version(args): 

1513 def setup(parser): 

1514 parser.add_option( 

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

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

1517 parser.add_option( 

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

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

1520 

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

1522 

1523 if options.short: 

1524 print(grond.__version__) 

1525 return 

1526 

1527 elif not options.failsafe: 

1528 from grond import info 

1529 print(info.version_info()) 

1530 return 

1531 

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

1533 

1534 try: 

1535 import pyrocko 

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

1537 except ImportError: 

1538 print('pyrocko: N/A') 

1539 

1540 try: 

1541 import numpy 

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

1543 except ImportError: 

1544 print('numpy: N/A') 

1545 

1546 try: 

1547 import scipy 

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

1549 except ImportError: 

1550 print('scipy: N/A') 

1551 

1552 try: 

1553 import matplotlib 

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

1555 except ImportError: 

1556 print('matplotlib: N/A') 

1557 

1558 try: 

1559 from pyrocko.gui.qt_compat import Qt 

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

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

1562 except ImportError: 

1563 print('PyQt: N/A') 

1564 print('Qt: N/A') 

1565 

1566 import sys 

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

1568 

1569 if not options.failsafe: 

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

1571 

1572 

1573if __name__ == '__main__': 

1574 main()