Coverage for /usr/local/lib/python3.11/dist-packages/grond/problems/plot.py: 13%

549 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-11-27 15:15 +0000

1# https://pyrocko.org/grond - GPLv3 

2# 

3# The Grond Developers, 21st Century 

4import math 

5import logging 

6import random 

7 

8import numpy as num 

9from matplotlib import cm, patches, colors as mcolors 

10 

11from pyrocko.guts import Tuple, Float, Int, String, List, Bool, StringChoice 

12 

13from pyrocko.plot import mpl_margins, mpl_color, mpl_init, mpl_graph_color 

14from pyrocko.plot import beachball, hudson 

15 

16from grond.plot.config import PlotConfig 

17from grond.plot.section import SectionPlotConfig, SectionPlot 

18from grond.plot.collection import PlotItem 

19from grond.targets.phase_pick.target import PhasePickTarget 

20from grond import meta, core, stats 

21from matplotlib import pyplot as plt 

22 

23logger = logging.getLogger('grond.problem.plot') 

24 

25guts_prefix = 'grond' 

26 

27 

28def cluster_label(icluster, perc): 

29 if icluster == -1: 

30 return 'Unclust. (%.0f%%)' % perc 

31 else: 

32 return 'Cluster %i (%.0f%%)' % (icluster, perc) 

33 

34 

35def cluster_color(icluster): 

36 if icluster == -1: 

37 return mpl_color('aluminium3') 

38 else: 

39 return mpl_graph_color(icluster) 

40 

41 

42def fixlim(lo, hi): 

43 if lo == hi: 

44 return lo - 1.0, hi + 1.0 

45 else: 

46 return lo, hi 

47 

48 

49def eigh_sorted(mat): 

50 evals, evecs = num.linalg.eigh(mat) 

51 iorder = num.argsort(evals) 

52 return evals[iorder], evecs[:, iorder] 

53 

54 

55class JointparPlot(PlotConfig): 

56 ''' 

57 Source problem parameter's tradeoff plots. 

58 ''' 

59 

60 name = 'jointpar' 

61 size_cm = Tuple.T(2, Float.T(), default=(20., 20.)) 

62 misfit_cutoff = Float.T(optional=True) 

63 ibootstrap = Int.T(optional=True) 

64 color_parameter = String.T(default='misfit') 

65 exclude = List.T(String.T()) 

66 include = List.T(String.T()) 

67 show_ellipses = Bool.T(default=False) 

68 nsubplots = Int.T(default=6) 

69 show_ticks = Bool.T(default=False) 

70 show_reference = Bool.T(default=True) 

71 show_axes = Bool.T(default=False) 

72 

73 def make(self, environ): 

74 cm = environ.get_plot_collection_manager() 

75 history = environ.get_history(subset='harvest') 

76 optimiser = environ.get_optimiser() 

77 sref = 'Dark gray boxes mark reference solution.' \ 

78 if self.show_reference else '' 

79 

80 mpl_init(fontsize=self.font_size) 

81 cm.create_group_mpl( 

82 self, 

83 self.draw_figures(history, optimiser), 

84 title=u'Jointpar Plot', 

85 section='solution', 

86 feather_icon='crosshair', 

87 description=u''' 

88Source problem parameter's scatter plots, to evaluate the resolution of source 

89parameters and possible trade-offs between pairs of model parameters. 

90 

91A subset of model solutions (from harvest) is shown in two dimensions for all 

92possible parameter pairs as points. The point color indicates the misfit for 

93the model solution with cold colors (blue) for high misfit models and warm 

94colors (red) for low misfit models. The plot ranges are defined by the given 

95parameter bounds and shows the model space of the optimsation. %s''' % sref) 

96 

97 def draw_figures(self, history, optimiser): 

98 

99 color_parameter = self.color_parameter 

100 exclude = self.exclude 

101 include = self.include 

102 nsubplots = self.nsubplots 

103 figsize = self.size_inch 

104 ibootstrap = 0 if self.ibootstrap is None else self.ibootstrap 

105 misfit_cutoff = self.misfit_cutoff 

106 show_ellipses = self.show_ellipses 

107 msize = 1.5 

108 cmap = 'coolwarm' 

109 

110 problem = history.problem 

111 if not problem: 

112 return [] 

113 

114 models = history.models 

115 

116 exclude = list(exclude) 

117 bounds = problem.get_combined_bounds() 

118 for ipar in range(problem.ncombined): 

119 par = problem.combined[ipar] 

120 lo, hi = bounds[ipar] 

121 if lo == hi: 

122 exclude.append(par.name) 

123 

124 xref = problem.get_reference_model() 

125 

126 isort = history.get_sorted_misfits_idx(chain=ibootstrap)[::-1] 

127 models = history.get_sorted_models(chain=ibootstrap)[::-1] 

128 nmodels = history.nmodels 

129 

130 gms = history.get_sorted_misfits(chain=ibootstrap)[::-1] 

131 if misfit_cutoff is not None: 

132 ibest = gms < misfit_cutoff 

133 gms = gms[ibest] 

134 models = models[ibest] 

135 

136 kwargs = {} 

137 

138 if color_parameter == 'dist': 

139 mx = num.mean(models, axis=0) 

140 cov = num.cov(models.T) 

141 mdists = core.mahalanobis_distance(models, mx, cov) 

142 icolor = meta.ordersort(mdists) 

143 

144 elif color_parameter == 'misfit': 

