LoopStructural 1.6.1__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 (129) hide show
  1. LoopStructural/__init__.py +52 -0
  2. LoopStructural/datasets/__init__.py +23 -0
  3. LoopStructural/datasets/_base.py +301 -0
  4. LoopStructural/datasets/_example_models.py +10 -0
  5. LoopStructural/datasets/data/claudius.csv +21049 -0
  6. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  7. LoopStructural/datasets/data/duplex.csv +126 -0
  8. LoopStructural/datasets/data/duplexbb.txt +2 -0
  9. LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
  10. LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  11. LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
  12. LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  13. LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  14. LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
  15. LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
  16. LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
  17. LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
  18. LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
  19. LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
  20. LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
  21. LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
  22. LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
  23. LoopStructural/datasets/data/intrusion.csv +1017 -0
  24. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  25. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  26. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  27. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  28. LoopStructural/datasets/data/refolded_fold.csv +205 -0
  29. LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
  30. LoopStructural/datatypes/__init__.py +4 -0
  31. LoopStructural/datatypes/_bounding_box.py +422 -0
  32. LoopStructural/datatypes/_point.py +166 -0
  33. LoopStructural/datatypes/_structured_grid.py +94 -0
  34. LoopStructural/datatypes/_surface.py +184 -0
  35. LoopStructural/export/exporters.py +554 -0
  36. LoopStructural/export/file_formats.py +15 -0
  37. LoopStructural/export/geoh5.py +100 -0
  38. LoopStructural/export/gocad.py +126 -0
  39. LoopStructural/export/omf_wrapper.py +88 -0
  40. LoopStructural/interpolators/__init__.py +105 -0
  41. LoopStructural/interpolators/_api.py +143 -0
  42. LoopStructural/interpolators/_builders.py +149 -0
  43. LoopStructural/interpolators/_cython/__init__.py +0 -0
  44. LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
  45. LoopStructural/interpolators/_discrete_interpolator.py +692 -0
  46. LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
  47. LoopStructural/interpolators/_geological_interpolator.py +380 -0
  48. LoopStructural/interpolators/_interpolator_factory.py +89 -0
  49. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  50. LoopStructural/interpolators/_operator.py +38 -0
  51. LoopStructural/interpolators/_p1interpolator.py +228 -0
  52. LoopStructural/interpolators/_p2interpolator.py +277 -0
  53. LoopStructural/interpolators/_surfe_wrapper.py +174 -0
  54. LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
  55. LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
  56. LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
  57. LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
  58. LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  59. LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
  60. LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
  61. LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
  62. LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
  63. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
  64. LoopStructural/interpolators/supports/__init__.py +55 -0
  65. LoopStructural/interpolators/supports/_aabb.py +77 -0
  66. LoopStructural/interpolators/supports/_base_support.py +114 -0
  67. LoopStructural/interpolators/supports/_face_table.py +70 -0
  68. LoopStructural/interpolators/supports/_support_factory.py +32 -0
  69. LoopStructural/modelling/__init__.py +29 -0
  70. LoopStructural/modelling/core/__init__.py +0 -0
  71. LoopStructural/modelling/core/geological_model.py +1867 -0
  72. LoopStructural/modelling/features/__init__.py +32 -0
  73. LoopStructural/modelling/features/_analytical_feature.py +79 -0
  74. LoopStructural/modelling/features/_base_geological_feature.py +364 -0
  75. LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
  76. LoopStructural/modelling/features/_geological_feature.py +288 -0
  77. LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
  78. LoopStructural/modelling/features/_region.py +18 -0
  79. LoopStructural/modelling/features/_structural_frame.py +186 -0
  80. LoopStructural/modelling/features/_unconformity_feature.py +83 -0
  81. LoopStructural/modelling/features/builders/__init__.py +5 -0
  82. LoopStructural/modelling/features/builders/_base_builder.py +111 -0
  83. LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
  84. LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
  85. LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
  86. LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
  87. LoopStructural/modelling/features/fault/__init__.py +3 -0
  88. LoopStructural/modelling/features/fault/_fault_function.py +444 -0
  89. LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
  90. LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
  91. LoopStructural/modelling/features/fold/__init__.py +9 -0
  92. LoopStructural/modelling/features/fold/_fold.py +167 -0
  93. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
  94. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
  95. LoopStructural/modelling/features/fold/_foldframe.py +194 -0
  96. LoopStructural/modelling/features/fold/_svariogram.py +188 -0
  97. LoopStructural/modelling/input/__init__.py +2 -0
  98. LoopStructural/modelling/input/fault_network.py +80 -0
  99. LoopStructural/modelling/input/map2loop_processor.py +165 -0
  100. LoopStructural/modelling/input/process_data.py +650 -0
  101. LoopStructural/modelling/input/project_file.py +84 -0
  102. LoopStructural/modelling/intrusions/__init__.py +25 -0
  103. LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
  104. LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
  105. LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
  106. LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
  107. LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
  108. LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
  109. LoopStructural/utils/__init__.py +38 -0
  110. LoopStructural/utils/_surface.py +143 -0
  111. LoopStructural/utils/_transformation.py +76 -0
  112. LoopStructural/utils/config.py +18 -0
  113. LoopStructural/utils/dtm_creator.py +17 -0
  114. LoopStructural/utils/exceptions.py +31 -0
  115. LoopStructural/utils/helper.py +292 -0
  116. LoopStructural/utils/json_encoder.py +18 -0
  117. LoopStructural/utils/linalg.py +8 -0
  118. LoopStructural/utils/logging.py +79 -0
  119. LoopStructural/utils/maths.py +245 -0
  120. LoopStructural/utils/regions.py +103 -0
  121. LoopStructural/utils/typing.py +7 -0
  122. LoopStructural/utils/utils.py +68 -0
  123. LoopStructural/version.py +1 -0
  124. LoopStructural/visualisation/__init__.py +11 -0
  125. LoopStructural-1.6.1.dist-info/LICENSE +21 -0
  126. LoopStructural-1.6.1.dist-info/METADATA +81 -0
  127. LoopStructural-1.6.1.dist-info/RECORD +129 -0
  128. LoopStructural-1.6.1.dist-info/WHEEL +5 -0
  129. LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,82 @@
