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
« 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
8import numpy as num
9from matplotlib import cm, patches, colors as mcolors
11from pyrocko.guts import Tuple, Float, Int, String, List, Bool, StringChoice
13from pyrocko.plot import mpl_margins, mpl_color, mpl_init, mpl_graph_color
14from pyrocko.plot import beachball, hudson
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
23logger = logging.getLogger('grond.problem.plot')
25guts_prefix = 'grond'
28def cluster_label(icluster, perc):
29 if icluster == -1:
30 return 'Unclust. (%.0f%%)' % perc
31 else:
32 return 'Cluster %i (%.0f%%)' % (icluster, perc)
35def cluster_color(icluster):
36 if icluster == -1:
37 return mpl_color('aluminium3')
38 else:
39 return mpl_graph_color(icluster)
42def fixlim(lo, hi):
43 if lo == hi:
44 return lo - 1.0, hi + 1.0
45 else:
46 return lo, hi
49def eigh_sorted(mat):
50 evals, evecs = num.linalg.eigh(mat)
51 iorder = num.argsort(evals)
52 return evals[iorder], evecs[:, iorder]
55class JointparPlot(PlotConfig):
56 '''
57 Source problem parameter's tradeoff plots.
58 '''
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)
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 ''
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.
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)
97 def draw_figures(self, history, optimiser):
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'
110 problem = history.problem
111 if not problem:
112 return []
114 models = history.models
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)
124 xref = problem.get_reference_model()
126 isort = history.get_sorted_misfits_idx(chain=ibootstrap)[::-1]
127 models = history.get_sorted_models(chain=ibootstrap)[::-1]
128 nmodels = history.nmodels
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]
136 kwargs = {}
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)
144 elif color_parameter == 'misfit':
145 iorder = num.arange(nmodels)
146 icolor = iorder
148 elif color_parameter in problem.parameter_names:
149 ind = problem.name_to_index(color_parameter)
150 icolor = problem.extract(models, ind)
152 elif color_parameter in history.attribute_names:
153 icolor = history.get_attribute(color_parameter)[isort]
154 icolor_need = num.unique(icolor)
156 colors = []
157 for i in range(icolor_need[-1]+1):
158 colors.append(mpl_graph_color(i))
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)
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
175 smap[iselected] = ipar
176 iselected += 1
178 nselected = iselected
180 if nselected < 2:
181 logger.warning(
182 'Cannot draw joinpar figures with less than two parameters '
183 'selected.')
184 return []
186 nfig = (nselected - 2) // nsubplots + 1
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)
199 figs.append(figs_row)
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]
208 ixg = (iselected - 1)
209 iyg = jselected
211 ix = ixg % nsubplots
212 iy = iyg % nsubplots
214 ifig = ixg // nsubplots
215 jfig = iyg // nsubplots
217 aind = (nsubplots, nsubplots, (ix * nsubplots) + iy + 1)
219 item, fig = figs[ifig][jfig]
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)
227 axes = fig.add_subplot(*aind)
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)
235 xmin, xmax = fixlim(*xpar.scaled(bounds[jpar]))
236 ymin, ymax = fixlim(*ypar.scaled(bounds[ipar]))
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)]:
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.)
254 if iy == nsubplots - 1 or jselected + 1 == iselected:
255 for (ypos, yoff, y) in [
256 (0., 10., ymin),
257 (1.0, -10., ymax)]:
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.)
269 axes.set_xlim(xmin, xmax)
270 axes.set_ylim(ymin, ymax)
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([])
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.)
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())
302 fx = problem.extract(models, jpar)
303 fy = problem.extract(models, ipar)
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)
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])))
324 ell.set_facecolor('none')
325 ell.set_edgecolor(mpl_color('aluminium5'))
326 axes.add_artist(ell)
328 if self.show_reference:
329 fx = problem.extract(xref, jpar)
330 fy = problem.extract(xref, ipar)
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)
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)
343 return figs_flat
346class HistogramPlot(PlotConfig):
347 '''
348 Histograms or Gaussian kernel densities (default) of all parameters
349 (marginal distributions of model parameters).
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 '''
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)
372 def make(self, environ):
373 cm = environ.get_plot_collection_manager()
374 history = environ.get_history(subset='harvest')
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.
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:
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''')
399 def draw_figures(self, history):
401 import scipy.stats
402 from grond.core import make_stats
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')
414 problem = history.problem
416 models = history.models
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)
426 xref = problem.get_reference_model()
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
436 smap[iselected] = ipar
437 iselected += 1
439 nselected = iselected
440 del iselected
442 pnames = [
443 problem.combined[smap[iselected]].name
444 for iselected in range(nselected)]
446 rstats = make_stats(problem, models,
447 history.get_primary_chain_misfits(),
448 pnames=pnames)
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]
456 fig = plt.figure(figsize=figsize)
457 labelpos = mpl_margins(
458 fig, nw=1, nh=1, w=7., bottom=5., top=1, units=fontsize)
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))))
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
475 vps = num.linspace(vmin, vmax, 600)
476 pps = kde(vps)
478 axes.plot(
479 par.scaled(vps), par.inv_scaled(pps), color=stats_color)
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:])
488 axes.bar(par.scaled(vps), par.inv_scaled(pps),
489 par.scaled(2.*(vps - edges[:-1])),
490 color=bar_color)
492 pstats = rstats.parameter_stats_list[iselected]
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)
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)
514 if self.show_reference:
515 axes.axvline(
516 par.scaled(problem.extract(xref, ipar)),
517 color=ref_color)
519 item = PlotItem(name=par.name)
520 item.attributes['parameters'] = [par.name]
521 yield item, fig
524class MTDecompositionPlot(PlotConfig):
525 '''
526 Moment tensor decomposition plot.
527 '''
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)
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.
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 ''))
556 def draw_figures(self, history):
558 fontsize = self.font_size
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.)
564 problem = history.problem
565 models = history.models
567 if models.size == 0:
568 logger.warning('Empty models vector.')
569 return []
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]
576 ref_source = problem.base_source
578 mean_source = stats.get_mean_source(
579 problem, history.models)
581 best_source = history.get_best_source()
583 nlines_max = int(round(self.size_cm[1] / 5. * 4. - 1.0))
585 if self.cluster_attribute:
586 cluster_sources = history.mean_sources_by_cluster(
587 self.cluster_attribute)
588 else:
589 cluster_sources = []
591 def get_deco(source):
592 mt = source.pyrocko_moment_tensor()
593 return mt.standard_decomposition()
595 lines = []
596 lines.append(
597 ('Ensemble best', get_deco(best_source), mpl_color('aluminium5')))
599 lines.append(
600 ('Ensemble mean', get_deco(mean_source), mpl_color('aluminium5')))
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.))
615 if self.show_reference:
616 lines.append(
617 ('Reference', get_deco(ref_source), mpl_color('aluminium3')))
619 moment_full_max = max(deco[-1][0] for (_, deco, _) in lines)
621 for xpos, label in [
622 (0., 'Full'),
623 (2., 'Isotropic'),
624 (4., 'Deviatoric'),
625 (6., 'CLVD'),
626 (8., 'DC')]:
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)
639 for i, (label, deco, color_t) in enumerate(lines):
640 ypos = nlines_max - i - 1.0
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
648 size0 = moment_full / moment_full_max
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)
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)]:
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)
679 except beachball.BeachballError as e:
680 logger.warning(str(e))
682 axes.annotate(
683 'ERROR',
684 xy=(1. + xpos, ypos),
685 ha='center',
686 va='center',
687 color='red',
688 fontsize=fontsize)
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)
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)
708 axes.axison = False
709 axes.set_xlim(-2.25, 9.75)
710 axes.set_ylim(-0.5, nlines_max+0.5)
712 item = PlotItem(name='main')
713 return [[item, fig]]
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`.')
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.
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()
749 def draw_figures(self, history, color_p_axis=False):
750 from matplotlib import colors
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
758 problem = history.problem
759 sp = SectionPlot(config=self)
760 self._to_be_closed.append(sp)
762 fig = sp.fig
763 axes_en = sp.axes_xy
764 axes_dn = sp.axes_zy
765 axes_ed = sp.axes_xz
767 bounds = problem.get_combined_bounds()
769 models = history.get_sorted_primary_models()[::-1]
771 iorder = num.arange(history.nmodels)
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]]:
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)
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()
789 def scale_size(source):
790 if not hasattr(source, 'volume_change'):
791 return beachballsize_small
793 volume_change = source.volume_change
794 fac = (volume_change - volume_min) / (volume_max - volume_min)
795 return markersize * .25 + markersize * .5 * fac
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')]:
802 ixpar = problem.name_to_index(xparname)
803 iypar = problem.name_to_index(yparname)
805 xpar = problem.combined[ixpar]
806 ypar = problem.combined[iypar]
808 xmin, xmax = fixlim(*xpar.scaled(bounds[ixpar]))
809 ymin, ymax = fixlim(*ypar.scaled(bounds[iypar]))
811 try:
812 axes.set_facecolor(mpl_color('aluminium1'))
813 except AttributeError:
814 axes.patch.set_facecolor(mpl_color('aluminium1'))
816 rect = patches.Rectangle(
817 (xmin, ymin), xmax-xmin, ymax-ymin,
818 facecolor=mpl_color('white'),
819 edgecolor=mpl_color('aluminium2'))
821 axes.add_patch(rect)
823 # fxs = xpar.scaled(problem.extract(models, ixpar))
824 # fys = ypar.scaled(problem.extract(models, iypar))
826 # axes.set_xlim(*fixlim(num.min(fxs), num.max(fxs)))
827 # axes.set_ylim(*fixlim(num.min(fys), num.max(fys)))
829 cmap = cm.ScalarMappable(
830 norm=colors.PowerNorm(
831 gamma=self.normalisation_gamma,
832 vmin=iorder.min(),
833 vmax=iorder.max()),
835 cmap=plt.get_cmap('coolwarm'))
837 for ix, x in enumerate(models):
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)
847 # TODO: Add rotation in cross-sections
848 color = cmap.to_rgba(iorder[ix])
850 alpha = (iorder[ix] - iorder.min()) / \
851 float(iorder.max() - iorder.min())
852 alpha = alpha**self.normalisation_gamma
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)
866 except beachball.BeachballError as e:
867 logger.warning(str(e))
869 item = PlotItem(name='main')
870 return [[item, fig]]
873class MTFuzzyPlot(PlotConfig):
874 '''Fuzzy, propabalistic moment tensor plot '''
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')
885 plot_polarities_model = StringChoice.T(
886 choices=['mean', 'best', 'all'],
887 default='mean',
888 help='choice of model to use for plotting polarities')
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.
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'))
911 def draw_figures(self, history):
912 problem = history.problem
914 by_cluster = history.imodels_by_cluster(
915 self.cluster_attribute)
917 targets = [
918 t for t in problem.targets if
919 isinstance(t, PhasePickTarget) and t.weight_polarity > 0.]
921 ntargets = len(targets)
923 for icluster, percentage, imodels in by_cluster:
924 misfits = history.misfits[imodels]
925 models = history.models[imodels]
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()
931 mean_model = stats.get_mean_x(models)
932 mean_source = problem.get_source(mean_model)
934 sources = [problem.get_source(x) for x in models]
935 mts = [source.pyrocko_moment_tensor() for source in sources]
937 if self.plot_polarities:
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')
952 polarities = num.zeros((len(sources_polarities), ntargets, 4))
954 for isource, (source, model) in enumerate(
955 zip(sources_polarities, models_polarities)):
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)
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)
970 if self.cluster_attribute is not None:
971 color = cluster_color(icluster)
972 else:
973 color = 'black'
975 scale = 10.
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'))
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)
999 if self.plot_polarities:
1000 azi = polarities[:, :, 2].flatten()
1001 takeoff = polarities[:, :, 3].flatten()
1003 upper = takeoff > 90.
1004 azi[upper] += 180.
1005 takeoff[upper] = 180. - takeoff[upper]
1007 rtp = num.ones((azi.size, 3))
1008 rtp[:, 1] = num.deg2rad(takeoff)
1009 rtp[:, 2] = num.deg2rad(90.-azi)
1011 # to 3D coordinates (x, y, z)
1012 points = beachball.numpy_rtp2xyz(rtp)
1014 # project to 2D with same projection as used in beachball
1015 x, y = 0.5 * scale * beachball.project(points).T
1017 positive = (polarities[:, :, 0] > 0.).flatten()
1018 negative = num.logical_not(positive)
1020 axes.plot(
1021 x[positive], y[positive],
1022 'o',
1023 ms=8.,
1024 mew=1,
1025 mec='white',
1026 mfc=color)
1028 axes.plot(
1029 x[negative], y[negative],
1030 'o',
1031 ms=8.,
1032 mew=1,
1033 mec=color,
1034 mfc='white')
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()
1040 item = PlotItem(
1041 name=(
1042 'cluster_%i' % icluster
1043 if icluster >= 0
1044 else 'unclustered'))
1046 yield [item, fig]
1049class HudsonPlot(PlotConfig):
1051 '''
1052 Illustration of the solution distribution of decomposed moment tensor.
1053 '''
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)
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.
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''')
1080 def draw_figures(self, history):
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
1090 problem = history.problem
1091 best_source = history.get_best_source()
1092 mean_source = history.get_mean_source()
1094 fig = plt.figure(figsize=self.size_inch)
1095 axes = fig.add_subplot(1, 1, 1)
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)
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))
1117 else:
1118 data.append((u, v))
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)
1131 hudson.draw_axes(axes)
1133 mt = mean_source.pyrocko_moment_tensor()
1134 u, v = hudson.project(mt)
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))
1148 mt = best_source.pyrocko_moment_tensor()
1149 u, v = hudson.project(mt)
1151 axes.plot(
1152 u, v, 's',
1153 markersize=markersize,
1154 mew=1,
1155 mec='black',
1156 mfc='none',
1157 zorder=-2)
1159 if self.show_reference:
1160 mt = problem.base_source.pyrocko_moment_tensor()
1161 u, v = hudson.project(mt)
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))
1175 item = PlotItem(
1176 name='main')
1177 return [[item, fig]]
1180def get_plot_classes():
1181 return [
1182 JointparPlot,
1183 HistogramPlot,
1184 ]