145 iorder = num.arange(nmodels) 

146 icolor = iorder 

147 

148 elif color_parameter in problem.parameter_names: 

149 ind = problem.name_to_index(color_parameter) 

150 icolor = problem.extract(models, ind) 

151 

152 elif color_parameter in history.attribute_names: 

153 icolor = history.get_attribute(color_parameter)[isort] 

154 icolor_need = num.unique(icolor) 

155 

156 colors = [] 

157 for i in range(icolor_need[-1]+1): 

158 colors.append(mpl_graph_color(i)) 

159 

160 cmap = mcolors.ListedColormap(colors) 

161 cmap.set_under(mpl_color('aluminium3')) 

162 kwargs.update(dict(vmin=0, vmax=icolor_need[-1])) 

163 else: 

164 raise meta.GrondError( 

165 'Invalid color_parameter: %s' % color_parameter) 

166 

167 smap = {} 

168 iselected = 0 

169 for ipar in range(problem.ncombined): 

170 par = problem.combined[ipar] 

171 if exclude and par.name in exclude or \ 

172 include and par.name not in include: 

173 continue 

174 

175 smap[iselected] = ipar 

176 iselected += 1 

177 

178 nselected = iselected 

179 

180 if nselected < 2: 

181 logger.warning( 

182 'Cannot draw joinpar figures with less than two parameters ' 

183 'selected.') 

184 return [] 

185 

186 nfig = (nselected - 2) // nsubplots + 1 

187 

188 figs = [] 

189 for ifig in range(nfig): 

190 figs_row = [] 

191 for jfig in range(nfig): 

192 if ifig >= jfig: 

193 item = PlotItem(name='fig_%i_%i' % (ifig, jfig)) 

194 item.attributes['parameters'] = [] 

195 figs_row.append((item, plt.figure(figsize=figsize))) 

196 else: 

197 figs_row.append(None) 

198 

199 figs.append(figs_row) 

200 

201 for iselected in range(nselected): 

202 ipar = smap[iselected] 

203 ypar = problem.combined[ipar] 

204 for jselected in range(iselected): 

205 jpar = smap[jselected] 

206 xpar = problem.combined[jpar] 

207 

208 ixg = (iselected - 1) 

209 iyg = jselected 

210 

211 ix = ixg % nsubplots 

212 iy = iyg % nsubplots 

213 

214 ifig = ixg // nsubplots 

215 jfig = iyg // nsubplots 

216 

217 aind = (nsubplots, nsubplots, (ix * nsubplots) + iy + 1) 

218 

219 item, fig = figs[ifig][jfig] 

220 

221 tlist = item.attributes['parameters'] 

222 if xpar.name not in tlist: 

223 tlist.append(xpar.name) 

224 if ypar.name not in tlist: 

225 tlist.append(ypar.name) 

226 

227 axes = fig.add_subplot(*aind) 

228 

229 axes.axvline(0., color=mpl_color('aluminium3'), lw=0.5) 

230 axes.axhline(0., color=mpl_color('aluminium3'), lw=0.5) 

231 for spine in axes.spines.values(): 

232 spine.set_edgecolor(mpl_color('aluminium5')) 

233 spine.set_linewidth(0.5) 

234 

235 xmin, xmax = fixlim(*xpar.scaled(bounds[jpar])) 

236 ymin, ymax = fixlim(*ypar.scaled(bounds[ipar])) 

237 

238 if not self.show_axes: 

239 if ix == 0 or jselected + 1 == iselected: 

240 for (xpos, xoff, x) in [ 

241 (0.0, 10., xmin), 

242 (1.0, -10., xmax)]: 

243 

244 axes.annotate( 

245 '%.3g%s' % (x, xpar.get_unit_suffix()), 

246 xy=(xpos, 1.05), 

247 xycoords='axes fraction', 

248 xytext=(xoff, 5.), 

249 textcoords='offset points', 

250 verticalalignment='bottom', 

251 horizontalalignment='left', 

252 rotation=45.) 

253 

254 if iy == nsubplots - 1 or jselected + 1 == iselected: 

255 for (ypos, yoff, y) in [ 

256 (0., 10., ymin), 

257 (1.0, -10., ymax)]: 

258 

259 axes.annotate( 

260 '%.3g%s' % (y, ypar.get_unit_suffix()), 

261 xy=(1.0, ypos), 

262 xycoords='axes fraction', 

263 xytext=(5., yoff), 

264 textcoords='offset points', 

265 verticalalignment='bottom', 

266 horizontalalignment='left', 

267 rotation=45.) 

268 

269 axes.set_xlim(xmin, xmax) 

270 axes.set_ylim(ymin, ymax) 

271 

272 if not self.show_axes: 

273 if not self.show_ticks: 

274 axes.get_xaxis().set_ticks([]) 

275 axes.get_yaxis().set_ticks([]) 

276 else: 

277 axes.tick_params(length=4, which='both') 

278 axes.get_yaxis().set_ticklabels([]) 

279 axes.get_xaxis().set_ticklabels([]) 

280 

281 if iselected == nselected - 1 or ix == nsubplots - 1: 

282 axes.annotate( 

283 xpar.get_label(with_unit=False), 

284 xy=(0.5, -0.05), 

285 xycoords='axes fraction', 

286 verticalalignment='top', 

287 horizontalalignment='right', 

288 rotation=45.) 

289 

290 if iy == 0: 

