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.
- LoopStructural/__init__.py +52 -0
- LoopStructural/datasets/__init__.py +23 -0
- LoopStructural/datasets/_base.py +301 -0
- LoopStructural/datasets/_example_models.py +10 -0
- LoopStructural/datasets/data/claudius.csv +21049 -0
- LoopStructural/datasets/data/claudiusbb.txt +2 -0
- LoopStructural/datasets/data/duplex.csv +126 -0
- LoopStructural/datasets/data/duplexbb.txt +2 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
- LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
- LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
- LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
- LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
- LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
- LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
- LoopStructural/datasets/data/intrusion.csv +1017 -0
- LoopStructural/datasets/data/intrusionbb.txt +2 -0
- LoopStructural/datasets/data/onefoldbb.txt +2 -0
- LoopStructural/datasets/data/onefolddata.csv +2226 -0
- LoopStructural/datasets/data/refolded_bb.txt +2 -0
- LoopStructural/datasets/data/refolded_fold.csv +205 -0
- LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
- LoopStructural/datatypes/__init__.py +4 -0
- LoopStructural/datatypes/_bounding_box.py +422 -0
- LoopStructural/datatypes/_point.py +166 -0
- LoopStructural/datatypes/_structured_grid.py +94 -0
- LoopStructural/datatypes/_surface.py +184 -0
- LoopStructural/export/exporters.py +554 -0
- LoopStructural/export/file_formats.py +15 -0
- LoopStructural/export/geoh5.py +100 -0
- LoopStructural/export/gocad.py +126 -0
- LoopStructural/export/omf_wrapper.py +88 -0
- LoopStructural/interpolators/__init__.py +105 -0
- LoopStructural/interpolators/_api.py +143 -0
- LoopStructural/interpolators/_builders.py +149 -0
- LoopStructural/interpolators/_cython/__init__.py +0 -0
- LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
- LoopStructural/interpolators/_discrete_interpolator.py +692 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
- LoopStructural/interpolators/_geological_interpolator.py +380 -0
- LoopStructural/interpolators/_interpolator_factory.py +89 -0
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/interpolators/_operator.py +38 -0
- LoopStructural/interpolators/_p1interpolator.py +228 -0
- LoopStructural/interpolators/_p2interpolator.py +277 -0
- LoopStructural/interpolators/_surfe_wrapper.py +174 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
- LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
- LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
- LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
- LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
- LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
- LoopStructural/interpolators/supports/__init__.py +55 -0
- LoopStructural/interpolators/supports/_aabb.py +77 -0
- LoopStructural/interpolators/supports/_base_support.py +114 -0
- LoopStructural/interpolators/supports/_face_table.py +70 -0
- LoopStructural/interpolators/supports/_support_factory.py +32 -0
- LoopStructural/modelling/__init__.py +29 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1867 -0
- LoopStructural/modelling/features/__init__.py +32 -0
- LoopStructural/modelling/features/_analytical_feature.py +79 -0
- LoopStructural/modelling/features/_base_geological_feature.py +364 -0
- LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
- LoopStructural/modelling/features/_geological_feature.py +288 -0
- LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
- LoopStructural/modelling/features/_region.py +18 -0
- LoopStructural/modelling/features/_structural_frame.py +186 -0
- LoopStructural/modelling/features/_unconformity_feature.py +83 -0
- LoopStructural/modelling/features/builders/__init__.py +5 -0
- LoopStructural/modelling/features/builders/_base_builder.py +111 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
- LoopStructural/modelling/features/fault/__init__.py +3 -0
- LoopStructural/modelling/features/fault/_fault_function.py +444 -0
- LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
- LoopStructural/modelling/features/fold/__init__.py +9 -0
- LoopStructural/modelling/features/fold/_fold.py +167 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
- LoopStructural/modelling/features/fold/_foldframe.py +194 -0
- LoopStructural/modelling/features/fold/_svariogram.py +188 -0
- LoopStructural/modelling/input/__init__.py +2 -0
- LoopStructural/modelling/input/fault_network.py +80 -0
- LoopStructural/modelling/input/map2loop_processor.py +165 -0
- LoopStructural/modelling/input/process_data.py +650 -0
- LoopStructural/modelling/input/project_file.py +84 -0
- LoopStructural/modelling/intrusions/__init__.py +25 -0
- LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
- LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
- LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
- LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
- LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
- LoopStructural/utils/__init__.py +38 -0
- LoopStructural/utils/_surface.py +143 -0
- LoopStructural/utils/_transformation.py +76 -0
- LoopStructural/utils/config.py +18 -0
- LoopStructural/utils/dtm_creator.py +17 -0
- LoopStructural/utils/exceptions.py +31 -0
- LoopStructural/utils/helper.py +292 -0
- LoopStructural/utils/json_encoder.py +18 -0
- LoopStructural/utils/linalg.py +8 -0
- LoopStructural/utils/logging.py +79 -0
- LoopStructural/utils/maths.py +245 -0
- LoopStructural/utils/regions.py +103 -0
- LoopStructural/utils/typing.py +7 -0
- LoopStructural/utils/utils.py +68 -0
- LoopStructural/version.py +1 -0
- LoopStructural/visualisation/__init__.py +11 -0
- LoopStructural-1.6.1.dist-info/LICENSE +21 -0
- LoopStructural-1.6.1.dist-info/METADATA +81 -0
- LoopStructural-1.6.1.dist-info/RECORD +129 -0
- LoopStructural-1.6.1.dist-info/WHEEL +5 -0
- 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
|