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,288 @@
1
+ """
2
+ Geological features
3
+ """
4
+
5
+ from ...modelling.features import BaseFeature
6
+ from ...utils import getLogger
7
+ from ...modelling.features import FeatureType
8
+ from ...interpolators import GeologicalInterpolator, DiscreteInterpolator
9
+ import numpy as np
10
+ from typing import Optional, List, Union
11
+ from ...datatypes import ValuePoints, VectorPoints
12
+
13
+ from ...utils import LoopValueError
14
+
15
+ logger = getLogger(__name__)
16
+
17
+
18
+ class GeologicalFeature(BaseFeature):
19
+ """
20
+ Geological feature is class that is used to represent a geometrical element in a geological
21
+ model. For example foliations, fault planes, fold rotation angles etc.
22
+
23
+ Attributes
24
+ ----------
25
+ name : string
26
+ should be a unique name for the geological feature
27
+ support : a ScalarField
28
+ holds the property values for the feature and links to the
29
+ support geometry
30
+ data : list
31
+ list containing geological data
32
+ region : list
33
+ list of boolean functions defining whether the feature is
34
+ active
35
+ faults : list
36
+ list of FaultSegments that affect this feature
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ name: str,
42
+ interpolator: GeologicalInterpolator,
43
+ builder=None,
44
+ regions: list = [],
45
+ faults: list = [],
46
+ model=None,
47
+ ):
48
+ """Default constructor for geological feature
49
+
50
+ Parameters
51
+ ----------
52
+ name: str
53
+ interpolator : GeologicalInterpolator
54
+ builder : GeologicalFeatureBuilder
55
+ region : list
56
+ faults : list
57
+ model : GeologicalModel
58
+
59
+
60
+ """
61
+ BaseFeature.__init__(self, name, model, faults, regions, builder)
62
+ self.name = name
63
+ self.interpolator = interpolator
64
+ self.builder = builder
65
+ self.type = FeatureType.INTERPOLATED
66
+
67
+ def to_json(self):
68
+ """
69
+ Returns a json representation of the geological feature
70
+
71
+ Returns
72
+ -------
73
+ json : dict
74
+ json representation of the geological feature
75
+ """
76
+ json = super().to_json()
77
+ print(self.name, json)
78
+ json["interpolator"] = self.interpolator.to_json()
79
+ return json
80
+
81
+ def is_valid(self):
82
+ return self.interpolator.valid
83
+
84
+ def __getitem__(self, key):
85
+ return self._attributes[key]
86
+
87
+ def __setitem__(self, key, item):
88
+ self._attributes[key] = item
89
+
90
+ def set_model(self, model):
91
+ self.model = model
92
+
93
+ def evaluate_value(self, pos: np.ndarray, ignore_regions=False, fillnan=None) -> np.ndarray:
94
+ """
95
+ Evaluate the scalar field value of the geological feature at the locations
96
+ specified
97
+
98
+ Parameters
99
+ ----------
100
+ evaluation_points : np.ndarray
101
+ location to evaluate the scalar value
102
+
103
+ Returns
104
+ -------
105
+ values : numpy array
106
+ numpy array containing evaluated values
107
+
108
+ """
109
+ if pos.shape[1] != 3:
110
+ raise LoopValueError("Need Nx3 array of xyz points to evaluate value")
111
+ # TODO need to add a generic type checker for all methods
112
+ # if evaluation_points is not a numpy array try and convert
113
+ # otherwise error
114
+ evaluation_points = np.asarray(pos)
115
+ self.builder.up_to_date()
116
+ # check if the points are within the display region
117
+ v = np.zeros(evaluation_points.shape[0])
118
+ v[:] = np.nan
119
+ mask = self._calculate_mask(pos, ignore_regions=ignore_regions)
120
+ evaluation_points = self._apply_faults(evaluation_points)
121
+ if mask.dtype not in [int, bool]:
122
+ logger.error(f"Unable to evaluate value for {self.name}")
123
+ else:
124
+ v[mask] = self.interpolator.evaluate_value(evaluation_points[mask, :])
125
+ if fillnan == 'nearest':
126
+ import scipy.spatial as spatial
127
+
128
+ nanmask = np.isnan(v)
129
+ tree = spatial.cKDTree(evaluation_points[~nanmask, :])
130
+ _d, i = tree.query(evaluation_points[nanmask, :])
131
+ v[nanmask] = v[~nanmask][i]
132
+ return v
133
+
134
+ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
135
+ """
136
+
137
+ Parameters
138
+ ----------
139
+ locations : numpy array
140
+ location where the gradient is being evaluated
141
+
142
+ Returns
143
+ -------
144
+
145
+
146
+ """
147
+ if pos.shape[1] != 3:
148
+ raise LoopValueError("Need Nx3 array of xyz points to evaluate gradient")
149
+ logger.info(f'Calculating gradient for {self.name}')
150
+
151
+ self.builder.up_to_date()
152
+
153
+ v = np.zeros(pos.shape)
154
+ v[:] = np.nan
155
+ mask = self._calculate_mask(pos, ignore_regions=ignore_regions)
156
+ # evaluate the faults on the nodes of the faulted feature support
157
+ # then evaluate the gradient at these points
158
+ if len(self.faults) > 0:
159
+
160
+ if issubclass(type(self.interpolator), DiscreteInterpolator):
161
+ points = self.interpolator.support.nodes
162
+ else:
163
+ raise NotImplementedError(
164
+ "Faulted feature gradients are only supported by DiscreteInterpolator at the moment."
165
+ )
166
+ points_faulted = self._apply_faults(points)
167
+ values = self.interpolator.evaluate_value(points_faulted)
168
+ v[mask, :] = self.interpolator.support.evaluate_gradient(pos[mask, :], values)
169
+ return v
170
+ pos = self._apply_faults(pos)
171
+ if mask.dtype not in [int, bool]:
172
+ logger.error(f"Unable to evaluate gradient for {self.name}")
173
+ else:
174
+ v[mask, :] = self.interpolator.evaluate_gradient(pos[mask, :])
175
+ logger.info(f'Gradient calculated for {self.name}')
176
+ return v
177
+
178
+ def evaluate_gradient_misfit(self):
179
+ """
180
+
181
+ Returns
182
+ -------
183
+ misfit : np.array(N,dtype=double)
184
+ dot product between interpolated gradient and constraints
185
+ """
186
+ self.builder.up_to_date()
187
+ grad = self.interpolator.get_gradient_constraints()
188
+ norm = self.interpolator.get_norm_constraints()
189
+
190
+ dot = []
191
+ if grad.shape[0] > 0:
192
+ grad /= np.linalg.norm(grad, axis=1)[:, None]
193
+ model_grad = self.evaluate_gradient(grad[:, :3])
194
+ dot.append(np.einsum("ij,ij->i", model_grad, grad[:, :3:6]).tolist())
195
+
196
+ if norm.shape[0] > 0:
197
+ norm /= np.linalg.norm(norm, axis=1)[:, None]
198
+ model_norm = self.evaluate_gradient(norm[:, :3])
199
+ dot.append(np.einsum("ij,ij->i", model_norm, norm[:, :3:6]))
200
+
201
+ return np.array(dot)
202
+
203
+ def evaluate_value_misfit(self):
204
+ """
205
+
206
+ Returns
207
+ -------
208
+ misfit : np.array(N,dtype=double)
209
+ difference between interpolated scalar field and value constraints
210
+ """
211
+ self.builder.up_to_date()
212
+
213
+ locations = self.interpolator.get_value_constraints()
214
+ diff = np.abs(locations[:, 3] - self.evaluate_value(locations[:, :3]))
215
+ diff /= self.max() - self.min()
216
+ return diff
217
+
218
+ def copy(self, name=None):
219
+ if not name:
220
+ name = f"{self.name}_copy"
221
+ feature = GeologicalFeature(
222
+ name=name,
223
+ faults=self.faults,
224
+ regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
225
+ builder=self.builder,
226
+ model=self.model,
227
+ interpolator=self.interpolator,
228
+ )
229
+ return feature
230
+
231
+ def get_data(self, value_map: Optional[dict] = None) -> List[Union[ValuePoints, VectorPoints]]:
232
+ """Return the data associated with this geological feature
233
+
234
+ Parameters
235
+ ----------
236
+ value_map : Optional[dict], optional
237
+ A dictionary to map scalar values to another property, by default None
238
+
239
+ Returns
240
+ -------
241
+ List[Union[ValuePoints, VectorPoints]]
242
+ A container of either ValuePoints or VectorPoints
243
+ """
244
+
245
+ if self.builder is None:
246
+ return []
247
+ value_constraints = self.builder.get_value_constraints()
248
+ gradient_constraints = self.builder.get_gradient_constraints()
249
+ norm_constraints = self.builder.get_norm_constraints()
250
+ data = []
251
+ if gradient_constraints.shape[0] > 0:
252
+
253
+ data.append(
254
+ VectorPoints(
255
+ locations=self.model.rescale(gradient_constraints[:, :3]),
256
+ vectors=gradient_constraints[:, 3:6],
257
+ name=f'{self.name}+_gradient',
258
+ )
259
+ )
260
+ if norm_constraints.shape[0] > 0:
261
+ data.append(
262
+ VectorPoints(
263
+ locations=self.model.rescale(norm_constraints[:, :3]),
264
+ vectors=norm_constraints[:, 3:6],
265
+ name=f'{self.name}_norm',
266
+ )
267
+ )
268
+ if value_constraints.shape[0] > 0:
269
+ if value_map is not None:
270
+ for name, v in value_map.items():
271
+ data.append(
272
+ ValuePoints(
273
+ locations=self.model.rescale(
274
+ value_constraints[value_constraints == v, :3]
275
+ ),
276
+ values=value_constraints[value_constraints == v, 3],
277
+ name=f"{name}_value",
278
+ )
279
+ )
280
+ else:
281
+ data.append(
282
+ ValuePoints(
283
+ locations=self.model.rescale(value_constraints[:, :3]),
284
+ values=value_constraints[:, 3],
285
+ name=f"{self.name}_value",
286
+ )
287
+ )
288
+ return data
@@ -0,0 +1,93 @@
1
+ """
2
+ Geological features
3
+ """
4
+
5
+ from ...modelling.features import BaseFeature
6
+ from ...utils import getLogger
7
+ from ...modelling.features import FeatureType
8
+ import numpy as np
9
+ from typing import Callable, Optional
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ class LambdaGeologicalFeature(BaseFeature):
15
+
16
+ def __init__(
17
+ self,
18
+ function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
19
+ name: str = "unnamed_lambda",
20
+ gradient_function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
21
+ model=None,
22
+ regions: list = [],
23
+ faults: list = [],
24
+ builder=None,
25
+ ):
26
+ """A lambda geological feature is a wrapper for a geological
27
+ feature that has a function at the base. This can be then used
28
+ in place of a geological feature.
29
+
30
+ Parameters
31
+ ----------
32
+ function : _type_, optional
33
+ _description_, by default None
34
+ name : str, optional
35
+ _description_, by default "unnamed_lambda"
36
+ gradient_function : _type_, optional
37
+ _description_, by default None
38
+ model : _type_, optional
39
+ _description_, by default None
40
+ regions : list, optional
41
+ _description_, by default []
42
+ faults : list, optional
43
+ _description_, by default []
44
+ builder : _type_, optional
45
+ _description_, by default None
46
+ """
47
+ BaseFeature.__init__(self, name, model, faults, regions, builder)
48
+ self.type = FeatureType.LAMBDA
49
+ self.function = function
50
+ self.gradient_function = gradient_function
51
+
52
+ def evaluate_value(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
53
+ """_summary_
54
+
55
+ Parameters
56
+ ----------
57
+ xyz : np.ndarray
58
+ _description_
59
+
60
+ Returns
61
+ -------
62
+ np.ndarray
63
+ _description_
64
+ """
65
+ v = np.zeros((pos.shape[0]))
66
+ if self.function is None:
67
+ v[:] = np.nan
68
+ else:
69
+ v[:] = self.function(pos)
70
+ return v
71
+
72
+ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
73
+ """_summary_
74
+
75
+ Parameters
76
+ ----------
77
+ xyz : np.ndarray
78
+ _description_
79
+
80
+ Returns
81
+ -------
82
+ np.ndarray
83
+ _description_
84
+ """
85
+ v = np.zeros((pos.shape[0], 3))
86
+ if self.gradient_function is None:
87
+ v[:, :] = np.nan
88
+ else:
89
+ v[:, :] = self.gradient_function(pos)
90
+ return v
91
+
92
+ def get_data(self, value_map: Optional[dict] = None):
93
+ return
@@ -0,0 +1,18 @@
1
+ class Region:
2
+ def __init__(self, feature, value, sign):
3
+ self.feature = feature
4
+ self.value = value
5
+ self.sign = sign
6
+
7
+ def __call__(self, xyz):
8
+ if self.sign:
9
+ return self.feature.evaluate_value(xyz) > 0
10
+ else:
11
+ return self.feature.evaluate_value(xyz) < 0
12
+
13
+ def to_json(self):
14
+ return {
15
+ "feature": self.feature.name,
16
+ "value": self.value,
17
+ "sign": self.sign,
18
+ }
@@ -0,0 +1,186 @@
1
+ """
2
+ Structural frames
3
+ """
4
+
5
+ from ..features import BaseFeature, FeatureType
6
+ import numpy as np
7
+ from ...utils import getLogger
8
+ from typing import Optional, List, Union
9
+ from ...datatypes import ValuePoints, VectorPoints
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ class StructuralFrame(BaseFeature):
15
+ def __init__(self, name: str, features: list, fold=None, model=None):
16
+ """
17
+ Structural frame is a curvilinear coordinate system defined by
18
+ structural observations associated with a fault or fold.
19
+
20
+ Parameters
21
+ ----------
22
+ name - name of the structural frame
23
+ features - list of features to build the frame with
24
+ """
25
+ BaseFeature.__init__(self, name, model, [], [], None)
26
+ self.features = features
27
+ self.fold = fold
28
+ self.type = FeatureType.STRUCTURALFRAME
29
+
30
+ def to_json(self):
31
+ """
32
+ Return a json representation of the structural frame
33
+
34
+ Returns
35
+ -------
36
+ json : dict
37
+ json representation of the structural frame
38
+ """
39
+ json = {}
40
+ json["name"] = self.name
41
+ json["features"] = [f.name for f in self.features]
42
+ json["type"] = self.type
43
+ return json
44
+
45
+ def __getitem__(self, key):
46
+ """
47
+
48
+ Parameters
49
+ ----------
50
+ key index of feature to access
51
+
52
+ Returns
53
+ -------
54
+ the structural frame geological feature
55
+ """
56
+ return self.features[key]
57
+
58
+ def __setitem__(self, key, value):
59
+ """
60
+
61
+ Parameters
62
+ ----------
63
+ item index of feature to access
64
+
65
+ Returns
66
+ -------
67
+ the structural frame geological feature
68
+ """
69
+ self.features[key] = value
70
+
71
+ def set_model(self, model):
72
+ """Link the model that created the frame to the frame
73
+ and the features that make up the frame
74
+
75
+ Parameters
76
+ ----------
77
+ model : GeologicalModel
78
+ the geological model that created the fold frame
79
+ """
80
+ self.model = model
81
+ for f in self.features:
82
+ if f is None:
83
+ continue
84
+ f.set_model(model)
85
+
86
+ @property
87
+ def model(self):
88
+ return self._model
89
+
90
+ @model.setter
91
+ def model(self, model):
92
+ # causes circular import, could delay import?
93
+ # if type(model) == GeologicalModel:
94
+ for f in self.features:
95
+ if f is None:
96
+ continue
97
+ f.model = model
98
+
99
+ def add_region(self, region):
100
+ self.regions.append(region)
101
+ for i in range(3):
102
+ self.features[i].regions = self.regions
103
+
104
+ def get_feature(self, i):
105
+ """
106
+ Return the ith feature
107
+
108
+ Parameters
109
+ ----------
110
+ i
111
+
112
+ Returns
113
+ -------
114
+
115
+ """
116
+ return self.features[i]
117
+
118
+ def evaluate_value(self, evaluation_points, ignore_regions=False):
119
+ """
120
+ Evaluate the value of the structural frame for the points.
121
+ Can optionally only evaluate one coordinate
122
+
123
+ Parameters
124
+ ----------
125
+ evaluation_points
126
+ i
127
+
128
+ Returns
129
+ -------
130
+
131
+ """
132
+ v = np.zeros(evaluation_points.shape) # create new 3d array of correct length
133
+ v[:] = np.nan
134
+ v[:, 0] = self.features[0].evaluate_value(evaluation_points, ignore_regions=ignore_regions)
135
+ v[:, 1] = self.features[1].evaluate_value(evaluation_points, ignore_regions=ignore_regions)
136
+ v[:, 2] = self.features[2].evaluate_value(evaluation_points, ignore_regions=ignore_regions)
137
+ return v
138
+
139
+ def evaluate_gradient(self, evaluation_points, i=None, ignore_regions=False):
140
+ """
141
+ Evaluate the gradient of the structural frame.
142
+ Can optionally only evaluate the ith coordinate
143
+
144
+ Parameters
145
+ ----------
146
+ evaluation_points
147
+ i
148
+
149
+ Returns
150
+ -------
151
+
152
+ """
153
+ if i is not None:
154
+ return self.features[i].support.evaluate_gradient(
155
+ evaluation_points, ignore_regions=ignore_regions
156
+ )
157
+ return (
158
+ self.features[0].support.evaluate_gradient(
159
+ evaluation_points, ignore_regions=ignore_regions
160
+ ),
161
+ self.features[1].support.evaluate_gradient(
162
+ evaluation_points, ignore_regions=ignore_regions
163
+ ),
164
+ self.features[2].support.evaluate_gradient(
165
+ evaluation_points, ignore_regions=ignore_regions
166
+ ),
167
+ )
168
+
169
+ def get_data(self, value_map: Optional[dict] = None) -> List[Union[ValuePoints, VectorPoints]]:
170
+ """Return the data associated with the features in the
171
+ structural frame
172
+
173
+ Parameters
174
+ ----------
175
+ value_map : Optional[dict], optional
176
+ map scalar values to another property, by default None
177
+
178
+ Returns
179
+ -------
180
+ List
181
+ container of value or vector points
182
+ """
183
+ data = []
184
+ for f in self.features:
185
+ data.extend(f.get_data(value_map))
186
+ return data
@@ -0,0 +1,83 @@
1
+ from ...modelling.features import GeologicalFeature
2
+ from ...modelling.features import FeatureType
3
+
4
+ import numpy as np
5
+
6
+
7
+ class UnconformityFeature(GeologicalFeature):
8
+ """ """
9
+
10
+ def __init__(self, feature: GeologicalFeature, value: float, sign=True, onlap=False):
11
+ """
12
+
13
+ Parameters
14
+ ----------
15
+ feature
16
+ value
17
+ """
18
+ # create a shallow(ish) copy of the geological feature
19
+ # just don't link the regions
20
+ GeologicalFeature.__init__(
21
+ self,
22
+ name=f"{feature.name}_unconformity",
23
+ faults=feature.faults,
24
+ regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
25
+ builder=feature.builder,
26
+ model=feature.model,
27
+ interpolator=feature.interpolator,
28
+ )
29
+ self.value = value
30
+ self.type = FeatureType.UNCONFORMITY if onlap is False else FeatureType.ONLAPUNCONFORMITY
31
+ self.sign = sign
32
+ self.parent = feature
33
+
34
+ @property
35
+ def faults(self):
36
+ return self.parent.faults
37
+
38
+ def to_json(self):
39
+ json = super().to_json()
40
+ json["value"] = self.value
41
+ json["sign"] = self.sign
42
+ json["parent"] = self.parent.name
43
+ return json
44
+
45
+ def inverse(self):
46
+ """Returns an unconformity feature with the sign flipped
47
+ The feature is a shallow copy with the parent being set to
48
+ the parent of this feature
49
+
50
+ Returns
51
+ -------
52
+ UnconformityFeature
53
+ _description_
54
+ """
55
+ uc = UnconformityFeature(
56
+ self.parent,
57
+ self.value,
58
+ sign=not self.sign,
59
+ onlap=self.type == FeatureType.ONLAPUNCONFORMITY,
60
+ )
61
+ uc.name = self.name + "_inverse"
62
+ return uc
63
+
64
+ def evaluate(self, pos: np.ndarray) -> np.ndarray:
65
+ """
66
+
67
+ Parameters
68
+ ----------
69
+ pos : numpy array
70
+ locations to evaluate whether below or above unconformity
71
+
72
+ Returns
73
+ -------
74
+ np.ndarray.dtype(bool)
75
+ true if above the unconformity, false if below
76
+ """
77
+ if self.sign:
78
+ return self.evaluate_value(pos) < self.value
79
+ if not self.sign:
80
+ return self.evaluate_value(pos) > self.value
81
+
82
+ def __call__(self, pos) -> np.ndarray:
83
+ return self.evaluate(pos)
@@ -0,0 +1,5 @@
1
+ from ._base_builder import BaseBuilder
2
+ from ._geological_feature_builder import GeologicalFeatureBuilder
3
+ from ._folded_feature_builder import FoldedFeatureBuilder
4
+ from ._structural_frame_builder import StructuralFrameBuilder
5
+ from ._fault_builder import FaultBuilder