291 axes.annotate( 

292 ypar.get_label(with_unit=False), 

293 xy=(-0.05, 0.5), 

294 xycoords='axes fraction', 

295 verticalalignment='top', 

296 horizontalalignment='right', 

297 rotation=45.) 

298 else: 

299 axes.set_xlabel(xpar.get_label()) 

300 axes.set_ylabel(ypar.get_label()) 

301 

302 fx = problem.extract(models, jpar) 

303 fy = problem.extract(models, ipar) 

304 

305 axes.scatter( 

306 xpar.scaled(fx), 

307 ypar.scaled(fy), 

308 c=icolor, 

309 s=msize, alpha=0.5, cmap=cmap, edgecolors='none', **kwargs) 

310 

311 if show_ellipses: 

312 cov = num.cov((xpar.scaled(fx), ypar.scaled(fy))) 

313 evals, evecs = eigh_sorted(cov) 

314 evals = num.sqrt(evals) 

315 ell = patches.Ellipse( 

316 xy=( 

317 num.mean(xpar.scaled(fx)), 

318 num.mean(ypar.scaled(fy))), 

319 width=evals[0] * 2, 

320 height=evals[1] * 2, 

321 angle=num.rad2deg( 

322 num.arctan2(evecs[1][0], evecs[0][0]))) 

323 

324 ell.set_facecolor('none') 

325 ell.set_edgecolor(mpl_color('aluminium5')) 

326 axes.add_artist(ell) 

327 

328 if self.show_reference: 

329 fx = problem.extract(xref, jpar) 

330 fy = problem.extract(xref, ipar) 

331 

332 ref_color = mpl_color('aluminium6') 

333 ref_color_light = 'none' 

334 axes.plot( 

335 xpar.scaled(fx), ypar.scaled(fy), 's', 

336 mew=1.5, ms=5, mfc=ref_color_light, mec=ref_color) 

337 

338 figs_flat = [] 

339 for figs_row in figs: 

340 figs_flat.extend( 

341 item_fig for item_fig in figs_row if item_fig is not None) 

342 

343 return figs_flat 

344 

345 

346class HistogramPlot(PlotConfig): 

347 ''' 

348 Histograms or Gaussian kernel densities (default) of all parameters 

349 (marginal distributions of model parameters). 

350 

351 The histograms (by default shown as Gaussian kernel densities) show (red 

352 curved solid line) the distributions of the parameters (marginals) along 

353 with some characteristics: The red solid vertical line gives the median of 

354 the distribution and the dashed red vertical line the mean value. Dark gray 

355 vertical lines show reference values (given in the event.txt file). The 

356 overlapping red-shaded areas show the 68% confidence intervals (innermost 

357 area), the 90% confidence intervals (middle area) and the minimum and 

358 maximum values (widest area). The plot ranges are defined by the given 

359 parameter bounds and show the model space. Well resolved model parameters 

360 show peaked distributions. 

361 ''' 

362 

363 name = 'histogram' 

364 size_cm = Tuple.T(2, Float.T(), default=(12.5, 7.5)) 

365 exclude = List.T(String.T()) 

366 include = List.T(String.T()) 

367 method = StringChoice.T( 

368 choices=['gaussian_kde', 'histogram'], 

369 default='gaussian_kde') 

370 show_reference = Bool.T(default=True) 

371 

372 def make(self, environ): 

373 cm = environ.get_plot_collection_manager() 

374 history = environ.get_history(subset='harvest') 

375 

376 mpl_init(fontsize=self.font_size) 

377 cm.create_group_mpl( 

378 self, 

379 self.draw_figures(history), 

380 title=u'Histogram', 

381 section='solution', 

382 feather_icon='bar-chart-2', 

383 description=u''' 

384Distribution of the problem's parameters. 

385 

386The histograms are shown either as Gaussian kernel densities (red curved solid 

387line) or as bar plots the distributions of the parameters (marginals) along 

388with some characteristics: 

389 

390The red solid vertical line gives the median of the distribution and the dashed 

391red vertical line the mean value. Dark gray vertical lines show reference 

392parameter values if given in the event.txt file. The overlapping red-shaded 

393areas show the 68% confidence intervals (innermost area), the 90% confidence 

394intervals (middle area) and the minimum and maximum values (widest area). The 

395plot ranges are defined by the given parameter bounds and show the model 

396space. 

397''') 

398 

399 def draw_figures(self, history): 

400 

401 import scipy.stats 

402 from grond.core import make_stats 

403 

404 exclude = self.exclude 

405 include = self.include 

406 figsize = self.size_inch 

407 fontsize = self.font_size 

408 method = self.method 

409 ref_color = mpl_color('aluminium6') 

410 stats_color = mpl_color('scarletred2') 

411 bar_color = mpl_color('scarletred1') 

412 stats_color3 = mpl_color('scarletred3') 

413 

414 problem = history.problem 

415 

416 models = history.models 

417 

418 bounds = problem.get_combined_bounds() 

419 exclude = list(exclude) 

420 for ipar in range(problem.ncombined): 

421 par = problem.combined[ipar] 

422 vmin, vmax = bounds[ipar] 

423 if vmin == vmax: 

424 exclude.append(par.name) 

425 

426 xref = problem.get_reference_model() 

427 

428 smap = {} 

429 iselected = 0 

430 for ipar in range(problem.ncombined): 

431 par = problem.combined[ipar] 

432 if exclude and par.name in exclude or \ 

