Skip to content

Commit 8e1200f

Browse files
seismanmichaelgrundyvonnefroehlich
authored
Figure.meca: Add the private _FocalMechanismConvention class to simplify codes (#3551)
Co-authored-by: Michael Grund <[email protected]> Co-authored-by: Yvonne Fröhlich <[email protected]>
1 parent 9dda628 commit 8e1200f

File tree

2 files changed

+231
-198
lines changed

2 files changed

+231
-198
lines changed

pygmt/src/_common.py

+213-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Common functions used in multiple PyGMT functions/methods.
33
"""
44

5+
from collections.abc import Sequence
6+
from enum import StrEnum
57
from pathlib import Path
6-
from typing import Any
8+
from typing import Any, ClassVar, Literal
79

10+
from pygmt.exceptions import GMTInvalidInput
811
from pygmt.src.which import which
912

1013

@@ -39,3 +42,212 @@ def _data_geometry_is_point(data: Any, kind: str) -> bool:
3942
except FileNotFoundError:
4043
pass
4144
return False
45+
46+
47+
class _FocalMechanismConventionCode(StrEnum):
48+
"""
49+
Enum to handle focal mechanism convention codes.
50+
51+
The enum names are in the format of ``CONVENTION_COMPONENT``, where ``CONVENTION``
52+
is the focal mechanism convention and ``COMPONENT`` is the component of the seismic
53+
moment tensor to plot. The enum values are the single-letter codes that can be used
54+
in meca/coupe's ``-S`` option.
55+
56+
For some conventions, ``COMPONENT`` is not applicable, but we still define the enums
57+
to simplify the code logic.
58+
"""
59+
60+
AKI_DC = "a"
61+
AKI_DEVIATORIC = "a"
62+
AKI_FULL = "a"
63+
GCMT_DC = "c"
64+
GCMT_DEVIATORIC = "c"
65+
GCMT_FULL = "c"
66+
PARTIAL_DC = "p"
67+
PARTIAL_DEVIATORIC = "p"
68+
PARTIAL_FULL = "p"
69+
MT_DC = "d"
70+
MT_DEVIATORIC = "z"
71+
MT_FULL = "m"
72+
PRINCIPAL_AXIS_DC = "y"
73+
PRINCIPAL_AXIS_DEVIATORIC = "t"
74+
PRINCIPAL_AXIS_FULL = "x"
75+
76+
77+
class _FocalMechanismConvention:
78+
"""
79+
Class to handle focal mechanism convention, code, and associated parameters.
80+
81+
Examples
82+
--------
83+
>>> from pygmt.src._common import _FocalMechanismConvention
84+
85+
>>> conv = _FocalMechanismConvention("aki")
86+
>>> conv.code
87+
<_FocalMechanismConventionCode.AKI_DC: 'a'>
88+
>>> conv.params
89+
['strike', 'dip', 'rake', 'magnitude']
90+
91+
>>> conv = _FocalMechanismConvention("mt")
92+
>>> conv.code
93+
<_FocalMechanismConventionCode.MT_FULL: 'm'>
94+
>>> conv.params
95+
['mrr', 'mtt', 'mff', 'mrt', 'mrf', 'mtf', 'exponent']
96+
97+
>>> conv = _FocalMechanismConvention("mt", component="dc")
98+
>>> conv.code
99+
<_FocalMechanismConventionCode.MT_DC: 'd'>
100+
>>> conv.params
101+
['mrr', 'mtt', 'mff', 'mrt', 'mrf', 'mtf', 'exponent']
102+
103+
>>> conv = _FocalMechanismConvention("a")
104+
>>> conv.code
105+
<_FocalMechanismConventionCode.AKI_DC: 'a'>
106+
>>> conv.params
107+
['strike', 'dip', 'rake', 'magnitude']
108+
109+
>>> conv = _FocalMechanismConvention.from_params(
110+
... ["strike", "dip", "rake", "magnitude"]
111+
... )
112+
>>> conv.code
113+
<_FocalMechanismConventionCode.AKI_DC: 'a'>
114+
115+
>>> conv = _FocalMechanismConvention(convention="invalid")
116+
Traceback (most recent call last):
117+
...
118+
pygmt.exceptions.GMTInvalidInput: Invalid focal mechanism ...'.
119+
120+
>>> conv = _FocalMechanismConvention("mt", component="invalid")
121+
Traceback (most recent call last):
122+
...
123+
pygmt.exceptions.GMTInvalidInput: Invalid focal mechanism ...'.
124+
125+
>>> _FocalMechanismConvention.from_params(["strike", "dip", "rake"])
126+
Traceback (most recent call last):
127+
...
128+
pygmt.exceptions.GMTInvalidInput: Fail to determine focal mechanism convention...
129+
"""
130+
131+
# Mapping of focal mechanism conventions to their parameters.
132+
_params: ClassVar = {
133+
"aki": ["strike", "dip", "rake", "magnitude"],
134+
"gcmt": [
135+
"strike1",
136+
"dip1",
137+
"rake1",
138+
"strike2",
139+
"dip2",
140+
"rake2",
141+
"mantissa",
142+
"exponent",
143+
],
144+
"partial": ["strike1", "dip1", "strike2", "fault_type", "magnitude"],
145+
"mt": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"],
146+
"principal_axis": [
147+
"t_value",
148+
"t_azimuth",
149+
"t_plunge",
150+
"n_value",
151+
"n_azimuth",
152+
"n_plunge",
153+
"p_value",
154+
"p_azimuth",
155+
"p_plunge",
156+
"exponent",
157+
],
158+
}
159+
160+
def __init__(
161+
self,
162+
convention: Literal["aki", "gcmt", "partial", "mt", "principal_axis"],
163+
component: Literal["full", "deviatoric", "dc"] = "full",
164+
):
165+
"""
166+
Initialize the ``_FocalMechanismConvention`` object from ``convention`` and
167+
``component``.
168+
169+
If the convention is specified via a single-letter code, ``convention`` and
170+
``component`` are determined from the code.
171+
172+
Parameters
173+
----------
174+
convention
175+
The focal mechanism convention. Valid values are:
176+
177+
- ``"aki"``: Aki and Richards convention.
178+
- ``"gcmt"``: Global CMT (Centroid Moment Tensor) convention.
179+
- ``"partial"``: Partial focal mechanism convention.
180+
- ``"mt"``: Moment Tensor convention.
181+
- ``"principal_axis"``: Principal axis convention.
182+
component
183+
The component of the seismic moment tensor to plot. Valid values are:
184+
185+
- ``"full"``: the full seismic moment tensor
186+
- ``"dc"``: the closest double couple defined from the moment tensor (zero
187+
trace and zero determinant)
188+
- ``"deviatoric"``: deviatoric part of the moment tensor (zero trace)
189+
190+
Doesn't apply to the conventions ``"aki"``, ``"gcmt"``, and ``"partial"``.
191+
"""
192+
# TODO(Python>=3.12): Simplify to "convention in _FocalMechanismConventionCode".
193+
if convention in _FocalMechanismConventionCode.__members__.values():
194+
# Convention is specified via the actual single-letter convention code.
195+
self.code = _FocalMechanismConventionCode(convention)
196+
# Parse the convention from the convention code name.
197+
self._convention = "_".join(self.code.name.split("_")[:-1]).lower()
198+
else: # Convention is specified via "convention" and "component".
199+
name = f"{convention.upper()}_{component.upper()}" # e.g., "AKI_DC"
200+
if name not in _FocalMechanismConventionCode.__members__:
201+
msg = (
202+
"Invalid focal mechanism convention with "
203+
f"convention='{convention}' and component='{component}'."
204+
)
205+
raise GMTInvalidInput(msg)
206+
self.code = _FocalMechanismConventionCode[name]
207+
self._convention = convention
208+
209+
@property
210+
def params(self):
211+
"""
212+
The parameters associated with the focal mechanism convention.
213+
"""
214+
return self._params[self._convention]
215+
216+
@classmethod
217+
def from_params(
218+
cls,
219+
params: Sequence[str],
220+
component: Literal["full", "deviatoric", "dc"] = "full",
221+
) -> "_FocalMechanismConvention":
222+
"""
223+
Create a _FocalMechanismConvention object from a sequence of parameters.
224+
225+
The method checks if the given parameters are a superset of a supported focal
226+
mechanism convention to determine the convention. If the parameters are not
227+
sufficient to determine the convention, an exception is raised.
228+
229+
Parameters
230+
----------
231+
params
232+
Sequence of parameters to determine the focal mechanism convention. The
233+
order of the parameters does not matter.
234+
235+
Returns
236+
-------
237+
_FocalMechanismConvention
238+
The _FocalMechanismConvention object.
239+
240+
Raises
241+
------
242+
GMTInvalidInput
243+
If the focal mechanism convention cannot be determined from the given
244+
parameters.
245+
"""
246+
for convention, param_list in cls._params.items():
247+
if set(param_list).issubset(set(params)):
248+
return cls(convention, component=component)
249+
msg = (
250+
"Fail to determine focal mechanism convention from the given parameters: "
251+
f"{', '.join(params)}."
252+
)
253+
raise GMTInvalidInput(msg)

0 commit comments

Comments
 (0)