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,32 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class FeatureType(IntEnum):
5
+ """ """
6
+
7
+ BASE = 0
8
+ INTERPOLATED = 1
9
+ STRUCTURALFRAME = 2
10
+ REGION = 3
11
+ FOLDED = 4
12
+ ANALYTICAL = 5
13
+ LAMBDA = 6
14
+ UNCONFORMITY = 7
15
+ INTRUSION = 8
16
+ FAULT = 9
17
+ DOMAINFAULT = 10
18
+ INACTIVEFAULT = 11
19
+ ONLAPUNCONFORMITY = 12
20
+
21
+
22
+ # from .builders._geological_feature_builder import GeologicalFeatureBuilder
23
+ from ._base_geological_feature import BaseFeature
24
+ from ._geological_feature import GeologicalFeature
25
+ from ._lambda_geological_feature import LambdaGeologicalFeature
26
+
27
+ # from .builders._geological_feature_builder import GeologicalFeatureBuilder
28
+ from ._structural_frame import StructuralFrame
29
+ from ._cross_product_geological_feature import CrossProductGeologicalFeature
30
+
31
+ from ._unconformity_feature import UnconformityFeature
32
+ from ._analytical_feature import AnalyticalGeologicalFeature
@@ -0,0 +1,79 @@
1
+ import numpy as np
2
+ from ...modelling.features import BaseFeature
3
+ from ...utils import getLogger
4
+ from ...modelling.features import FeatureType
5
+ from typing import Optional
6
+
7
+ logger = getLogger(__name__)
8
+
9
+
10
+ class AnalyticalGeologicalFeature(BaseFeature):
11
+ """
12
+ Geological feature is class that is used to represent a geometrical element in a geological
13
+ model. For example foliations, fault planes, fold rotation angles etc.
14
+
15
+ Attributes
16
+ ----------
17
+ name : string
18
+ should be a unique name for the geological feature
19
+ support : a ScalarField
20
+ holds the property values for the feature and links to the
21
+ support geometry
22
+ data : list
23
+ list containing geological data
24
+ region : list
25
+ list of boolean functions defining whether the feature is
26
+ active
27
+ faults : list
28
+ list of FaultSegments that affect this feature
29
+ """
30
+
31
+ def __init__(self, name, vector, origin, regions=[], faults=[], model=None, builder=None):
32
+ BaseFeature.__init__(self, name, model, faults, regions, builder)
33
+ self.vector = np.array(vector, dtype=float)
34
+ self.origin = np.array(origin, dtype=float)
35
+ self.type = FeatureType.ANALYTICAL
36
+
37
+ def to_json(self):
38
+ """
39
+ Returns a json representation of the geological feature
40
+
41
+ Returns
42
+ -------
43
+ json : dict
44
+ json representation of the geological feature
45
+ """
46
+ json = super().to_json()
47
+ json["vector"] = self.vector.tolist()
48
+ json["origin"] = self.origin.tolist()
49
+ return json
50
+
51
+ def evaluate_value(self, xyz, ignore_regions=False):
52
+ xyz = np.array(xyz)
53
+ if len(xyz.shape) == 1:
54
+ xyz = xyz[None, :]
55
+ if len(xyz.shape) != 2:
56
+ raise ValueError("xyz must be a 1D or 2D array")
57
+ xyz2 = np.zeros(xyz.shape)
58
+ xyz2[:] = xyz[:]
59
+ for f in self.faults:
60
+ xyz2[:] = f.apply_to_points(xyz)
61
+ if self.model is not None:
62
+ xyz2[:] = self.model.rescale(xyz2, inplace=False)
63
+ xyz2[:] = xyz2 - self.origin
64
+ normal = self.vector / np.linalg.norm(self.vector)
65
+ distance = normal[0] * xyz2[:, 0] + normal[1] * xyz2[:, 1] + normal[2] * xyz2[:, 2]
66
+ return distance / np.linalg.norm(self.vector)
67
+
68
+ def evaluate_gradient(self, xyz, ignore_regions=False):
69
+ xyz = np.array(xyz)
70
+ if len(xyz.shape) == 1:
71
+ xyz = xyz[None, :]
72
+ if len(xyz.shape) != 2:
73
+ raise ValueError("xyz must be a 1D or 2D array")
74
+ v = np.zeros(xyz.shape)
75
+ v[:, :] = self.vector[None, :]
76
+ return v
77
+
78
+ def get_data(self, value_map: Optional[dict] = None):
79
+ return
@@ -0,0 +1,364 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from typing import Union, List, Optional
5
+ from LoopStructural.modelling.features import FeatureType
6
+ from LoopStructural.utils import getLogger
7
+ from LoopStructural.utils.typing import NumericInput
8
+ from LoopStructural.utils import LoopIsosurfacer, surface_list
9
+ from LoopStructural.datatypes import VectorPoints
10
+
11
+ import numpy as np
12
+
13
+ logger = getLogger(__name__)
14
+
15
+
16
+ class BaseFeature(metaclass=ABCMeta):
17
+ """
18
+ Base class for geological features.
19
+ """
20
+
21
+ def __init__(self, name: str, model=None, faults: list = [], regions: list = [], builder=None):
22
+ """Base geological feature, this is a virtual class and should not be
23
+ used directly. Inheret from this to implement a new type of geological
24
+ feature or use one of the exisitng implementations
25
+
26
+ Parameters
27
+ ----------
28
+ name :
29
+ Name of the geological feature to add
30
+ model : GeologicalModel, optional
31
+ the model the feature is associated with, by default None
32
+ faults : list, optional
33
+ any faults that fault this feature, by default []
34
+ regions : list, optional
35
+ any regions that affect this feature, by default []
36
+ builder : GeologicalFeatureBuilder, optional
37
+ the builder of the feature, by default None
38
+ """
39
+ self.name = name
40
+ self.type = FeatureType.BASE
41
+ self.regions = regions
42
+ self._faults = []
43
+ if faults:
44
+ self.faults = faults
45
+ self._model = model
46
+ self.builder = builder
47
+ self.faults_enabled = True
48
+ self._min = None
49
+ self._max = None
50
+
51
+ @property
52
+ def faults(self):
53
+ return self._faults
54
+
55
+ @faults.setter
56
+ def faults(self, faults: list):
57
+ _faults = []
58
+ try:
59
+ for f in faults:
60
+ if not issubclass(type(f), BaseFeature):
61
+ raise TypeError("Faults must be a list of BaseFeature")
62
+ _faults.append(f)
63
+ except TypeError:
64
+ logger.error(
65
+ f'Faults must be a list of BaseFeature \n Trying to set using {type(faults)}'
66
+ )
67
+ raise TypeError("Faults must be a list of BaseFeature")
68
+
69
+ self._faults = _faults
70
+
71
+ def to_json(self):
72
+ """
73
+ Returns a json representation of the geological feature
74
+
75
+ Returns
76
+ -------
77
+ json : dict
78
+ json representation of the geological feature
79
+ """
80
+ json = {}
81
+ json["name"] = self.name
82
+ json["regions"] = [r.to_json() for r in self.regions]
83
+ json["faults"] = [f.name for f in self.faults]
84
+ json["type"] = self.type
85
+ return json
86
+
87
+ def __str__(self):
88
+ _str = "-----------------------------------------------------\n"
89
+ _str += f"{self.name} {self.type} \n"
90
+ _str += "-----------------------------------------------------\n"
91
+ _str += f"\t{len(self.regions)} regions\n"
92
+ for r in self.regions:
93
+ _str += f"\t \t{r.__str__}\n"
94
+ _str += f"\t{len(self.faults)} faults.\n"
95
+ _str += f"\tFault enabled {self.faults_enabled}\n"
96
+
97
+ for f in self.faults:
98
+ _str += f"\t \t{f.__str__}\n"
99
+ return _str
100
+
101
+ def __repr__(self):
102
+ return self.__str__()
103
+
104
+ @property
105
+ def model(self):
106
+ return self._model
107
+
108
+ @model.setter
109
+ def model(self, model):
110
+ from LoopStructural import GeologicalModel
111
+
112
+ # causes circular import, could delay import?
113
+ if isinstance(model, GeologicalModel):
114
+ self._model = model
115
+ elif not model:
116
+ self._model = None
117
+ logger.error("Model not set")
118
+ else:
119
+ raise TypeError("Model must be a GeologicalModel")
120
+
121
+ def toggle_faults(self):
122
+ """
123
+ Turn the fault off for a feature
124
+ This function is only really used for debugging or creating methods
125
+ explanation figures
126
+
127
+ Returns
128
+ -------
129
+
130
+ """
131
+ logger.warning(f"Toggling faults for feature {self.name}")
132
+ self.faults_enabled = not self.faults_enabled
133
+
134
+ def add_region(self, region):
135
+ """
136
+ Adds a region where the geological feature is active to the model.
137
+
138
+ Parameters
139
+ ----------
140
+ region : boolean function(x,y,z)
141
+ returns true if inside region, false if outside
142
+ can be passed as a lambda function e.g.
143
+ lambda pos : feature.evaluate_value(pos) > 0
144
+
145
+ Returns
146
+ -------
147
+
148
+ """
149
+ self.regions.append(region)
150
+
151
+ def __call__(self, xyz):
152
+ """Calls evaluate_value method
153
+
154
+ Parameters
155
+ ----------
156
+ xyz : np.ndarray
157
+ location to evaluate feature
158
+
159
+ Returns
160
+ -------
161
+ np.ndarray
162
+ the value of the feature at the locations
163
+ """
164
+ return self.evaluate_value(xyz)
165
+
166
+ @abstractmethod
167
+ def evaluate_value(self, pos, ignore_regions=False):
168
+ """
169
+ Evaluate the feature at a given position.
170
+ """
171
+ raise NotImplementedError
172
+
173
+ def evaluate_normalised_value(self, pos: NumericInput):
174
+ """Evaluate the feature value scaling between 0 and 1
175
+
176
+ Parameters
177
+ ----------
178
+ pos : NumericInput
179
+ An array or arraylike object with locations
180
+ """
181
+ value = self.evaluate_value(pos)
182
+ return (value - self.min()) / (self.max() - self.min())
183
+
184
+ def _calculate_mask(self, evaluation_points: np.ndarray, ignore_regions=False) -> np.ndarray:
185
+ """Calculate the mask for which evaluation points need to be calculated
186
+
187
+ Parameters
188
+ ----------
189
+ evaluation_points : np.ndarray
190
+ location to be evaluated, Nx3 array
191
+
192
+ Returns
193
+ -------
194
+ np.ndarray
195
+ bool mask Nx1 ndarray
196
+ """
197
+ mask = np.zeros(evaluation_points.shape[0]).astype(bool)
198
+
199
+ mask[:] = True
200
+ if not ignore_regions:
201
+ # check regions
202
+ for r in self.regions:
203
+ # try:
204
+ mask = np.logical_and(mask, r(evaluation_points))
205
+ return mask
206
+
207
+ def _apply_faults(self, evaluation_points: np.ndarray, reverse: bool = False) -> np.ndarray:
208
+ """Calculate the restored location of the points given any faults if faults are enabled
209
+
210
+ Parameters
211
+ ----------
212
+ evaluation_points : np.ndarray
213
+ location to be evaluated, Nx3 array
214
+
215
+ Returns
216
+ -------
217
+ np.ndarray
218
+ faulted value Nx1 ndarray
219
+ """
220
+
221
+ if self.faults_enabled:
222
+ # check faults
223
+ for f in self.faults:
224
+ evaluation_points = f.apply_to_points(evaluation_points, reverse=reverse)
225
+ return evaluation_points
226
+
227
+ @abstractmethod
228
+ def evaluate_gradient(self, pos, ignore_regions=False):
229
+ """
230
+ Evaluate the gradient of the feature at a given position.
231
+ """
232
+
233
+ raise NotImplementedError
234
+
235
+ def min(self):
236
+ """Calculate the min value of the geological feature
237
+ in the model
238
+
239
+ Returns
240
+ -------
241
+ minimum, float
242
+ min value of the feature evaluated on a regular grid in the model domain
243
+ """
244
+ if self.model is None:
245
+ return 0
246
+
247
+ return np.nanmin(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
248
+
249
+ def max(self):
250
+ """Calculate the maximum value of the geological feature
251
+ in the model
252
+
253
+ Returns
254
+ -------
255
+ maximum, float
256
+ max value of the feature evaluated on a regular grid in the model domain
257
+ """
258
+ if self.model is None:
259
+ return 0
260
+ return np.nanmax(self.evaluate_value(self.model.regular_grid((10, 10, 10))))
261
+
262
+ def __tojson__(self):
263
+ regions = [r.name for r in self.regions]
264
+ faults = [f.name for f in self.faults]
265
+ return {
266
+ "name": self.name,
267
+ "type": self.type,
268
+ "regions": regions,
269
+ "faults": faults,
270
+ }
271
+
272
+ def surfaces(
273
+ self,
274
+ value: Union[float, int, List[Union[float, int]]],
275
+ bounding_box=None,
276
+ name: Optional[Union[List[str], str]] = None,
277
+ ) -> surface_list:
278
+ """Find the surfaces of the geological feature at a given value
279
+
280
+ Parameters
281
+ ----------
282
+ value : Union[float, int, List[float, int]]
283
+ value or list of values to find the surface of the feature
284
+
285
+ Returns
286
+ -------
287
+ list
288
+ list of surfaces
289
+ """
290
+ if bounding_box is None:
291
+ if self.model is None:
292
+ raise ValueError("Must specify bounding box")
293
+ bounding_box = self.model.bounding_box
294
+ callable = lambda xyz: self.evaluate_value(self.model.scale(xyz))
295
+ isosurfacer = LoopIsosurfacer(bounding_box, callable=callable)
296
+ if name is None and self.name is not None:
297
+ name = self.name
298
+ return isosurfacer.fit(value, name)
299
+
300
+ def scalar_field(self, bounding_box=None):
301
+ """Create a scalar field for the feature
302
+
303
+ Parameters
304
+ ----------
305
+ bounding_box : Optional[BoundingBox], optional
306
+ bounding box to evaluate the scalar field in, by default None
307
+
308
+ Returns
309
+ -------
310
+ np.ndarray
311
+ scalar field
312
+ """
313
+ if bounding_box is None:
314
+ if self.model is None:
315
+ raise ValueError("Must specify bounding box")
316
+ bounding_box = self.model.bounding_box
317
+ grid = bounding_box.structured_grid(name=self.name)
318
+ value = self.evaluate_value(
319
+ self.model.scale(bounding_box.regular_grid(local=False, order='F'))
320
+ )
321
+ grid.properties[self.name] = value
322
+
323
+ value = self.evaluate_value(bounding_box.cell_centers(order='F'))
324
+ grid.cell_properties[self.name] = value
325
+ return grid
326
+
327
+ def vector_field(self, bounding_box=None, tolerance=0.05, scale=1.0):
328
+ """Create a vector field for the feature
329
+
330
+ Parameters
331
+ ----------
332
+ bounding_box : Optional[BoundingBox], optional
333
+ bounding box to evaluate the vector field in, by default None
334
+
335
+ Returns
336
+ -------
337
+ np.ndarray
338
+ vector field
339
+ """
340
+ if bounding_box is None:
341
+ if self.model is None:
342
+ raise ValueError("Must specify bounding box")
343
+ bounding_box = self.model.bounding_box
344
+ grid = bounding_box.vtk()
345
+ points = grid.points
346
+ value = self.evaluate_gradient(points)
347
+
348
+ return VectorPoints(points, value, self.name)
349
+
350
+ @abstractmethod
351
+ def get_data(self, value_map: Optional[dict] = None):
352
+ """Get the data for the feature
353
+
354
+ Parameters
355
+ ----------
356
+ value_map : Optional[dict], optional
357
+ map a scalar value to a string, by default None
358
+
359
+ Returns
360
+ -------
361
+ dict
362
+ dictionary of data
363
+ """
364
+ raise NotImplementedError
@@ -0,0 +1,100 @@
1
+ """
2
+ """
3
+
4
+ import numpy as np
5
+ from typing import Optional
6
+
7
+ from ...modelling.features import BaseFeature
8
+
9
+ from ...utils import getLogger
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ class CrossProductGeologicalFeature(BaseFeature):
15
+ def __init__(
16
+ self,
17
+ name: str,
18
+ geological_feature_a: BaseFeature,
19
+ geological_feature_b: BaseFeature,
20
+ ):
21
+ """
22
+
23
+ Create a geological feature for a vector field using the cross
24
+ product between
25
+ two existing features
26
+ Parameters
27
+ ----------
28
+ name: feature name
29
+ geological_feature_a: first feature
30
+ geological_feature_b: second feature
31
+
32
+
33
+ Parameters
34
+ ----------
35
+ name : str
36
+ name of the feature
37
+ geological_feature_a : BaseFeature
38
+ Left hand side of cross product
39
+ geological_feature_b : BaseFeature
40
+ Right hand side of cross product
41
+ """
42
+ super().__init__(name)
43
+ self.geological_feature_a = geological_feature_a
44
+ self.geological_feature_b = geological_feature_b
45
+ self.value_feature = None
46
+
47
+ def evaluate_gradient(self, locations: np.ndarray, ignore_regions=False) -> np.ndarray:
48
+ """
49
+ Calculate the gradient of the geological feature by using numpy to
50
+ calculate the cross
51
+ product between the two existing feature gradients.
52
+ This means both features have to be evaluated for the locations
53
+
54
+ Parameters
55
+ ----------
56
+ locations
57
+
58
+ Returns
59
+ -------
60
+
61
+ """
62
+ v1 = self.geological_feature_a.evaluate_gradient(locations, ignore_regions)
63
+ # v1 /= np.linalg.norm(v1,axis=1)[:,None]
64
+ v2 = self.geological_feature_b.evaluate_gradient(locations, ignore_regions)
65
+ # v2 /= np.linalg.norm(v2,axis=1)[:,None]
66
+ return np.cross(v1, v2, axisa=1, axisb=1)
67
+
68
+ def evaluate_value(self, evaluation_points: np.ndarray, ignore_regions=False) -> np.ndarray:
69
+ """
70
+ Return 0 because there is no value for this feature
71
+ Parameters
72
+ ----------
73
+ evaluation_points
74
+
75
+ Returns
76
+ -------
77
+
78
+ """
79
+ values = np.zeros(evaluation_points.shape[0])
80
+ if self.value_feature is not None:
81
+ values[:] = self.value_feature.evaluate_value(evaluation_points, ignore_regions)
82
+ return values
83
+
84
+ def mean(self):
85
+ if self.value_feature:
86
+ return self.value_feature.mean()
87
+ return 0.0
88
+
89
+ def min(self):
90
+ if self.value_feature:
91
+ return self.value_feature.min()
92
+ return 0.0
93
+
94
+ def max(self):
95
+ if self.value_feature:
96
+ return self.value_feature.max()
97
+ return 0.0
98
+
99
+ def get_data(self, value_map: Optional[dict] = None):
100
+ return