433 include and par.name not in include: 

434 continue 

435 

436 smap[iselected] = ipar 

437 iselected += 1 

438 

439 nselected = iselected 

440 del iselected 

441 

442 pnames = [ 

443 problem.combined[smap[iselected]].name 

444 for iselected in range(nselected)] 

445 

446 rstats = make_stats(problem, models, 

447 history.get_primary_chain_misfits(), 

448 pnames=pnames) 

449 

450 for iselected in range(nselected): 

451 ipar = smap[iselected] 

452 par = problem.combined[ipar] 

453 vs = problem.extract(models, ipar) 

454 vmin, vmax = bounds[ipar] 

455 

456 fig = plt.figure(figsize=figsize) 

457 labelpos = mpl_margins( 

458 fig, nw=1, nh=1, w=7., bottom=5., top=1, units=fontsize) 

459 

460 axes = fig.add_subplot(1, 1, 1) 

461 labelpos(axes, 2.5, 2.0) 

462 axes.set_xlabel(par.get_label()) 

463 axes.set_ylabel('PDF') 

464 axes.set_xlim(*fixlim(*par.scaled((vmin, vmax)))) 

465 

466 if method == 'gaussian_kde': 

467 try: 

468 kde = scipy.stats.gaussian_kde(vs) 

469 except Exception: 

470 logger.warning( 

471 'Cannot create plot histogram with gaussian_kde: ' 

472 'possibly all samples have the same value.') 

473 continue 

474 

475 vps = num.linspace(vmin, vmax, 600) 

476 pps = kde(vps) 

477 

478 axes.plot( 

479 par.scaled(vps), par.inv_scaled(pps), color=stats_color) 

480 

481 elif method == 'histogram': 

482 pps, edges = num.histogram( 

483 vs, 

484 bins=num.linspace(vmin, vmax, num=40), 

485 density=True) 

486 vps = 0.5 * (edges[:-1] + edges[1:]) 

487 

488 axes.bar(par.scaled(vps), par.inv_scaled(pps), 

489 par.scaled(2.*(vps - edges[:-1])), 

490 color=bar_color) 

491 

492 pstats = rstats.parameter_stats_list[iselected] 

493 

494 axes.axvspan( 

495 par.scaled(pstats.minimum), 

496 par.scaled(pstats.maximum), 

497 color=stats_color, alpha=0.1) 

498 axes.axvspan( 

499 par.scaled(pstats.percentile16), 

500 par.scaled(pstats.percentile84), 

501 color=stats_color, alpha=0.1) 

502 axes.axvspan( 

503 par.scaled(pstats.percentile5), 

504 par.scaled(pstats.percentile95), 

505 color=stats_color, alpha=0.1) 

506 

507 axes.axvline( 

508 par.scaled(pstats.median), 

509 color=stats_color3, alpha=0.5) 

510 axes.axvline( 

511 par.scaled(pstats.mean), 

512 color=stats_color3, ls=':', alpha=0.5) 

513 

514 if self.show_reference: 

515 axes.axvline( 

516 par.scaled(problem.extract(xref, ipar)), 

517 color=ref_color) 

518 

519 item = PlotItem(name=par.name) 

520 item.attributes['parameters'] = [par.name] 

521 yield item, fig 

522 

523 

524class MTDecompositionPlot(PlotConfig): 

525 ''' 

526 Moment tensor decomposition plot. 

527 ''' 

528 

529 name = 'mt_decomposition' 

530 size_cm = Tuple.T(2, Float.T(), default=(15., 5.)) 

531 cluster_attribute = meta.StringID.T( 

532 optional=True, 

533 help='name of attribute to use as cluster IDs') 

534 show_reference = Bool.T(default=True) 

535 

536 def make(self, environ): 

537 cm = environ.get_plot_collection_manager() 

538 history = environ.get_history(subset='harvest') 

539 mpl_init(fontsize=self.font_size) 

540 cm.create_group_mpl( 

541 self, 

542 self.draw_figures(history), 

543 title=u'MT Decomposition', 

544 section='solution', 

545 feather_icon='sun', 

546 description=u''' 

547Moment tensor decomposition of the best-fitting solution into isotropic, 

548deviatoric and best double couple components. 

549 

550Shown are the ensemble best, the ensemble mean%s and, if available, a reference 

551mechanism. The symbol size indicates the relative strength of the components. 

552The inversion result is consistent and stable if ensemble mean and ensemble 

553best have similar symbol size and patterns. 

554''' % (', cluster results' if self.cluster_attribute else '')) 

555 

556 def draw_figures(self, history): 

557 

558 fontsize = self.font_size 

559 

560 fig = plt.figure(figsize=self.size_inch) 

561 axes = fig.add_subplot(1, 1, 1, aspect=1.0) 

562 fig.subplots_adjust(left=0., right=1., bottom=0., top=1.) 

563 

564 problem = history.problem 

565 models = history.models 

566 

567 if models.size == 0: 

568 logger.warning('Empty models vector.') 

569 return [] 

570 

571 # gms = problem.combine_misfits(history.misfits) 

572 # isort = num.argsort(gms) 

573 # iorder = num.empty_like(isort) 

574 # iorder[isort] = num.arange(iorder.size)[::-1] 

575 

576 ref_source = problem.base_source 

577 

578 mean_source = stats.get_mean_source( 

579 problem, history.models) 

580 

581 best_source = history.get_best_source() 

