77import numpy as np
88
99from 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
1111from mtuq .grid_search import DataArray , DataFrame
1212from mtuq .util import warn
1313from 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
82112def 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