LoopStructural 1.6.2__py3-none-any.whl → 1.6.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of LoopStructural might be problematic. Click here for more details.
- LoopStructural/datatypes/_bounding_box.py +77 -7
- LoopStructural/datatypes/_point.py +67 -7
- LoopStructural/datatypes/_structured_grid.py +17 -0
- LoopStructural/datatypes/_surface.py +17 -0
- LoopStructural/export/omf_wrapper.py +49 -21
- LoopStructural/interpolators/__init__.py +13 -0
- LoopStructural/interpolators/_api.py +81 -13
- LoopStructural/interpolators/_builders.py +141 -141
- LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +78 -88
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- LoopStructural/interpolators/_interpolator_builder.py +55 -0
- LoopStructural/interpolators/_interpolator_factory.py +7 -18
- LoopStructural/interpolators/_p1interpolator.py +3 -3
- LoopStructural/interpolators/_surfe_wrapper.py +42 -12
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
- LoopStructural/interpolators/supports/_3d_base_structured.py +28 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -12
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
- LoopStructural/interpolators/supports/__init__.py +7 -0
- LoopStructural/interpolators/supports/_base_support.py +7 -0
- LoopStructural/modelling/__init__.py +1 -3
- LoopStructural/modelling/core/geological_model.py +11 -12
- LoopStructural/modelling/features/__init__.py +1 -0
- LoopStructural/modelling/features/_analytical_feature.py +48 -18
- LoopStructural/modelling/features/_base_geological_feature.py +37 -8
- LoopStructural/modelling/features/_cross_product_geological_feature.py +7 -0
- LoopStructural/modelling/features/_geological_feature.py +50 -12
- LoopStructural/modelling/features/_projected_vector_feature.py +112 -0
- LoopStructural/modelling/features/_structural_frame.py +16 -18
- LoopStructural/modelling/features/_unconformity_feature.py +3 -3
- LoopStructural/modelling/features/builders/_base_builder.py +8 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +47 -16
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +7 -2
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_function_feature.py +3 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +50 -53
- LoopStructural/modelling/features/fold/__init__.py +1 -2
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
- LoopStructural/modelling/features/fold/_foldframe.py +4 -4
- LoopStructural/modelling/features/fold/_svariogram.py +81 -46
- LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
- LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
- LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
- LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
- LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
- LoopStructural/modelling/input/process_data.py +47 -26
- LoopStructural/modelling/input/project_file.py +49 -23
- LoopStructural/modelling/intrusions/intrusion_feature.py +3 -0
- LoopStructural/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +17 -5
- LoopStructural/utils/_transformation.py +98 -14
- LoopStructural/utils/colours.py +50 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +51 -0
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.6.dist-info/METADATA +160 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/RECORD +66 -59
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/WHEEL +1 -1
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
- LoopStructural-1.6.2.dist-info/METADATA +0 -81
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from ast import List
|
|
3
|
+
from typing import Union, Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
import numpy.typing as npt
|
|
6
|
+
from .._svariogram import SVariogram
|
|
7
|
+
from scipy.optimize import curve_fit
|
|
8
|
+
|
|
9
|
+
from .....utils import getLogger
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseFoldRotationAngleProfile(metaclass=ABCMeta):
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
rotation_angle: Optional[npt.NDArray[np.float64]] = None,
|
|
19
|
+
fold_frame_coordinate: Optional[npt.NDArray[np.float64]] = None,
|
|
20
|
+
):
|
|
21
|
+
"""Base class for fold rotation angle functions
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
rotation_angle : npt.NDArray[np.float64], optional
|
|
26
|
+
the calculated fold rotation angle from observations in degrees, by default None
|
|
27
|
+
fold_frame_coordinate : npt.NDArray[np.float64], optional
|
|
28
|
+
fold frame coordinate scalar field value, by default None
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
self.rotation_angle = rotation_angle
|
|
32
|
+
self.fold_frame_coordinate = fold_frame_coordinate
|
|
33
|
+
self._evaluation_points = None
|
|
34
|
+
self._observers = []
|
|
35
|
+
self._svariogram = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def svario(self) -> SVariogram:
|
|
39
|
+
if self.fold_frame_coordinate is None or self.rotation_angle is None:
|
|
40
|
+
raise ValueError("Fold rotation angle and fold frame coordinate must be set")
|
|
41
|
+
if self._svariogram is None:
|
|
42
|
+
self._svariogram = SVariogram(self.fold_frame_coordinate, self.rotation_angle)
|
|
43
|
+
return self._svariogram
|
|
44
|
+
|
|
45
|
+
@svario.setter
|
|
46
|
+
def svario(self, value: SVariogram):
|
|
47
|
+
if isinstance(value, SVariogram):
|
|
48
|
+
self._svariogram = value
|
|
49
|
+
else:
|
|
50
|
+
logger.error("svario must be an instance of SVariogram")
|
|
51
|
+
raise ValueError("svario must be an instance of SVariogram")
|
|
52
|
+
|
|
53
|
+
def add_observer(self, watcher):
|
|
54
|
+
self._observers.append(watcher)
|
|
55
|
+
|
|
56
|
+
def notify_observers(self):
|
|
57
|
+
for observer in self._observers:
|
|
58
|
+
observer.set_not_up_to_date(self)
|
|
59
|
+
|
|
60
|
+
def calculate_misfit(
|
|
61
|
+
self,
|
|
62
|
+
rotation_angle: np.ndarray,
|
|
63
|
+
fold_frame_coordinate: np.ndarray,
|
|
64
|
+
) -> np.ndarray:
|
|
65
|
+
"""Calculate the rotation angle for the fold frame coordinate and return the misfit
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
params : dict, optional
|
|
70
|
+
Any parameters required to fit the function to the data, by default {}
|
|
71
|
+
rotation_angle : np.ndarray
|
|
72
|
+
fold rotation angle in degrees
|
|
73
|
+
fold_frame_coordinate : np.ndarray
|
|
74
|
+
fold frame coordinate
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
misfit : np.ndarray
|
|
79
|
+
returns misfit in degrees"""
|
|
80
|
+
return np.tan(np.deg2rad(rotation_angle)) - np.tan(
|
|
81
|
+
np.deg2rad(self.__call__(fold_frame_coordinate))
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def estimate_wavelength(
|
|
85
|
+
self, svariogram_parameters: dict = {}, wavelength_number: int = 1
|
|
86
|
+
) -> Union[float, np.ndarray]:
|
|
87
|
+
"""Estimate the wavelength of the fold profile using the svariogram parameters
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
svariogram_parameters : dict
|
|
92
|
+
svariogram parameters
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
float
|
|
97
|
+
estimated wavelength
|
|
98
|
+
"""
|
|
99
|
+
wl = self.svario.find_wavelengths(**svariogram_parameters)
|
|
100
|
+
if wavelength_number == 1:
|
|
101
|
+
return wl[0]
|
|
102
|
+
return wl
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def evaluation_points(self):
|
|
106
|
+
"""Return the evaluation points for the fold rotation angle function
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
np.ndarray
|
|
111
|
+
evaluation points
|
|
112
|
+
"""
|
|
113
|
+
if self._evaluation_points is not None:
|
|
114
|
+
return self._evaluation_points
|
|
115
|
+
return np.linspace(
|
|
116
|
+
np.min(self.fold_frame_coordinate), np.max(self.fold_frame_coordinate), 300
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@evaluation_points.setter
|
|
120
|
+
def evaluation_points(self, value):
|
|
121
|
+
self._evaluation_points = value
|
|
122
|
+
|
|
123
|
+
def fit(self, params: dict = {}) -> bool:
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
params : dict, optional
|
|
129
|
+
_description_, by default {}
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
bool
|
|
134
|
+
_description_
|
|
135
|
+
"""
|
|
136
|
+
if len(self.params) > 0:
|
|
137
|
+
success = False
|
|
138
|
+
if self.rotation_angle is None or self.fold_frame_coordinate is None:
|
|
139
|
+
logger.error("Fold rotation angle and fold frame coordinate must be set")
|
|
140
|
+
return False
|
|
141
|
+
guess = params.get(
|
|
142
|
+
"guess",
|
|
143
|
+
self.initial_guess(
|
|
144
|
+
wavelength=params.get("wavelength", None),
|
|
145
|
+
reset=params.get("reset", False),
|
|
146
|
+
svariogram_parameters=params.get("svariogram_parameters", {}),
|
|
147
|
+
calculate_wavelength=params.get("calculate_wavelength", True),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
mask = np.logical_or(
|
|
151
|
+
~np.isnan(self.fold_frame_coordinate), ~np.isnan(self.rotation_angle)
|
|
152
|
+
)
|
|
153
|
+
logger.info(f"Percentage of points not used {np.sum(~mask)/len(mask)*100}")
|
|
154
|
+
try:
|
|
155
|
+
logger.info(f"Trying to fit fold rotation angle with guess {guess}")
|
|
156
|
+
logger.info(f"Fold profile type: {self.__class__.__name__}")
|
|
157
|
+
res = curve_fit(
|
|
158
|
+
self._function,
|
|
159
|
+
self.fold_frame_coordinate[mask],
|
|
160
|
+
np.tan(np.deg2rad(self.rotation_angle[mask])),
|
|
161
|
+
p0=guess,
|
|
162
|
+
full_output=True,
|
|
163
|
+
)
|
|
164
|
+
logger.info(f'Fit results: {res[0]}')
|
|
165
|
+
guess = res[0]
|
|
166
|
+
logger.info(res[3])
|
|
167
|
+
success = True
|
|
168
|
+
except Exception as _e:
|
|
169
|
+
logger.error("Could not fit curve to S-Plot, check the wavelength")
|
|
170
|
+
try:
|
|
171
|
+
self.update_params(guess)
|
|
172
|
+
except Exception as _e:
|
|
173
|
+
logger.error("Could not update parameters")
|
|
174
|
+
return False
|
|
175
|
+
return success
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def update_params(self, params: Union[List, npt.NDArray[np.float64]]) -> None:
|
|
180
|
+
"""Update the parameters of the fold rotation angle function
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
params : dict
|
|
185
|
+
parameters to update
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def initial_guess(
|
|
191
|
+
self,
|
|
192
|
+
wavelength: Optional[float] = None,
|
|
193
|
+
calculate_wavelength: bool = True,
|
|
194
|
+
svariogram_parameters: dict = {},
|
|
195
|
+
reset: bool = False,
|
|
196
|
+
) -> np.ndarray:
|
|
197
|
+
"""_summary_
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
selfcalculate_wavelength : bool, optional
|
|
202
|
+
_description_, by default True
|
|
203
|
+
svariogram_parameters : dict, optional
|
|
204
|
+
_description_, by default {}
|
|
205
|
+
reset : bool, optional
|
|
206
|
+
_description_, by default False
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
np.ndarray
|
|
211
|
+
_description_
|
|
212
|
+
"""
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def _function(s, *args, **kwargs):
|
|
218
|
+
"""This is the function that is used to calculate the fold rotation angle
|
|
219
|
+
for a given fold frame coordinate
|
|
220
|
+
it is not called directly but is used by the __call__ method
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
s
|
|
225
|
+
*args
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
_description_
|
|
230
|
+
"""
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
def plot(self, ax=None, show_data=True, **kwargs):
|
|
234
|
+
"""Plot the fold rotation angle function
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
ax : _description_, optional
|
|
239
|
+
_description_, by default None
|
|
240
|
+
**kwargs
|
|
241
|
+
passed to matplotlib plot
|
|
242
|
+
"""
|
|
243
|
+
if ax is None:
|
|
244
|
+
import matplotlib.pyplot as plt
|
|
245
|
+
|
|
246
|
+
fig, ax = plt.subplots()
|
|
247
|
+
if show_data:
|
|
248
|
+
ax.scatter(self.fold_frame_coordinate, self.rotation_angle, c="r")
|
|
249
|
+
ax.plot(self.evaluation_points, self(self.evaluation_points), **kwargs)
|
|
250
|
+
return ax
|
|
251
|
+
|
|
252
|
+
def __call__(self, s):
|
|
253
|
+
return np.rad2deg(np.arctan(self._function(s, **self.params)))
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from ._base_fold_rotation_angle import BaseFoldRotationAngleProfile
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from typing import Optional, List, Union
|
|
5
|
+
from .....utils import getLogger
|
|
6
|
+
|
|
7
|
+
logger = getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FourierSeriesFoldRotationAngleProfile(BaseFoldRotationAngleProfile):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
rotation_angle: Optional[npt.NDArray[np.float64]] = None,
|
|
14
|
+
fold_frame_coordinate: Optional[npt.NDArray[np.float64]] = None,
|
|
15
|
+
c0=0,
|
|
16
|
+
c1=0,
|
|
17
|
+
c2=0,
|
|
18
|
+
w=1,
|
|
19
|
+
):
|
|
20
|
+
"""_summary_
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
rotation_angle : Optional[npt.NDArray[np.float64]], optional
|
|
25
|
+
_description_, by default None
|
|
26
|
+
fold_frame_coordinate : Optional[npt.NDArray[np.float64]], optional
|
|
27
|
+
_description_, by default None
|
|
28
|
+
c0 : int, optional
|
|
29
|
+
_description_, by default 0
|
|
30
|
+
c1 : int, optional
|
|
31
|
+
_description_, by default 0
|
|
32
|
+
c2 : int, optional
|
|
33
|
+
_description_, by default 0
|
|
34
|
+
w : int, optional
|
|
35
|
+
_description_, by default 1
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(rotation_angle, fold_frame_coordinate)
|
|
38
|
+
self._c0 = c0
|
|
39
|
+
self._c1 = c1
|
|
40
|
+
self._c2 = c2
|
|
41
|
+
self._w = w
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def c0(self):
|
|
45
|
+
return self._c0
|
|
46
|
+
|
|
47
|
+
@c0.setter
|
|
48
|
+
def c0(self, value):
|
|
49
|
+
self.notify_observers()
|
|
50
|
+
self._c0 = value
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def c1(self):
|
|
54
|
+
return self._c1
|
|
55
|
+
|
|
56
|
+
@c1.setter
|
|
57
|
+
def c1(self, value):
|
|
58
|
+
self.notify_observers()
|
|
59
|
+
self._c1 = value
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def c2(self):
|
|
63
|
+
return self._c2
|
|
64
|
+
|
|
65
|
+
@c2.setter
|
|
66
|
+
def c2(self, value):
|
|
67
|
+
self.notify_observers()
|
|
68
|
+
self._c2 = value
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def w(self):
|
|
72
|
+
return self._w
|
|
73
|
+
|
|
74
|
+
@w.setter
|
|
75
|
+
def w(self, value):
|
|
76
|
+
if value <= 0:
|
|
77
|
+
raise ValueError('wavelength must be greater than 0')
|
|
78
|
+
self.notify_observers()
|
|
79
|
+
self._w = value
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _function(x, c0, c1, c2, w):
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
x
|
|
88
|
+
c0
|
|
89
|
+
c1
|
|
90
|
+
c2
|
|
91
|
+
w
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
v = np.array(x.astype(float))
|
|
98
|
+
# v.fill(c0)
|
|
99
|
+
v = c0 + c1 * np.cos(2 * np.pi / w * x) + c2 * np.sin(2 * np.pi / w * x)
|
|
100
|
+
return v
|
|
101
|
+
|
|
102
|
+
def initial_guess(
|
|
103
|
+
self,
|
|
104
|
+
wavelength: Optional[float] = None,
|
|
105
|
+
calculate_wavelength: bool = True,
|
|
106
|
+
svariogram_parameters: dict = {},
|
|
107
|
+
reset: bool = False,
|
|
108
|
+
):
|
|
109
|
+
# reset the fold paramters before fitting
|
|
110
|
+
# otherwise use the current values to fit
|
|
111
|
+
if reset:
|
|
112
|
+
self.c0 = np.mean(np.arctan(np.deg2rad(self.rotation_angle)))
|
|
113
|
+
self.c1 = 0
|
|
114
|
+
self.c2 = np.max(np.arctan(np.deg2rad(self.rotation_angle)))
|
|
115
|
+
self.w = 1
|
|
116
|
+
if calculate_wavelength:
|
|
117
|
+
self.w = self.estimate_wavelength(svariogram_parameters=svariogram_parameters)
|
|
118
|
+
if wavelength is not None:
|
|
119
|
+
self.w = wavelength
|
|
120
|
+
guess = [self.c0, self.c1, self.c2, self.w]
|
|
121
|
+
return guess
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def params(self):
|
|
125
|
+
return {
|
|
126
|
+
"c0": self.c0,
|
|
127
|
+
"c1": self.c1,
|
|
128
|
+
"c2": self.c2,
|
|
129
|
+
"w": self.w,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@params.setter
|
|
133
|
+
def params(self, params):
|
|
134
|
+
for key in params:
|
|
135
|
+
if key == 'w':
|
|
136
|
+
if params[key] <= 0:
|
|
137
|
+
raise ValueError('wavelength must be greater than 0')
|
|
138
|
+
setattr(self, key, params[key])
|
|
139
|
+
self.c0 = params["c0"]
|
|
140
|
+
self.c1 = params["c1"]
|
|
141
|
+
self.c2 = params["c2"]
|
|
142
|
+
self.w = params["w"]
|
|
143
|
+
|
|
144
|
+
def update_params(self, params: Union[List[float], npt.NDArray[np.float64]]):
|
|
145
|
+
if len(params) != 4:
|
|
146
|
+
raise ValueError('params must have 4 elements')
|
|
147
|
+
self.c0 = params[0]
|
|
148
|
+
self.c1 = params[1]
|
|
149
|
+
self.c2 = params[2]
|
|
150
|
+
self.w = params[3]
|
|
151
|
+
|
|
152
|
+
def calculate_misfit(self, s, rotation_angle):
|
|
153
|
+
return np.tan(np.deg2rad(rotation_angle)) - np.tan(np.deg2rad(self.__call__(s)))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from ._base_fold_rotation_angle import BaseFoldRotationAngleProfile
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from typing import Optional, Callable
|
|
5
|
+
from .....utils import getLogger
|
|
6
|
+
|
|
7
|
+
logger = getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LambdaFoldRotationAngleProfile(BaseFoldRotationAngleProfile):
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
fn: Callable[[np.ndarray], np.ndarray],
|
|
15
|
+
rotation_angle: Optional[npt.NDArray[np.float64]] = None,
|
|
16
|
+
fold_frame_coordinate: Optional[npt.NDArray[np.float64]] = None,
|
|
17
|
+
):
|
|
18
|
+
"""The fold frame function using the lambda profile from Laurent 2016
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
rotation_angle : npt.NDArray[np.float64], optional
|
|
23
|
+
the calculated fold rotation angle from observations in degrees, by default None
|
|
24
|
+
fold_frame_coordinate : npt.NDArray[np.float64], optional
|
|
25
|
+
fold frame coordinate scalar field value, by default None
|
|
26
|
+
lambda_ : float, optional
|
|
27
|
+
lambda parameter, by default 0
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(rotation_angle, fold_frame_coordinate)
|
|
30
|
+
self._function = fn
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def params(self):
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
def update_params(self, params):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def initial_guess(
|
|
40
|
+
self,
|
|
41
|
+
wavelength: float | None = None,
|
|
42
|
+
calculate_wavelength: bool = True,
|
|
43
|
+
svariogram_parameters: dict = {},
|
|
44
|
+
reset: bool = False,
|
|
45
|
+
) -> np.ndarray:
|
|
46
|
+
return np.array([])
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from ._base_fold_rotation_angle import BaseFoldRotationAngleProfile
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from typing import Optional, Union, List
|
|
5
|
+
from .....utils import getLogger
|
|
6
|
+
|
|
7
|
+
logger = getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TrigoFoldRotationAngleProfile(BaseFoldRotationAngleProfile):
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
rotation_angle: Optional[npt.NDArray[np.float64]] = None,
|
|
15
|
+
fold_frame_coordinate: Optional[npt.NDArray[np.float64]] = None,
|
|
16
|
+
origin: float = 0,
|
|
17
|
+
wavelength: float = 0,
|
|
18
|
+
inflectionpointangle: float = 0,
|
|
19
|
+
):
|
|
20
|
+
"""The fold frame function using the trigo profile from Laurent 2016
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
rotation_angle : npt.NDArray[np.float64], optional
|
|
25
|
+
the calculated fold rotation angle from observations in degrees, by default None
|
|
26
|
+
fold_frame_coordinate : npt.NDArray[np.float64], optional
|
|
27
|
+
fold frame coordinate scalar field value, by default None
|
|
28
|
+
origin : float, optional
|
|
29
|
+
phase shift parameter, by default 0
|
|
30
|
+
wavelength : float, optional
|
|
31
|
+
wavelength of the profile, by default 0
|
|
32
|
+
inflectionpointangle : float, optional
|
|
33
|
+
height of the profile, tightness of fold, by default 0
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(rotation_angle, fold_frame_coordinate)
|
|
36
|
+
self._origin = origin
|
|
37
|
+
self._wavelength = wavelength
|
|
38
|
+
self._inflectionpointangle = inflectionpointangle
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def origin(self):
|
|
42
|
+
return self._origin
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def wavelength(self):
|
|
46
|
+
return self._wavelength
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def inflectionpointangle(self):
|
|
50
|
+
return self._inflectionpointangle
|
|
51
|
+
|
|
52
|
+
@origin.setter
|
|
53
|
+
def origin(self, value):
|
|
54
|
+
if np.isfinite(value):
|
|
55
|
+
self.notify_observers()
|
|
56
|
+
|
|
57
|
+
self._origin = value
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError("origin must be a finite number")
|
|
60
|
+
|
|
61
|
+
@wavelength.setter
|
|
62
|
+
def wavelength(self, value):
|
|
63
|
+
if np.isfinite(value):
|
|
64
|
+
self.notify_observers()
|
|
65
|
+
|
|
66
|
+
self._wavelength = value
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError("wavelength must be a finite number")
|
|
69
|
+
|
|
70
|
+
@inflectionpointangle.setter
|
|
71
|
+
def inflectionpointangle(self, value):
|
|
72
|
+
if np.isfinite(value):
|
|
73
|
+
if value < np.deg2rad(-90) or value > np.deg2rad(90):
|
|
74
|
+
logger.error(f"Inflection point angle is {np.rad2deg(value)} degrees")
|
|
75
|
+
raise ValueError("inflectionpointangle must be between 0 and 90")
|
|
76
|
+
self.notify_observers()
|
|
77
|
+
self._inflectionpointangle = value
|
|
78
|
+
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError("inflectionpointangle must be a finite number")
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def params(self):
|
|
84
|
+
return {
|
|
85
|
+
"origin": self.origin,
|
|
86
|
+
"wavelength": self.wavelength,
|
|
87
|
+
"inflectionpointangle": self.inflectionpointangle,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _function(s, origin, wavelength, inflectionpointangle):
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
s
|
|
97
|
+
origin
|
|
98
|
+
wavelength
|
|
99
|
+
inflectionpointangle
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
tan_alpha_delta_half = np.tan(inflectionpointangle)
|
|
106
|
+
tan_alpha_shift = 0
|
|
107
|
+
x = (s - origin) / wavelength
|
|
108
|
+
return tan_alpha_delta_half * np.sin(2 * np.pi * x) + tan_alpha_shift
|
|
109
|
+
|
|
110
|
+
# def __call__(self, fold_frame_coordinate):
|
|
111
|
+
# return np.rad2deg(
|
|
112
|
+
# np.tan(
|
|
113
|
+
# self._function(
|
|
114
|
+
# fold_frame_coordinate, self.origin, self.wavelength, self.inflectionpointangle
|
|
115
|
+
# )
|
|
116
|
+
# )
|
|
117
|
+
# )
|
|
118
|
+
|
|
119
|
+
def calculate_misfit(
|
|
120
|
+
self, rotation_angle: np.ndarray, fold_frame_coordinate: np.ndarray
|
|
121
|
+
) -> np.ndarray:
|
|
122
|
+
return super().calculate_misfit(rotation_angle, fold_frame_coordinate)
|
|
123
|
+
|
|
124
|
+
def update_params(self, params: Union[List, npt.NDArray[np.float64]]) -> None:
|
|
125
|
+
self.origin = params[0]
|
|
126
|
+
self.wavelength = params[1]
|
|
127
|
+
self.inflectionpointangle = params[2]
|
|
128
|
+
|
|
129
|
+
def initial_guess(
|
|
130
|
+
self,
|
|
131
|
+
wavelength: Optional[float] = None,
|
|
132
|
+
calculate_wavelength: bool = True,
|
|
133
|
+
svariogram_parameters: dict = {},
|
|
134
|
+
reset: bool = True,
|
|
135
|
+
):
|
|
136
|
+
# reset the fold paramters before fitting
|
|
137
|
+
# otherwise use the current values to fit
|
|
138
|
+
if reset:
|
|
139
|
+
self.origin = 0
|
|
140
|
+
self.wavelength = 0
|
|
141
|
+
self.inflectionpointangle = np.deg2rad(45) # otherwise there is a numerical error
|
|
142
|
+
if calculate_wavelength:
|
|
143
|
+
self.wavelength = self.estimate_wavelength(svariogram_parameters=svariogram_parameters)
|
|
144
|
+
if wavelength is not None:
|
|
145
|
+
self.wavelength = wavelength
|
|
146
|
+
guess = [
|
|
147
|
+
self.fold_frame_coordinate.mean(),
|
|
148
|
+
self.wavelength,
|
|
149
|
+
np.max(np.arctan(np.deg2rad(self.rotation_angle))),
|
|
150
|
+
]
|
|
151
|
+
return np.array(guess)
|