582 

583 nlines_max = int(round(self.size_cm[1] / 5. * 4. - 1.0)) 

584 

585 if self.cluster_attribute: 

586 cluster_sources = history.mean_sources_by_cluster( 

587 self.cluster_attribute) 

588 else: 

589 cluster_sources = [] 

590 

591 def get_deco(source): 

592 mt = source.pyrocko_moment_tensor() 

593 return mt.standard_decomposition() 

594 

595 lines = [] 

596 lines.append( 

597 ('Ensemble best', get_deco(best_source), mpl_color('aluminium5'))) 

598 

599 lines.append( 

600 ('Ensemble mean', get_deco(mean_source), mpl_color('aluminium5'))) 

601 

602 for (icluster, perc, source) in cluster_sources: 

603 if len(lines) < nlines_max - int(self.show_reference): 

604 lines.append( 

605 (cluster_label(icluster, perc), 

606 get_deco(source), 

607 cluster_color(icluster))) 

608 else: 

609 logger.warning( 

610 'Skipping display of cluster %i because figure height is ' 

611 'too small. Figure height should be at least %g cm.' % ( 

612 icluster, (3 + len(cluster_sources) 

613 + int(self.show_reference)) * 5/4.)) 

614 

615 if self.show_reference: 

616 lines.append( 

617 ('Reference', get_deco(ref_source), mpl_color('aluminium3'))) 

618 

619 moment_full_max = max(deco[-1][0] for (_, deco, _) in lines) 

620 

621 for xpos, label in [ 

622 (0., 'Full'), 

623 (2., 'Isotropic'), 

624 (4., 'Deviatoric'), 

625 (6., 'CLVD'), 

626 (8., 'DC')]: 

627 

628 axes.annotate( 

629 label, 

630 xy=(1 + xpos, nlines_max), 

631 xycoords='data', 

632 xytext=(0., 0.), 

633 textcoords='offset points', 

634 ha='center', 

635 va='center', 

636 color='black', 

637 fontsize=fontsize) 

638 

639 for i, (label, deco, color_t) in enumerate(lines): 

640 ypos = nlines_max - i - 1.0 

641 

642 [(moment_iso, ratio_iso, m_iso), 

643 (moment_dc, ratio_dc, m_dc), 

644 (moment_clvd, ratio_clvd, m_clvd), 

645 (moment_devi, ratio_devi, m_devi), 

646 (moment_full, ratio_full, m_full)] = deco 

647 

648 size0 = moment_full / moment_full_max 

649 

650 axes.annotate( 

651 label, 

652 xy=(-2., ypos), 

653 xycoords='data', 

654 xytext=(0., 0.), 

655 textcoords='offset points', 

656 ha='left', 

657 va='center', 

658 color='black', 

659 fontsize=fontsize) 

660 

661 for xpos, mt_part, ratio, ops in [ 

662 (0., m_full, ratio_full, '-'), 

663 (2., m_iso, ratio_iso, '='), 

664 (4., m_devi, ratio_devi, '='), 

665 (6., m_clvd, ratio_clvd, '+'), 

666 (8., m_dc, ratio_dc, None)]: 

667 

668 if ratio > 1e-4: 

669 try: 

670 beachball.plot_beachball_mpl( 

671 mt_part, axes, 

672 beachball_type='full', 

673 position=(1. + xpos, ypos), 

674 size=0.9 * size0 * math.sqrt(ratio), 

675 size_units='data', 

676 color_t=color_t, 

677 linewidth=1.0) 

678 

679 except beachball.BeachballError as e: 

680 logger.warning(str(e)) 

681 

682 axes.annotate( 

683 'ERROR', 

684 xy=(1. + xpos, ypos), 

685 ha='center', 

686 va='center', 

687 color='red', 

688 fontsize=fontsize) 

689 

690 else: 

691 axes.annotate( 

692 'N/A', 

693 xy=(1. + xpos, ypos), 

694 ha='center', 

695 va='center', 

696 color='black', 

697 fontsize=fontsize) 

698 

699 if ops is not None: 

700 axes.annotate( 

701 ops, 

702 xy=(2. + xpos, ypos), 

703 ha='center', 

704 va='center', 

705 color='black', 

706 fontsize=fontsize) 

707 

708 axes.axison = False 

709 axes.set_xlim(-2.25, 9.75) 

710 axes.set_ylim(-0.5, nlines_max+0.5) 

711 

712 item = PlotItem(name='main') 

713 return [[item, fig]] 

714 

715 

716class MTLocationPlot(SectionPlotConfig): 

717 ''' MT location plot of the best solutions in three cross-sections. ''' 

718 name = 'location_mt' 

719 beachball_type = StringChoice.T( 

720 choices=['full', 'deviatoric', 'dc'], 

721 default='dc') 

722 normalisation_gamma = Float.T( 

723 default=3., 

724 help='Normalisation of colors and alpha as :math:`x^\\gamma`.' 

725 'A linear colormap/alpha with :math:`\\gamma=1`.') 

726 

727 def make(self, environ): 

728 environ.setup_modelling() 

729 cm = environ.get_plot_collection_manager() 

730 history = environ.get_history(subset='harvest') 

731 mpl_init(fontsize=self.font_size) 

732 self._to_be_closed = [] 

