LoopStructural 1.6.2__py3-none-any.whl → 1.6.3__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.

Files changed (56) hide show
  1. LoopStructural/datatypes/_bounding_box.py +19 -4
  2. LoopStructural/datatypes/_point.py +31 -0
  3. LoopStructural/datatypes/_structured_grid.py +17 -0
  4. LoopStructural/datatypes/_surface.py +17 -0
  5. LoopStructural/export/omf_wrapper.py +49 -21
  6. LoopStructural/interpolators/__init__.py +13 -0
  7. LoopStructural/interpolators/_api.py +81 -13
  8. LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
  9. LoopStructural/interpolators/_discrete_interpolator.py +100 -53
  10. LoopStructural/interpolators/_finite_difference_interpolator.py +68 -78
  11. LoopStructural/interpolators/_geological_interpolator.py +27 -10
  12. LoopStructural/interpolators/_p1interpolator.py +3 -3
  13. LoopStructural/interpolators/_surfe_wrapper.py +42 -12
  14. LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
  15. LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
  16. LoopStructural/interpolators/supports/_3d_base_structured.py +24 -7
  17. LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -9
  18. LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
  19. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
  20. LoopStructural/interpolators/supports/__init__.py +7 -0
  21. LoopStructural/interpolators/supports/_base_support.py +7 -0
  22. LoopStructural/modelling/__init__.py +1 -1
  23. LoopStructural/modelling/core/geological_model.py +0 -2
  24. LoopStructural/modelling/features/_analytical_feature.py +25 -16
  25. LoopStructural/modelling/features/_geological_feature.py +47 -11
  26. LoopStructural/modelling/features/builders/_base_builder.py +8 -0
  27. LoopStructural/modelling/features/builders/_folded_feature_builder.py +45 -14
  28. LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
  29. LoopStructural/modelling/features/builders/_structural_frame_builder.py +5 -0
  30. LoopStructural/modelling/features/fault/__init__.py +1 -1
  31. LoopStructural/modelling/features/fault/_fault_function.py +19 -1
  32. LoopStructural/modelling/features/fault/_fault_segment.py +15 -49
  33. LoopStructural/modelling/features/fold/__init__.py +1 -2
  34. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
  35. LoopStructural/modelling/features/fold/_foldframe.py +4 -4
  36. LoopStructural/modelling/features/fold/_svariogram.py +81 -46
  37. LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
  38. LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
  39. LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
  40. LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
  41. LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
  42. LoopStructural/modelling/input/process_data.py +6 -0
  43. LoopStructural/modelling/input/project_file.py +24 -3
  44. LoopStructural/utils/_surface.py +5 -2
  45. LoopStructural/utils/colours.py +26 -0
  46. LoopStructural/utils/features.py +5 -0
  47. LoopStructural/utils/maths.py +51 -0
  48. LoopStructural/version.py +1 -1
  49. LoopStructural-1.6.3.dist-info/METADATA +146 -0
  50. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.dist-info}/RECORD +53 -48
  51. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.dist-info}/WHEEL +1 -1
  52. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  53. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
  54. LoopStructural-1.6.2.dist-info/METADATA +0 -81
  55. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.dist-info}/LICENSE +0 -0
  56. {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.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)
@@ -441,6 +441,12 @@ class ProcessInputData:
441
441
  for g in reversed(sg):
442
442
  if g not in self.thicknesses:
443
443
  logger.warning(f"No thicknesses for {g}")
444
+ stratigraphic_value[g] = np.nan
445
+ if self.thicknesses[g] <= 0:
446
+ logger.error(
447
+ f"Thickness for {g} is less than or equal to 0\n Update the thickness value for {g} before continuing"
448
+ )
449
+
444
450
  stratigraphic_value[g] = np.nan
445
451
  else:
446
452
  stratigraphic_value[g] = value
@@ -22,7 +22,8 @@ class LoopProjectfileProcessor(ProcessInputData):
22
22
  orientations = self.projectfile.stratigraphyOrientations
23
23
  fault_orientations = self.projectfile.faultOrientations
24
24
  fault_locations = self.projectfile.faultLocations
25
-
25
+ fault_relationships = self.projectfile["eventRelationships"]
26
+ faultLog = self.projectfile.faultLog.set_index("eventId")
26
27
  orientations.rename(columns=column_map, inplace=True)
27
28
  contacts.rename(columns=column_map, inplace=True)
28
29
  fault_locations.rename(columns=column_map, inplace=True)
@@ -63,6 +64,26 @@ class LoopProjectfileProcessor(ProcessInputData):
63
64
  ],
64
65
  )
65
66
  )
67
+
68
+ for i in fault_relationships.index:
69
+ fault_relationships.loc[i, "Fault1"] = faultLog.loc[
70
+ fault_relationships.loc[i, "eventId1"], "name"
71
+ ]
72
+ fault_relationships.loc[i, "Fault2"] = faultLog.loc[
73
+ fault_relationships.loc[i, "eventId2"], "name"
74
+ ]
75
+ fault_edges = []
76
+ fault_edge_properties = []
77
+ for i in fault_relationships.index:
78
+ fault_edges.append(
79
+ (fault_relationships.loc[i, "Fault1"], fault_relationships.loc[i, "Fault2"])
80
+ )
81
+ fault_edge_properties.append(
82
+ {
83
+ "type": fault_relationships.loc[i, "type"],
84
+ "angle": fault_relationships.loc[i, "angle"],
85
+ }
86
+ )
66
87
  super().__init__(
67
88
  contacts=contacts,
68
89
  contact_orientations=orientations,
@@ -73,12 +94,12 @@ class LoopProjectfileProcessor(ProcessInputData):
73
94
  fault_orientations=fault_orientations,
74
95
  fault_locations=fault_locations,
75
96
  fault_properties=fault_properties,
76
- fault_edges=[], # list(fault_graph.edges),
97
+ fault_edges=fault_edges, # list(fault_graph.edges),
77
98
  colours=colours,
78
99
  fault_stratigraphy=None,
79
100
  intrusions=None,
80
101
  use_thickness=use_thickness,
81
102
  origin=self.projectfile.origin,
82
103
  maximum=self.projectfile.maximum,
83
- # fault_edge_properties=fault_edge_properties
104
+ fault_edge_properties=fault_edge_properties,
84
105
  )
@@ -96,9 +96,12 @@ class LoopIsosurfacer:
96
96
  "Number of isosurfaces must be greater than 1. Either use a positive integer or provide a list or float for a specific isovalue."
97
97
  )
98
98
  elif isinstance(values, int):
99
+ var = np.nanmax(all_values) - np.nanmin(all_values)
100
+ # buffer slices by 5% to make sure that we don't get isosurface does't exist issues
101
+ buffer = var * 0.05
99
102
  isovalues = np.linspace(
100
- np.nanmin(all_values) + np.finfo(float).eps,
101
- np.nanmax(all_values) - np.finfo(float).eps,
103
+ np.nanmin(all_values) + buffer,
104
+ np.nanmax(all_values) - buffer,
102
105
  values,
103
106
  )
104
107
  logger.info(f'Isosurfacing at values: {isovalues}')