1
+ from ....modelling.features import BaseFeature, StructuralFrame
2
+ from typing import Optional
3
+ from ....utils import getLogger
4
+
5
+ logger = getLogger(__name__)
6
+
7
+
8
+ class FaultDisplacementFeature(BaseFeature):
9
+ """ """
10
+
11
+ def __init__(
12
+ self,
13
+ fault_frame,
14
+ displacement,
15
+ name="fault_displacement",
16
+ model=None,
17
+ faults=[],
18
+ regions=[],
19
+ builder=None,
20
+ ):
21
+ """
22
+ Geological feature representing the fault displacement
23
+
24
+ Parameters
25
+ ----------
26
+ fault_frame - geometry of the fault
27
+ displacement - function defining fault displacement
28
+ """
29
+ BaseFeature.__init__(self, f"{name}_displacement", model, faults, regions, builder)
30
+ self.fault_frame = StructuralFrame(
31
+ f"{fault_frame.name}_displacementframe",
32
+ [fault_frame[0].copy(), fault_frame[1].copy(), fault_frame[2].copy()],
33
+ )
34
+ self.displacement = displacement
35
+
36
+ def evaluate_value(self, location):
37
+ """
38
+ Return the value of the fault displacement
39
+
40
+ Parameters
41
+ ----------
42
+ location
43
+
44
+ Returns
45
+ -------
46
+
47
+ """
48
+ fault_suface = self.fault_frame.features[0].evaluate_value(location)
49
+ fault_displacement = self.fault_frame.features[1].evaluate_value(location)
50
+ fault_strike = self.fault_frame.features[2].evaluate_value(location)
51
+ d = self.displacement(fault_suface, fault_displacement, fault_strike)
52
+ return d
53
+
54
+ def evaluate_gradient(self, location):
55
+ """
56
+ get the scaled displacement
57
+
58
+ Parameters
59
+ ----------
60
+ location
61
+
62
+ Returns
63
+ -------
64
+
65
+ """
66
+ fault_suface = self.fault_frame.features[0].evaluate_value(location)
67
+ fault_displacement = self.fault_frame.features[1].evaluate_value(location)
68
+ fault_strike = self.fault_frame.features[2].evaluate_value(location)
69
+ d = self.displacement(fault_suface, fault_displacement, fault_strike)
70
+ return d
71
+
72
+ def evaluate_on_surface(self, location):
73
+ """
74
+ TODO what is this for?
75
+ """
76
+ fault_displacement = self.fault_frame.features[1].evaluate_value(location)
77
+ fault_strike = self.fault_frame.features[2].evaluate_value(location)
78
+ d = self.displacement.evaluate(fault_displacement, fault_strike)
79
+ return d
80
+
81
+ def get_data(self, value_map: Optional[dict] = None):
82
+ pass
@@ -0,0 +1,505 @@
1
+ from ....modelling.features.fault._fault_function_feature import (
2
+ FaultDisplacementFeature,
3
+ )
4
+ from ....modelling.features import FeatureType
5
+ from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D
6
+ from ....utils import getLogger, NegativeRegion, PositiveRegion
7
+ from ....modelling.features import StructuralFrame
8
+
9
+ from concurrent.futures import ThreadPoolExecutor
10
+ import numpy as np
11
+
12
+ logger = getLogger(__name__)
13
+
14
+ use_threads = False # there is a bug with threading causing the interpolation dictionary to change size during iterations
15
+ # for now 19/4/24 LG is turning threading off. This will slow down evaluation of models a bit.
16
+
17
+
18
+ class FaultSegment(StructuralFrame):
19
+ """
20
+ Class for representing a slip event of a fault
21
+ """
22
+
23
+ def __init__(
24
+ self, features, name, faultfunction=None, steps=10, displacement=1.0, fold=None, model=None
25
+ ):
26
+ """
27
+ A slip event of a fault
28
+
29
+ Parameters
30
+ ----------
31
+ faultframe : FaultFrame
32
+ the fault frame defining the faut geometry
33
+ faultfunction : function/lambda function
34
+ optional displacement function for spatially variable fault displacement
35
+ steps : int
36
+ how many integration steps for faults
37
+ kwargs
38
+ """
39
+ StructuralFrame.__init__(self, features, name, fold, model)
40
+ self.type = FeatureType.FAULT
41
+ self.displacement = displacement
42
+ self._faultfunction = BaseFault.fault_displacement
43
+ self.steps = steps
44
+ self.regions = []
45
+ self.faults_enabled = True
46
+
47
+ self.builder = None
48
+ self.splay = {}
49
+ self.abut = {}
50
+ self.fault_offset = 0.0
51
+
52
+ @property
53
+ def faultfunction(self):
54
+ return self._faultfunction
55
+
56
+ @faultfunction.setter
57
+ def faultfunction(self, value):
58
+ if callable(value):
59
+ self._faultfunction = value
60
+ elif isinstance(value, str) and value == "BaseFault":
61
+ self._faultfunction = BaseFault.fault_displacement
62
+ elif isinstance(value, str) and value == "BaseFault3D":
63
+ self._faultfunction = BaseFault3D.fault_displacement
64
+ else:
65
+ raise ValueError("Fault function must be a function or BaseFault")
66
+
67
+ @property
68
+ def fault_normal_vector(self):
69
+ if self.builder is None:
70
+ raise ValueError("Fault builder not set")
71
+ return self.builder.fault_normal_vector
72
+
73
+ @property
74
+ def fault_slip_vector(self):
75
+ if self.builder is None:
76
+ raise ValueError("Fault builder not set")
77
+ return self.builder.fault_slip_vector
78
+
79
+ @property
80
+ def fault_strike_vector(self):
81
+ if self.builder is None:
82
+ raise ValueError("Fault builder not set")
83
+ return self.builder.fault_strike_vector
84
+
85
+ @property
86
+ def fault_minor_axis(self):
87
+ if self.builder is None:
88
+ raise ValueError("Fault builder not set")
89
+ return self.builder.fault_minor_axis
90
+
91
+ @property
92
+ def fault_major_axis(self):
93
+ if self.builder is None:
94
+ raise ValueError("Fault builder not set")
95
+ return self.builder.fault_major_axis
96
+
97
+ @property
98
+ def fault_intermediate_axis(self):
99
+ if self.builder is None:
100
+ raise ValueError("Fault builder not set")
101
+ return self.builder.fault_intermediate_axis
102
+
103
+ @property
104
+ def fault_centre(self):
105
+ if self.builder is None:
106
+ raise ValueError("Fault builder not set")
107
+ return self.builder.fault_centre
108
+
109
+ @property
110
+ def displacementfeature(self):
111
+ return FaultDisplacementFeature(self, self.faultfunction, name=self.name, model=self.model)
112
+
113
+ def set_fault_offset(self, offset: float):
114
+ self.fault_offset = offset
115
+
116
+ def set_model(self, model):
117
+ """
118
+ Link a geological model to the feature
119
+
120
+ Parameters
121
+ ----------
122
+ model - GeologicalModel
123
+
124
+ Returns
125
+ -------
126
+
127
+ """
128
+ self.model = model
129
+
130
+ def set_displacement(self, displacement, scale=True):
131
+ """
132
+ Set the fault displacement to a new value
133
+
134
+ Parameters
135
+ ----------
136
+ displacement - double
137
+ scale - boolean
138
+
139
+ Returns
140
+ -------
141
+
142
+ """
143
+ if scale and self.model is not None:
144
+ self.displacement = displacement / self.model.scale_factor
145
+ elif not scale:
146
+ self.displacement = displacement
147
+ else:
148
+ logger.warning("Displacement not updated")
149
+
150
+ def evaluate(self, locations):
151
+ """
152
+ Evaluate which side of fault
153
+
154
+ Parameters
155
+ ----------
156
+ locations numpy array
157
+ location to evaluate
158
+
159
+ Returns
160
+ -------
161
+ boolean array true if on hanging wall, false if on footwall
162
+
163
+ """
164
+ v = self.__getitem__(0).evaluate_value(locations)
165
+ v[~np.isnan(v)] = v[~np.isnan(v)] > 0
166
+ v[np.isnan(v)] = 0
167
+ return v.astype(bool)
168
+
169
+ def inside_volume(self, locations, threshold=0.001):
170
+ # v = self.faultframe.evaluate_value(locations)
171
+ v = self.evaluate_displacement(locations) / self.displacement
172
+ v[np.isnan(v)] = 0
173
+ return np.abs(v) > threshold
174
+ # return np.all(np.logical_and(v > -1,v<1),axis=1)
175
+
176
+ def evaluate_value(self, locations):
177
+ """
178
+ Return the value of the fault surface scalar field
179
+
180
+ Parameters
181
+ ----------
182
+ locations - numpy array
183
+ location to evaluate scalar field
184
+
185
+ Returns
186
+ -------
187
+
188
+ """
189
+ # v = np.zeros(locations.shape[0])
190
+ # v[:] = np.nan
191
+ # mask =
192
+ # mask = np.zeros(locations.shape[0]).astype(bool)
193
+ # mask[:] = True
194
+ # # check regions
195
+ # for r in self.regions:
196
+ # try:
197
+ # mask = np.logical_and(mask, r(locations))
198
+ # except:
199
+ # logger.error("nan slicing")
200
+ # v[mask] = self.__getitem__(0).evaluate_value(locations[mask, :])
201
+ return self.__getitem__(0).evaluate_value(locations)
202
+
203
+ def mean(self):
204
+ return self.__getitem__(0).mean()
205
+
206
+ def max(self):
207
+ return self.__getitem__(0).max()
208
+
209
+ def min(self):
210
+ return self.__getitem__(0).min()
211
+
212
+ def evaluate_gradient(self, locations):
213
+ """
214
+ Return the fault slip direction at the location
215
+
216
+ Parameters
217
+ ----------
218
+ locations - numpy array Nx3
219
+
220
+
221
+ Returns
222
+ -------
223
+
224
+ """
225
+ v = np.zeros(locations.shape)
226
+ v[:, :] = np.nan
227
+ mask = np.zeros(locations.shape[0]).astype(bool)
228
+ mask[:] = True
229
+ # check regions
230
+ for r in self.regions:
231
+ try:
232
+ mask = np.logical_and(mask, r(locations))
233
+ except:
234
+ logger.error("nan slicing ")
235
+ # need to scale with fault displacement
236
+ v[mask, :] = self.__getitem__(1).evaluate_gradient(locations[mask, :])
237
+ scale = self.displacementfeature.evaluate_value(locations[mask, :])
238
+ v[mask, :] *= scale[:, None]
239
+ return v
240
+
241
+ def evaluate_displacement(self, points):
242
+ newp = np.copy(points).astype(float)
243
+ # evaluate fault function for all
244
+ # points then define mask for only points affected by fault
245
+ gx = None
246
+ gy = None
247
+ gz = None
248
+ if use_threads:
249
+ with ThreadPoolExecutor(max_workers=8) as executor:
250
+ # all of these operations should be independent so
251
+ # just run as different threads
252
+ gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp)
253
+ gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp)
254
+ gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp)
255
+ gx = gx_future.result()
256
+ gy = gy_future.result()
257
+ gz = gz_future.result()
258
+ else:
259
+ gx = self.__getitem__(0).evaluate_value(newp)
260
+ gy = self.__getitem__(1).evaluate_value(newp)
261
+ gz = self.__getitem__(2).evaluate_value(newp)
262
+ d = np.zeros(gx.shape)
263
+ mask = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
264
+ mask = np.logical_and(mask, ~np.isnan(gz))
265
+ d[~mask] = 0
266
+ gx_mask = np.zeros_like(mask, dtype=bool)
267
+ gx_mask[mask] = gx[mask] > 0
268
+ d[gx_mask] = 1.0
269
+ if self.faultfunction is not None:
270
+ d[mask] = self.faultfunction(gx[mask] + self.fault_offset, gy[mask], gz[mask])
271
+ return d * self.displacement
272
+
273
+ def apply_to_points(self, points, reverse=False):
274
+ """
275
+ Unfault the array of points
276
+
277
+ Parameters
278
+ ----------
279
+ points - numpy array Nx3
280
+
281
+ Returns
282
+ -------
283
+
284
+ """
285
+ steps = self.steps
286
+ newp = np.copy(points).astype(float)
287
+ # evaluate fault function for all points
288
+ # then define mask for only points affected by fault
289
+ gx = None
290
+ gy = None
291
+ gz = None
292
+ if use_threads:
293
+ with ThreadPoolExecutor(max_workers=8) as executor:
294
+ # all of these operations should be
295
+ # independent so just run as different threads
296
+ gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp)
297
+ gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp)
298
+ gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp)
299
+ gx = gx_future.result()
300
+ gy = gy_future.result()
301
+ gz = gz_future.result()
302
+ else:
303
+ gx = self.__getitem__(0).evaluate_value(newp)
304
+ gy = self.__getitem__(1).evaluate_value(newp)
305
+ gz = self.__getitem__(2).evaluate_value(newp)
306
+ d = np.zeros(gx.shape)
307
+ mask = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
308
+ mask = np.logical_and(mask, ~np.isnan(gz))
309
+ d[~mask] = 0
310
+ gx_mask = np.zeros_like(mask, dtype=bool)
311
+ gx_mask[mask] = gx[mask] > 0
312
+ d[gx_mask] = 1.0
313
+ if self.faultfunction is not None:
314
+ d[mask] = self.faultfunction(gx[mask], gy[mask], gz[mask])
315
+ mask = np.abs(d) > 0.0
316
+
317
+ d *= self.displacement
318
+ if reverse:
319
+ d *= -1.0
320
+ # calculate the fault frame for the evaluation points
321
+ for _i in range(steps):
322
+ gx = None
323
+ gy = None
324
+ gz = None
325
+ g = None
326
+ if use_threads:
327
+ with ThreadPoolExecutor(max_workers=8) as executor:
328
+ # all of these operations should be
329
+ # independent so just run as different threads
330
+ gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp[mask, :])
331
+ g_future = executor.submit(self.__getitem__(1).evaluate_gradient, newp[mask, :])
332
+ gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp[mask, :])
333
+ gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp[mask, :])
334
+ gx = gx_future.result()
335
+ g = g_future.result()
336
+ gy = gy_future.result()
337
+ gz = gz_future.result()
338
+ else:
339
+ gx = self.__getitem__(0).evaluate_value(newp[mask, :])
340
+ gy = self.__getitem__(1).evaluate_value(newp[mask, :])
341
+ gz = self.__getitem__(2).evaluate_value(newp[mask, :])
342
+ g = self.__getitem__(1).evaluate_gradient(newp[mask, :], ignore_regions=True)
343
+ # # get the fault frame val/grad for the points
344
+ # determine displacement magnitude, for constant displacement
345
+ # hanging wall should be > 0
346
+ d = np.zeros(gx.shape)
347
+ mask2 = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
348
+ mask2 = np.logical_and(mask2, ~np.isnan(gz))
349
+ d[~mask2] = 0
350
+ gx_mask2 = np.zeros_like(mask2, dtype=bool)
351
+ gx_mask2[mask2] = gx[mask2] > 0
352
+ # d[~np.isnan(gx)][gx[~np.isnan(gx)]>0] = 1
353
+ d[gx_mask2] = 1.0
354
+ # d[mask2][gx[mask2] < 0] = 0.
355
+ # d[gx < 0] = 0.
356
+ if self.faultfunction is not None:
357
+ d[mask2] = self.faultfunction(gx[mask2], gy[mask2], gz[mask2])
358
+ d *= self.displacement
359
+ # normalise when length is >0
360
+ g_mag = np.zeros(g.shape[0])
361
+ g_mag[mask2] = np.linalg.norm(g[mask2], axis=1)
362
+ # g_mag = np.linalg.norm(g[mask2], axis=1)
363
+ g[g_mag > 0.0] /= g_mag[g_mag > 0, None]
364
+ # multiply displacement vector by the displacement magnitude for
365
+ # step
366
+
367
+ g *= (1.0 / steps) * d[:, None]
368
+ # newp[mask, :].copy()
369
+ # apply displacement
370
+ newp[mask, :] += g
371
+
372
+ return newp
373
+
374
+ def apply_to_vectors(self, vector: np.ndarray, scale_parameter: float = 1.0) -> np.ndarray:
375
+ """Rotates the vector through the fault displacement field
376
+
377
+ Parameters
378
+ ----------
379
+ vector : np.ndarray
380
+ Nx6 array of vectors x,y,z,vx,vy,vz
381
+ """
382
+
383
+ # define a regular tetrahedron as a mask to apply to every vector observation.
384
+ # the regular tetrahedron is located at the location of the vector datapoint
385
+ # and the corner values of the tetrahedron are defined by setting the barycenter
386
+ # of the tetrahedron as 0 and then using the vector to calculate the values of the
387
+ # corner assuming the tetra is a P1 tetra with a linear gradient.
388
+ # a scaling parameter is used to determine the size of the tetrahedron
389
+ # the nodes of the tetrahedron are then restored by the fault and then gradient is
390
+ # recalculated using the updated node positions but the original corner values
391
+
392
+ regular_tetrahedron = np.array(
393
+ [
394
+ [np.sqrt(8 / 9), 0, -1 / 3],
395
+ [-np.sqrt(2 / 9), np.sqrt(2 / 3), -1 / 3],
396
+ [-np.sqrt(2 / 9), -np.sqrt(2 / 3), -1 / 3],
397
+ [0, 0, 1],
398
+ ]
399
+ )
400
+ regular_tetrahedron *= scale_parameter
401
+ xyz = vector[:, :3]
402
+ tetrahedron = np.zeros((xyz.shape[0], 4, 3))
403
+ tetrahedron[:] = xyz[:, None, :]
404
+ tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
405
+
406
+ vectors = vector[:, 3:]
407
+ corners = np.einsum('ikj,ij->ik', tetrahedron - xyz[:, None, :], vectors)
408
+ tetrahedron = tetrahedron.reshape(-1, 3)
409
+ tetrahedron = self.apply_to_points(tetrahedron)
410
+ tetrahedron = tetrahedron.reshape(-1, 4, 3)
411
+ m = np.array(
412
+ [
413
+ [
414
+ (tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
415
+ (tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
416
+ (tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
417
+ ],
418
+ [
419
+ (tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
420
+ (tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
421
+ (tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
422
+ ],
423
+ [
424
+ (tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
425
+ (tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
426
+ (tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
427
+ ],
428
+ ]
429
+ )
430
+ I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
431
+ m = np.swapaxes(m, 0, 2)
432
+ element_gradients = np.linalg.inv(m)
433
+
434
+ element_gradients = element_gradients.swapaxes(1, 2)
435
+ element_gradients = element_gradients @ I
436
+ v = np.sum(element_gradients * corners[:, None, :], axis=2)
437
+ return v
438
+
439
+ def add_abutting_fault(self, abutting_fault_feature, positive=None):
440
+
441
+ # check whether the fault is on the hanging wall or footwall of abutting fault
442
+ abutting_region = None
443
+ if positive is None:
444
+ pts = (
445
+ self.__getitem__(0).builder.data[["X", "Y", "Z"]].to_numpy()
446
+ ) # get_value_constraints()
447
+ abut_value = np.nanmedian(abutting_fault_feature.evaluate_value(pts))
448
+ positive = abut_value > 0
449
+ # we want to crop the fault by the abutting
450
+ # fault so create a positive/neg region and
451
+ # include the fault centre and normal vector to help
452
+ # outside of the fault interpolation support
453
+ if positive:
454
+ abutting_region = PositiveRegion(
455
+ abutting_fault_feature,
456
+ vector=abutting_fault_feature.fault_normal_vector,
457
+ point=abutting_fault_feature.fault_centre,
458
+ )
459
+ if not positive:
460
+ abutting_region = NegativeRegion(
461
+ abutting_fault_feature,
462
+ vector=abutting_fault_feature.fault_normal_vector,
463
+ point=abutting_fault_feature.fault_centre,
464
+ )
465
+ self.abut[abutting_fault_feature.name] = abutting_region
466
+ self.__getitem__(0).add_region(abutting_region)
467
+
468
+ def save(self, filename, scalar_field=True, slip_vector=True, surface=True):
469
+ """
470
+ Save the fault to a file
471
+
472
+ Parameters
473
+ ----------
474
+ filename - str
475
+ filename to save to
476
+
477
+ Returns
478
+ -------
479
+
480
+ """
481
+ filename = str(filename)
482
+ ext = filename.split(".")[-1]
483
+ info = ''
484
+ if scalar_field:
485
+ if ext == '.geoh5':
486
+ info = ''
487
+ else:
488
+ info = '_scalar_field'
489
+ a = self.features[0].scalar_field()
490
+ a.merge(self.features[1].scalar_field())
491
+ a.merge(self.features[2].scalar_field())
492
+ a.merge(self.displacementfeature.scalar_field())
493
+ a.save(f'{filename}{info}.{ext}')
494
+ if slip_vector:
495
+ if ext == '.geoh5':
496
+ info = ''
497
+ else:
498
+ info = '_slip_vector'
499
+ self.vector_field().save(f'{filename}{info}.{ext}')
500
+ if surface:
501
+ if ext == '.geoh5':
502
+ info = ''
503
+ else:
504
+ info = '_surface'
505
+ self.surfaces([0])[0].save(f'{filename}{info}.{ext}')
@@ -0,0 +1,9 @@
1
+ """
2
+
3
+ """
4
+
5
+ from ._fold import FoldEvent
6
+ from ._svariogram import SVariogram
7
+ from ._fold_rotation_angle_feature import FoldRotationAngleFeature, fourier_series
8
+ from ._foldframe import FoldFrame
9
+ from ._fold_rotation_angle import FoldRotationAngle