733 cm.create_group_mpl( 

734 self, 

735 self.draw_figures(history), 

736 title=u'MT Location', 

737 section='solution', 

738 feather_icon='target', 

739 description=u''' 

740Location plot of the ensemble of best solutions in three cross-sections. 

741 

742The coordinate range is defined by the search space given in the config file. 

743Symbols show best double-couple mechanisms, and colors indicate low (red) and 

744high (blue) misfit. 

745''') 

746 for obj in self._to_be_closed: 

747 obj.close() 

748 

749 def draw_figures(self, history, color_p_axis=False): 

750 from matplotlib import colors 

751 

752 color = 'black' 

753 fontsize = self.font_size 

754 markersize = fontsize * 1.5 

755 beachballsize_small = markersize * 0.5 

756 beachball_type = self.beachball_type 

757 

758 problem = history.problem 

759 sp = SectionPlot(config=self) 

760 self._to_be_closed.append(sp) 

761 

762 fig = sp.fig 

763 axes_en = sp.axes_xy 

764 axes_dn = sp.axes_zy 

765 axes_ed = sp.axes_xz 

766 

767 bounds = problem.get_combined_bounds() 

768 

769 models = history.get_sorted_primary_models()[::-1] 

770 

771 iorder = num.arange(history.nmodels) 

772 

773 for parname, set_label, set_lim in [ 

774 ['east_shift', sp.set_xlabel, sp.set_xlim], 

775 ['north_shift', sp.set_ylabel, sp.set_ylim], 

776 ['depth', sp.set_zlabel, sp.set_zlim]]: 

777 

778 ipar = problem.name_to_index(parname) 

779 par = problem.combined[ipar] 

780 set_label(par.get_label()) 

781 xmin, xmax = fixlim(*par.scaled(bounds[ipar])) 

782 set_lim(xmin, xmax) 

783 

784 if 'volume_change' in problem.parameter_names: 

785 volumes = models[:, problem.name_to_index('volume_change')] 

786 volume_max = volumes.max() 

787 volume_min = volumes.min() 

788 

789 def scale_size(source): 

790 if not hasattr(source, 'volume_change'): 

791 return beachballsize_small 

792 

793 volume_change = source.volume_change 

794 fac = (volume_change - volume_min) / (volume_max - volume_min) 

795 return markersize * .25 + markersize * .5 * fac 

796 

797 for axes, xparname, yparname in [ 

798 (axes_en, 'east_shift', 'north_shift'), 

799 (axes_dn, 'depth', 'north_shift'), 

800 (axes_ed, 'east_shift', 'depth')]: 

801 

802 ixpar = problem.name_to_index(xparname) 

803 iypar = problem.name_to_index(yparname) 

804 

805 xpar = problem.combined[ixpar] 

806 ypar = problem.combined[iypar] 

807 

808 xmin, xmax = fixlim(*xpar.scaled(bounds[ixpar])) 

809 ymin, ymax = fixlim(*ypar.scaled(bounds[iypar])) 

810 

811 try: 

812 axes.set_facecolor(mpl_color('aluminium1')) 

813 except AttributeError: 

814 axes.patch.set_facecolor(mpl_color('aluminium1')) 

815 

816 rect = patches.Rectangle( 

817 (xmin, ymin), xmax-xmin, ymax-ymin, 

818 facecolor=mpl_color('white'), 

819 edgecolor=mpl_color('aluminium2')) 

820 

821 axes.add_patch(rect) 

822 

823 # fxs = xpar.scaled(problem.extract(models, ixpar)) 

824 # fys = ypar.scaled(problem.extract(models, iypar)) 

825 

826 # axes.set_xlim(*fixlim(num.min(fxs), num.max(fxs))) 

827 # axes.set_ylim(*fixlim(num.min(fys), num.max(fys))) 

828 

829 cmap = cm.ScalarMappable( 

830 norm=colors.PowerNorm( 

831 gamma=self.normalisation_gamma, 

832 vmin=iorder.min(), 

833 vmax=iorder.max()), 

834 

835 cmap=plt.get_cmap('coolwarm')) 

836 

837 for ix, x in enumerate(models): 

838 

839 source = problem.get_source(x) 

840 mt = source.pyrocko_moment_tensor( 

841 store=problem.get_gf_store(problem.targets[0]), 

842 target=problem.targets[0]) 

843 fx = problem.extract(x, ixpar) 

844 fy = problem.extract(x, iypar) 

845 sx, sy = xpar.scaled(fx), ypar.scaled(fy) 

846 

847 # TODO: Add rotation in cross-sections 

848 color = cmap.to_rgba(iorder[ix]) 

849 

850 alpha = (iorder[ix] - iorder.min()) / \ 

851 float(iorder.max() - iorder.min()) 

852 alpha = alpha**self.normalisation_gamma 

853 

854 try: 

855 beachball.plot_beachball_mpl( 

856 mt, axes, 

857 beachball_type=beachball_type, 

858 position=(sx, sy), 

859 size=scale_size(source), 

860 color_t=color, 

861 color_p=color if color_p_axis else 'white', 

862 alpha=alpha, 

863 zorder=1, 

864 linewidth=0.25) 

865 

866 except beachball.BeachballError as e: 

867 logger.warning(str(e)) 

868 

869 item = PlotItem(name='main') 

870 return [[item, fig]] 

871 

872 

873class MTFuzzyPlot(PlotConfig): 

874 '''Fuzzy, propabalistic moment tensor plot ''' 

875 

876 name = 'mt_fuzzy' 

