Skip to content

Commit b163fcc

Browse files
committed
Reimplement omega angle calculation and confidence curve from clean master. Follows PR #341
1 parent 53e2091 commit b163fcc

3 files changed

Lines changed: 116 additions & 10 deletions

File tree

mtuq/graphics/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
plot_data_greens1, plot_data_greens2, plot_data_greens3
3838

3939
from mtuq.graphics.uq.omega import\
40-
plot_cdf, plot_pdf, plot_screening_curve
40+
plot_cdf, plot_pdf, plot_screening_curve, plot_confidence_curve
4141

4242
from mtuq.graphics.uq import\
4343
likelihood_analysis

mtuq/graphics/uq/_matplotlib.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,55 @@ def _plot_omega_matplotlib(filename, omega, values,
607607
pyplot.savefig(filename)
608608
pyplot.close()
609609

610+
def _plot_confidence_curve_matplotlib(filename, fractional_volume, values,
611+
title=None, xlabel='V', ylabel=r'$\mathcal{P}(V)$', figsize=(4., 4.), fontsize=18.):
612+
613+
fractional_volume = np.insert(fractional_volume, 0, 0)
614+
values = np.insert(values, 0, 0)
615+
fractional_volume = np.append(fractional_volume, 1)
616+
values = np.append(values, 1)
617+
fig, ax = pyplot.subplots(figsize=figsize)
618+
619+
ax.plot(fractional_volume, values, 'r-', linewidth=2.5, clip_on=False)
620+
621+
ax.plot(fractional_volume, fractional_volume, linestyle='--', color='gray', linewidth=1.5)
622+
623+
ax.fill_between(fractional_volume, values, 0, color='gray', alpha=0.3)
624+
625+
# Make sure the plot starts at 0,0
626+
fractional_volume = np.insert(fractional_volume, 0, 0)
627+
values = np.insert(values, 0, 0)
628+
629+
ax.plot([1], [1], 'ko', clip_on=False) # Marker at top-right corner
630+
ax.plot([0], [0], 'ko', clip_on=False) # Bottom-left corner marker
631+
632+
633+
# Display in text the average value of P(V)
634+
average = np.mean(values)
635+
ax.text(0.72, 0.10, r'$\mathcal{{P}}_{{AV}} = {:.2f}$'.format(average), # Use raw string (r'') and LaTeX math formatting
636+
fontsize=fontsize+2, ha='center', va='center', transform=ax.transAxes)
637+
638+
# Customize tick labels
639+
ax.set_xticks(np.linspace(0, 1, 11)) # Keep original ticks
640+
ax.set_yticks(np.linspace(0, 1, 11))
641+
ax.set_xticklabels(['0' if tick == 0 else '1' if tick == 1 else '' for tick in np.linspace(0, 1, 11)], fontsize=fontsize-2)
642+
ax.set_yticklabels(['0' if tick == 0 else '1' if tick == 1 else '' for tick in np.linspace(0, 1, 11)], fontsize=fontsize-2)
643+
644+
645+
if title:
646+
ax.set_title(title, fontsize=fontsize)
647+
648+
if xlabel:
649+
ax.set_xlabel(xlabel, fontsize=fontsize)
650+
651+
if ylabel:
652+
ax.set_ylabel(ylabel, fontsize=fontsize)
653+
654+
ax.set_xlim(0, 1)
655+
ax.set_ylim(0, 1)
656+
ax.margins(x=0.02, y=0.02)
657+
658+
pyplot.savefig(filename, bbox_inches='tight', dpi=300)
610659
#
611660
# utility functions
612661
#

mtuq/graphics/uq/omega.py

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import numpy as np
88

99
from mtuq import Force, MomentTensor
10-
from mtuq.graphics.uq._matplotlib import _plot_omega_matplotlib
10+
from mtuq.graphics.uq._matplotlib import _plot_omega_matplotlib, _plot_confidence_curve_matplotlib
1111
from mtuq.grid_search import DataArray, DataFrame
1212
from mtuq.util import warn
1313
from mtuq.util.math import to_mij
@@ -47,7 +47,7 @@ def plot_pdf(filename, df, var, m0=None, nbins=50, normalized=False, **kwargs):
4747

4848

4949

50-
def plot_cdf(filename, df, var, nbins=50, normalized=False, **kwargs):
50+
def plot_cdf(filename, df, var, m0=None, nbins=50, normalized=False, **kwargs):
5151
""" Plots cumulative distribution function over angular distance
5252
5353
.. rubric :: Input arguments
@@ -77,7 +77,37 @@ def plot_cdf(filename, df, var, nbins=50, normalized=False, **kwargs):
7777

7878
_plot_omega(filename, omega, np.cumsum(pdf), **kwargs)
7979

80+
def plot_confidence_curve(filename, df, var, m0=None, nbins=50, normalized=False, **kwargs):
81+
""" Plots confidence curve over fractional volume
8082
83+
.. rubric :: Input arguments
84+
85+
``filename`` (`str`):
86+
Name of output image file
87+
88+
``df`` (`DataFrame`):
89+
Data structure containing moment tensors and corresponding misfit values
90+
91+
``var`` (`float` or `array`):
92+
Data variance
93+
94+
``nbins`` (`int`):
95+
Number of angular distance bins
96+
97+
``normalized`` (`bool`):
98+
Normalize each angular distance bin by volume of corresponding shell?
99+
100+
"""
101+
if not isuniform(df):
102+
warn('plot_confidence_curve requires randomly-drawn grid')
103+
return
104+
105+
omega, pdf = _calculate_pdf(df, var, m0=m0, nbins=nbins,
106+
normalized=normalized)
107+
_, pdf_homo = _calculate_pdf(df*0, var, m0=m0, nbins=nbins,
108+
normalized=normalized)
109+
110+
_plot_omega(filename, np.cumsum(pdf_homo/np.sum(pdf_homo)), np.cumsum(pdf/np.sum(pdf)), backend=_plot_confidence_curve_matplotlib, **kwargs)
81111

82112
def plot_screening_curve(filename, ds, var, nbins=50, **kwargs):
83113
""" Plots explosion screening curve (maximum likelihood versus angular
@@ -164,7 +194,7 @@ def _calculate_omega(df, m0=None):
164194
# extract reference vector
165195
if type(m0)==MomentTensor:
166196
# convert from lune to mij parameters
167-
m0 = m0.as_vector()
197+
m0 = m0.as_matrix()
168198

169199
elif type(m0)==Force:
170200
raise NotImplementedError
@@ -173,14 +203,41 @@ def _calculate_omega(df, m0=None):
173203
# assume df holds likelihoods, try maximum likelihood estimate
174204
idx = _argmax(df)
175205
m0 = m[idx,:]
176-
177-
# vectorized dot product
178-
dp = np.dot(m, m0)
179-
dp /= np.sum(m0**2)**0.5
180-
dp /= np.sum(m**2, axis=1)**0.5
206+
m0 = np.array([[m0[0], m0[3], m0[4]],
207+
[m0[3], m0[1], m0[5]],
208+
[m0[4], m0[5], m0[2]]])
209+
210+
211+
m_tensors = np.zeros((m.shape[0], 3, 3))
212+
m_tensors[:, 0, 0] = m[:, 0] # Mrr
213+
m_tensors[:, 1, 1] = m[:, 1] # Mtt
214+
m_tensors[:, 2, 2] = m[:, 2] # Mpp
215+
m_tensors[:, 0, 1] = m[:, 3] # Mrt
216+
m_tensors[:, 1, 0] = m[:, 3] # Mrt (symmetric)
217+
m_tensors[:, 0, 2] = m[:, 4] # Mrp
218+
m_tensors[:, 2, 0] = m[:, 4] # Mrp (symmetric)
219+
m_tensors[:, 1, 2] = m[:, 5] # Mtp
220+
m_tensors[:, 2, 1] = m[:, 5] # Mtp (symmetric)
221+
222+
# Compute the dot product of the tensors M and N
223+
dot_product = np.einsum('...ij,...ij->...', m0, m_tensors)
224+
225+
# Compute the norms of the tensors M and N
226+
norm_M = np.sqrt(np.einsum('...ij,...ij->...', m0, m0))
227+
norm_N = np.sqrt(np.einsum('...ij,...ij->...', m_tensors, m_tensors))
228+
229+
# Compute the cosine of the angle between the tensors
230+
cos_angle = dot_product / (norm_M * norm_N)
231+
232+
# Clip values to the valid range for arccos (prevent errors from numerical precision)
233+
cos_angle = cos_angle.clip(-1, 1)
181234

182235
# return angles as NumPy array
183-
omega = 180./np.pi * np.arccos(dp)
236+
omega = 180./np.pi * np.arccos(cos_angle)
237+
238+
# Fix nan values for identical vectors
239+
omega[np.isnan(omega)] = 0.
240+
184241
return omega
185242

186243

0 commit comments

Comments
 (0)