877 size_cm = Tuple.T(2, Float.T(), default=(10., 10.)) 

878 cluster_attribute = meta.StringID.T( 

879 optional=True, 

880 help='name of attribute to use as cluster IDs') 

881 plot_polarities = Bool.T( 

882 default=True, 

883 help='show observed polarities used in inversion') 

884 

885 plot_polarities_model = StringChoice.T( 

886 choices=['mean', 'best', 'all'], 

887 default='mean', 

888 help='choice of model to use for plotting polarities') 

889 

890 def make(self, environ): 

891 environ.setup_modelling() 

892 cm = environ.get_plot_collection_manager() 

893 history = environ.get_history(subset='harvest') 

894 mpl_init(fontsize=self.font_size) 

895 cm.create_group_mpl( 

896 self, 

897 self.draw_figures(history), 

898 title=u'Fuzzy MT', 

899 section='solution', 

900 feather_icon='wind', 

901 description=u''' 

902A fuzzy moment tensor, illustrating the solution's uncertainty. 

903 

904The P wave radiation pattern strength of every ensemble solution is stacked for 

905all ray spokes. The projection shows the stacked radiation pattern. If the 

906variability of the ensemble solutions is small, the fuzzy plot has clearly 

907separated black and white fields, consistent with the nodal lines of the %s 

908best solution (indicated in red). 

909''' % ('cluster' if self.cluster_attribute is not None else 'global')) 

910 

911 def draw_figures(self, history): 

912 problem = history.problem 

913 

914 by_cluster = history.imodels_by_cluster( 

915 self.cluster_attribute) 

916 

917 targets = [ 

918 t for t in problem.targets if 

919 isinstance(t, PhasePickTarget) and t.weight_polarity > 0.] 

920 

921 ntargets = len(targets) 

922 

923 for icluster, percentage, imodels in by_cluster: 

924 misfits = history.misfits[imodels] 

925 models = history.models[imodels] 

926 

927 best_model = stats.get_best_x(problem, models, misfits) 

928 best_source = problem.get_source(best_model) 

929 best_mt = best_source.pyrocko_moment_tensor() 

930 

931 mean_model = stats.get_mean_x(models) 

932 mean_source = problem.get_source(mean_model) 

933 

934 sources = [problem.get_source(x) for x in models] 

935 mts = [source.pyrocko_moment_tensor() for source in sources] 

936 

937 if self.plot_polarities: 

938 

939 if self.plot_polarities_model == 'all': 

940 sources_polarities = sources 

941 models_polarities = models 

942 elif self.plot_polarities_model == 'best': 

943 sources_polarities = [best_source] 

944 models_polarities = [best_model] 

945 elif self.plot_polarities_model == 'mean': 

946 sources_polarities = [mean_source] 

947 models_polarities = [mean_model] 

948 else: 

949 raise meta.GrondError( 

950 'Invalid argument for plot_polarities_model') 

951 

952 polarities = num.zeros((len(sources_polarities), ntargets, 4)) 

953 

954 for isource, (source, model) in enumerate( 

955 zip(sources_polarities, models_polarities)): 

956 

957 results = problem.evaluate(model, targets=targets) 

958 for itarget, result in enumerate(results): 

959 result = results[itarget] 

960 polarities[isource, itarget, :] = ( 

961 result.polarity_obs, 

962 result.polarity_syn, 

963 result.azimuth, 

964 result.takeoff_angle) 

965 

966 fig = plt.figure(figsize=self.size_inch) 

967 fig.subplots_adjust(left=0., right=1., bottom=0., top=1.) 

968 axes = fig.add_subplot(1, 1, 1, aspect=1.0) 

969 

970 if self.cluster_attribute is not None: 

971 color = cluster_color(icluster) 

972 else: 

973 color = 'black' 

974 

975 scale = 10. 

976 

977 beachball.plot_fuzzy_beachball_mpl_pixmap( 

978 mts, axes, best_mt, 

979 beachball_type='full', 

980 size=scale, 

981 size_units='data', 

982 position=(0., 0.), 

983 color_t=color, 

984 edgecolor='black', 

985 best_color=mpl_color('scarletred2')) 

986 

987 if self.cluster_attribute is not None: 

988 axes.annotate( 

989 cluster_label(icluster, percentage), 

990 xy=(0., -0.5*scale), 

991 xycoords='data', 

992 xytext=(0., self.font_size/2.), 

993 textcoords='offset points', 

994 ha='center', 

995 va='bottom', 

996 color='black', 

997 fontsize=self.font_size) 

998 

999 if self.plot_polarities: 

1000 azi = polarities[:, :, 2].flatten() 

1001 takeoff = polarities[:, :, 3].flatten() 

1002 

1003 upper = takeoff > 90. 

1004 azi[upper] += 180. 

1005 takeoff[upper] = 180. - takeoff[upper] 

1006 

1007 rtp = num.ones((azi.size, 3)) 

1008 rtp[:, 1] = num.deg2rad(takeoff) 

1009 rtp[:, 2] = num.deg2rad(90.-azi) 

1010 

1011 # to 3D coordinates (x, y, z) 

1012 points = beachball.numpy_rtp2xyz(rtp) 

1013 

1014 # project to 2D with same projection as used in beachball 

1015 x, y = 0.5 * scale * beachball.project(points).T 

1016 

1017 positive = (polarities[:, :, 0] > 0.).flatten() 

1018 negative = num.logical_not(positive) 

1019 

1020 axes.plot( 

1021 x[positive], y[positive], 

1022 'o', 

1023 ms=8., 

1024 mew=1, 

1025 mec='white', 

1026 mfc=color) 

1027 

1028 axes.plot( 

1029 x[negative], y[negative], 

1030 'o', 

1031 ms=8., 

1032 mew=1, 

1033 mec=color, 

1034 mfc='white') 

1035 

1036 axes.set_xlim(-1.1*0.5*scale, 1.1*0.5*scale) 

1037 axes.set_ylim(-1.1*0.5*scale, 1.1*0.5*scale) 

1038 axes.set_axis_off() 

1039 

1040 item = PlotItem( 

1041 name=( 

1042 'cluster_%i' % icluster 

1043 if icluster >= 0 

1044 else 'unclustered')) 

1045 

1046 yield [item, fig] 

1047 

1048 

1049class HudsonPlot(PlotConfig): 

1050 

1051 ''' 

1052 Illustration of the solution distribution of decomposed moment tensor. 

1053 ''' 

1054 

1055 name = 'hudson' 

1056 size_cm = Tuple.T(2, Float.T(), default=(17.5, 17.5*(3./4.))) 

1057 beachball_type = StringChoice.T( 

1058 choices=['full', 'deviatoric', 'dc'], 

1059 default='dc') 

1060 show_reference = Bool.T(default=True) 

1061 

1062 def make(self, environ): 

1063 cm = environ.get_plot_collection_manager() 

1064 history = environ.get_history(subset='harvest') 

1065 mpl_init(fontsize=self.font_size) 

1066 cm.create_group_mpl( 

1067 self, 

1068 self.draw_figures(history), 

1069 title=u'Hudson Plot', 

1070 section='solution', 

1071 feather_icon='box', 

1072 description=u''' 

1073Hudson's source type plot with the ensemble of bootstrap solutions. 

1074 

1075For about 10% of the solutions (randomly chosen), the focal mechanism is 

1076depicted, others are represented as dots. The square marks the global best 

1077fitting solution. 

1078''') 

1079 

1080 def draw_figures(self, history): 

1081 

1082 color = 'black' 

1083 fontsize = self.font_size 

1084 markersize = fontsize * 1.5 

1085 markersize_small = markersize * 0.2 

1086 beachballsize = markersize 

1087 beachballsize_small = beachballsize * 0.5 

1088 beachball_type = self.beachball_type 

1089 

1090 problem = history.problem 

1091 best_source = history.get_best_source() 

1092 mean_source = history.get_mean_source() 

1093 

1094 fig = plt.figure(figsize=self.size_inch) 

1095 axes = fig.add_subplot(1, 1, 1) 

1096 

1097 data = [] 

1098 for ix, x in enumerate(history.models): 

1099 source = problem.get_source(x) 

1100 mt = source.pyrocko_moment_tensor() 

1101 u, v = hudson.project(mt) 

1102 

1103 if random.random() < 0.1: 

1104 try: 

1105 beachball.plot_beachball_mpl( 

1106 mt, axes, 

1107 beachball_type=beachball_type, 

1108 position=(u, v), 

1109 size=beachballsize_small, 

1110 color_t=color, 

1111 alpha=0.5, 

1112 zorder=1, 

1113 linewidth=0.25) 

1114 except beachball.BeachballError as e: 

1115 logger.warning(str(e)) 

1116 

1117 else: 

1118 data.append((u, v)) 

1119 

1120 if data: 

1121 u, v = num.array(data).T 

1122 axes.plot( 

1123 u, v, 'o', 

1124 color=color, 

1125 ms=markersize_small, 

1126 mec='none', 

1127 mew=0, 

1128 alpha=0.25, 

1129 zorder=0) 

1130 

1131 hudson.draw_axes(axes) 

1132 

1133 mt = mean_source.pyrocko_moment_tensor() 

1134 u, v = hudson.project(mt) 

1135 

1136 try: 

1137 beachball.plot_beachball_mpl( 

1138 mt, axes, 

1139 beachball_type=beachball_type, 

1140 position=(u, v), 

1141 size=beachballsize, 

1142 color_t=color, 

1143 zorder=2, 

1144 linewidth=0.5) 

1145 except beachball.BeachballError as e: 

1146 logger.warning(str(e)) 

1147 

1148 mt = best_source.pyrocko_moment_tensor() 

1149 u, v = hudson.project(mt) 

1150 

1151 axes.plot( 

1152 u, v, 's', 

1153 markersize=markersize, 

1154 mew=1, 

1155 mec='black', 

1156 mfc='none', 

1157 zorder=-2) 

1158 

1159 if self.show_reference: 

1160 mt = problem.base_source.pyrocko_moment_tensor() 

1161 u, v = hudson.project(mt) 

1162 

1163 try: 

1164 beachball.plot_beachball_mpl( 

1165 mt, axes, 

1166 beachball_type=beachball_type, 

1167 position=(u, v), 

1168 size=beachballsize, 

1169 color_t='red', 

1170 zorder=2, 

1171 linewidth=0.5) 

1172 except beachball.BeachballError as e: 

1173 logger.warning(str(e)) 

1174 

1175 item = PlotItem( 

1176 name='main') 

1177 return [[item, fig]] 

1178 

1179 

1180def get_plot_classes(): 

1181 return [ 

1182 JointparPlot, 

1183 HistogramPlot, 

1184 ]