LoopStructural 1.0.9__cp38-cp38-win32.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.
- LoopStructural/__init__.py +36 -0
- LoopStructural/analysis/__init__.py +2 -0
- LoopStructural/analysis/_fault_displacement.py +67 -0
- LoopStructural/analysis/_fault_intersection.py +36 -0
- LoopStructural/analysis/_plane_fit.py +43 -0
- LoopStructural/datasets/__init__.py +12 -0
- LoopStructural/datasets/_base.py +65 -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/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 +2126 -0
- LoopStructural/interpolators/__init__.py +34 -0
- LoopStructural/interpolators/base_structured_3d_support.py +101 -0
- LoopStructural/interpolators/cython/__init__.py +0 -0
- LoopStructural/interpolators/cython/dsi_helper.c +31046 -0
- LoopStructural/interpolators/cython/dsi_helper.cp38-win32.pyd +0 -0
- LoopStructural/interpolators/discrete_fold_interpolator.py +202 -0
- LoopStructural/interpolators/discrete_interpolator.py +584 -0
- LoopStructural/interpolators/finite_difference_interpolator.py +399 -0
- LoopStructural/interpolators/geological_interpolator.py +197 -0
- LoopStructural/interpolators/operator.py +61 -0
- LoopStructural/interpolators/piecewiselinear_interpolator.py +437 -0
- LoopStructural/interpolators/structured_grid.py +421 -0
- LoopStructural/interpolators/structured_tetra.py +681 -0
- LoopStructural/interpolators/surfe_wrapper.py +118 -0
- LoopStructural/modelling/__init__.py +46 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1513 -0
- LoopStructural/modelling/core/geological_model_graph.py +880 -0
- LoopStructural/modelling/core/stratigraphic_column.py +5 -0
- LoopStructural/modelling/fault/__init__.py +4 -0
- LoopStructural/modelling/fault/fault_builder.py +143 -0
- LoopStructural/modelling/fault/fault_function.py +188 -0
- LoopStructural/modelling/fault/fault_function_feature.py +76 -0
- LoopStructural/modelling/fault/fault_segment.py +298 -0
- LoopStructural/modelling/features/__init__.py +8 -0
- LoopStructural/modelling/features/cross_product_geological_feature.py +90 -0
- LoopStructural/modelling/features/geological_feature.py +252 -0
- LoopStructural/modelling/features/geological_feature_builder.py +414 -0
- LoopStructural/modelling/features/lambda_geological_feature.py +36 -0
- LoopStructural/modelling/features/region_feature.py +34 -0
- LoopStructural/modelling/features/structural_frame.py +133 -0
- LoopStructural/modelling/features/structural_frame_builder.py +206 -0
- LoopStructural/modelling/features/unconformity_feature.py +74 -0
- LoopStructural/modelling/fold/__init__.py +8 -0
- LoopStructural/modelling/fold/fold.py +143 -0
- LoopStructural/modelling/fold/fold_rotation_angle.py +133 -0
- LoopStructural/modelling/fold/fold_rotation_angle_feature.py +58 -0
- LoopStructural/modelling/fold/foldframe.py +193 -0
- LoopStructural/modelling/fold/svariogram.py +180 -0
- LoopStructural/utils/__init__.py +18 -0
- LoopStructural/utils/bounding_box.py +21 -0
- LoopStructural/utils/exceptions.py +10 -0
- LoopStructural/utils/helper.py +382 -0
- LoopStructural/utils/logging.py +60 -0
- LoopStructural/utils/map2loop.py +353 -0
- LoopStructural/utils/regions.py +11 -0
- LoopStructural/utils/utils.py +69 -0
- LoopStructural/visualisation/__init__.py +31 -0
- LoopStructural/visualisation/map_viewer.py +322 -0
- LoopStructural/visualisation/model_plotter.py +17 -0
- LoopStructural/visualisation/model_visualisation.py +1113 -0
- LoopStructural/visualisation/rotation_angle_plotter.py +83 -0
- LoopStructural/visualisation/sphinx_scraper.py +34 -0
- LoopStructural/visualisation/stratigraphic_column.py +60 -0
- LoopStructural-1.0.9.dist-info/LICENSE +21 -0
- LoopStructural-1.0.9.dist-info/METADATA +19 -0
- LoopStructural-1.0.9.dist-info/RECORD +77 -0
- LoopStructural-1.0.9.dist-info/WHEEL +5 -0
- LoopStructural-1.0.9.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from LoopStructural.modelling.fault.fault_function_feature import FaultDisplacementFeature
|
|
4
|
+
from LoopStructural.modelling.fault.fault_function import BaseFault
|
|
5
|
+
from LoopStructural.utils import getLogger
|
|
6
|
+
logger = getLogger(__name__)
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FaultSegment:
|
|
12
|
+
"""
|
|
13
|
+
Class for representing a slip event of a fault
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, faultframe,
|
|
17
|
+
faultfunction = None,
|
|
18
|
+
steps = 3,
|
|
19
|
+
displacement=1.,
|
|
20
|
+
**kwargs):
|
|
21
|
+
"""
|
|
22
|
+
A slip event of a fault
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
faultframe : FaultFrame
|
|
27
|
+
the fault frame defining the faut geometry
|
|
28
|
+
faultfunction : function/lambda function
|
|
29
|
+
optional displacement function for spatially variable fault displacement
|
|
30
|
+
steps : int
|
|
31
|
+
how many integration steps for faults
|
|
32
|
+
kwargs
|
|
33
|
+
"""
|
|
34
|
+
self.faultframe = faultframe
|
|
35
|
+
self.type = 'fault'
|
|
36
|
+
self.name = kwargs.get('name', self.faultframe.name)
|
|
37
|
+
self.displacement = displacement
|
|
38
|
+
self.faultfunction = faultfunction
|
|
39
|
+
if faultfunction == 'BaseFault':
|
|
40
|
+
self.faultfunction = BaseFault.fault_displacement
|
|
41
|
+
self.steps = steps
|
|
42
|
+
self.regions = []
|
|
43
|
+
self.faults_enabled = True
|
|
44
|
+
self.displacementfeature = None
|
|
45
|
+
self.model = None
|
|
46
|
+
if self.faultframe is not None:
|
|
47
|
+
self.displacementfeature = FaultDisplacementFeature(
|
|
48
|
+
self.faultframe, self.faultfunction, name = self.name)
|
|
49
|
+
self.builder = None
|
|
50
|
+
|
|
51
|
+
def __getitem__(self, item):
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
item
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
return self.faultframe[item]
|
|
63
|
+
|
|
64
|
+
def set_model(self, model):
|
|
65
|
+
"""
|
|
66
|
+
Link a geological model to the feature
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
model - GeologicalModel
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
self.model = model
|
|
77
|
+
|
|
78
|
+
def set_displacement(self, displacement, scale = True):
|
|
79
|
+
"""
|
|
80
|
+
Set the fault displacement to a new value
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
displacement - double
|
|
85
|
+
scale - boolean
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
if scale and self.model is not None:
|
|
92
|
+
self.displacement = displacement / self.model.scale_factor
|
|
93
|
+
elif not scale:
|
|
94
|
+
self.displacement = displacement
|
|
95
|
+
else:
|
|
96
|
+
logger.warning("Displacement not updated")
|
|
97
|
+
|
|
98
|
+
def toggle_faults(self):
|
|
99
|
+
"""
|
|
100
|
+
Toggle faults that affect this fault segment
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
self.faults_enabled = ~self.faults_enabled
|
|
107
|
+
for i in range(3):
|
|
108
|
+
self.faultframe[i].toggle_faults()
|
|
109
|
+
|
|
110
|
+
def add_region(self, region):
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
region : boolean function(x,y,z)
|
|
116
|
+
A function that returns true if inside region, false if outside
|
|
117
|
+
can be passed as a lambda function e.g.
|
|
118
|
+
lambda pos : feature.evaluate_value(pos) > 0
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
self.regions.append(region)
|
|
125
|
+
|
|
126
|
+
def evaluate(self, locations):
|
|
127
|
+
"""
|
|
128
|
+
Evaluate which side of fault
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
locations numpy array
|
|
133
|
+
location to evaluate
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
boolean array true if on hanging wall, false if on footwall
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
return self.faultframe.features[0].evaluate_value(locations) > 0
|
|
142
|
+
|
|
143
|
+
def inside_volume(self,locations):
|
|
144
|
+
v = self.faultframe.evaluate_value(locations)
|
|
145
|
+
return np.all(np.logical_and(v > -1,v<1),axis=1)
|
|
146
|
+
|
|
147
|
+
def evaluate_value(self, locations):
|
|
148
|
+
"""
|
|
149
|
+
Return the value of the fault surface scalar field
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
locations - numpy array
|
|
154
|
+
location to evaluate scalar field
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
v = np.zeros(locations.shape[0])
|
|
161
|
+
v[:] = np.nan
|
|
162
|
+
mask = np.zeros(locations.shape[0]).astype(bool)
|
|
163
|
+
mask[:] = True
|
|
164
|
+
# check regions
|
|
165
|
+
for r in self.regions:
|
|
166
|
+
mask = np.logical_and(mask, r(locations))
|
|
167
|
+
v[mask]=self.faultframe[0].evaluate_value(locations[mask, :])
|
|
168
|
+
return v
|
|
169
|
+
|
|
170
|
+
def mean(self):
|
|
171
|
+
return self.faultframe[0].mean()
|
|
172
|
+
|
|
173
|
+
def max(self):
|
|
174
|
+
return self.faultframe[0].max()
|
|
175
|
+
|
|
176
|
+
def min(self):
|
|
177
|
+
return self.faultframe[0].min()
|
|
178
|
+
|
|
179
|
+
def evaluate_gradient(self, locations):
|
|
180
|
+
"""
|
|
181
|
+
Return the fault slip direction at the location
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
locations - numpy array Nx3
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
v = np.zeros(locations.shape)
|
|
193
|
+
v[:,:] = np.nan
|
|
194
|
+
mask = np.zeros(locations.shape[0]).astype(bool)
|
|
195
|
+
mask[:] = True
|
|
196
|
+
# check regions
|
|
197
|
+
for r in self.regions:
|
|
198
|
+
mask = np.logical_and(mask, r(locations))
|
|
199
|
+
# need to scale with fault displacement
|
|
200
|
+
v[mask,:] = self.faultframe[1].evaluate_gradient(locations[mask, :])
|
|
201
|
+
return v
|
|
202
|
+
|
|
203
|
+
def evaluate_displacement(self, points):
|
|
204
|
+
newp = np.copy(points).astype(float)
|
|
205
|
+
# evaluate fault function for all points then define mask for only points affected by fault
|
|
206
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
207
|
+
# all of these operations should be independent so just run as different threads
|
|
208
|
+
gx_future = executor.submit(self.faultframe.features[0].evaluate_value, newp)
|
|
209
|
+
gy_future = executor.submit(self.faultframe.features[1].evaluate_value, newp)
|
|
210
|
+
gz_future = executor.submit(self.faultframe.features[2].evaluate_value, newp)
|
|
211
|
+
gx = gx_future.result()
|
|
212
|
+
gy = gy_future.result()
|
|
213
|
+
gz = gz_future.result()
|
|
214
|
+
d = np.zeros(gx.shape)
|
|
215
|
+
mask = np.logical_and(~np.isnan(gx),~np.isnan(gy))
|
|
216
|
+
mask = np.logical_and(mask,~np.isnan(gz))
|
|
217
|
+
d[~mask] = 0
|
|
218
|
+
gx_mask = np.zeros_like(mask,dtype=bool)
|
|
219
|
+
gx_mask[mask] = gx[mask] > 0
|
|
220
|
+
d[gx_mask] = 1.
|
|
221
|
+
if self.faultfunction is not None:
|
|
222
|
+
d[mask] = self.faultfunction(gx[mask], gy[mask], gz[mask])
|
|
223
|
+
return d
|
|
224
|
+
def apply_to_points(self, points):
|
|
225
|
+
"""
|
|
226
|
+
Unfault the array of points
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
points - numpy array Nx3
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
steps = self.steps
|
|
237
|
+
newp = np.copy(points).astype(float)
|
|
238
|
+
# evaluate fault function for all points then define mask for only points affected by fault
|
|
239
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
240
|
+
# all of these operations should be independent so just run as different threads
|
|
241
|
+
gx_future = executor.submit(self.faultframe.features[0].evaluate_value, newp)
|
|
242
|
+
gy_future = executor.submit(self.faultframe.features[1].evaluate_value, newp)
|
|
243
|
+
gz_future = executor.submit(self.faultframe.features[2].evaluate_value, newp)
|
|
244
|
+
gx = gx_future.result()
|
|
245
|
+
gy = gy_future.result()
|
|
246
|
+
gz = gz_future.result()
|
|
247
|
+
d = np.zeros(gx.shape)
|
|
248
|
+
mask = np.logical_and(~np.isnan(gx),~np.isnan(gy))
|
|
249
|
+
mask = np.logical_and(mask,~np.isnan(gz))
|
|
250
|
+
d[~mask] = 0
|
|
251
|
+
gx_mask = np.zeros_like(mask,dtype=bool)
|
|
252
|
+
gx_mask[mask] = gx[mask] > 0
|
|
253
|
+
d[gx_mask] = 1.
|
|
254
|
+
if self.faultfunction is not None:
|
|
255
|
+
d[mask] = self.faultfunction(gx[mask], gy[mask], gz[mask])
|
|
256
|
+
mask = np.abs(d) > 0.
|
|
257
|
+
|
|
258
|
+
d *= self.displacement
|
|
259
|
+
# calculate the fault frame for the evaluation points
|
|
260
|
+
for i in range(steps):
|
|
261
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
262
|
+
# all of these operations should be independent so just run as different threads
|
|
263
|
+
gx_future = executor.submit(self.faultframe.features[0].evaluate_value, newp[mask, :])
|
|
264
|
+
g_future = executor.submit(self.faultframe.features[1].evaluate_gradient, newp[mask, :])
|
|
265
|
+
gy_future = executor.submit(self.faultframe.features[1].evaluate_value, newp[mask, :])
|
|
266
|
+
gz_future = executor.submit(self.faultframe.features[2].evaluate_value, newp[mask, :])
|
|
267
|
+
gx = gx_future.result()
|
|
268
|
+
g = g_future.result()
|
|
269
|
+
gy = gy_future.result()
|
|
270
|
+
gz = gz_future.result()
|
|
271
|
+
# # get the fault frame val/grad for the points
|
|
272
|
+
# determine displacement magnitude, for constant displacement
|
|
273
|
+
# hanging wall should be > 0
|
|
274
|
+
d = np.zeros(gx.shape)
|
|
275
|
+
mask2 = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
|
|
276
|
+
mask2 = np.logical_and(mask2, ~np.isnan(gz))
|
|
277
|
+
d[~mask2] = 0
|
|
278
|
+
gx_mask2 = np.zeros_like(mask2,dtype=bool)
|
|
279
|
+
gx_mask2[mask2] = gx[mask2] > 0
|
|
280
|
+
# d[~np.isnan(gx)][gx[~np.isnan(gx)]>0] = 1
|
|
281
|
+
d[gx_mask2] = 1.
|
|
282
|
+
# d[mask2][gx[mask2] < 0] = 0.
|
|
283
|
+
# d[gx < 0] = 0.
|
|
284
|
+
if self.faultfunction is not None:
|
|
285
|
+
d[mask2] = self.faultfunction(gx[mask2], gy[mask2], gz[mask2])
|
|
286
|
+
d *= self.displacement
|
|
287
|
+
# normalise when length is >0
|
|
288
|
+
g_mag = np.zeros(g.shape[0])
|
|
289
|
+
g_mag[mask2] = np.linalg.norm(g[mask2], axis=1)
|
|
290
|
+
# g_mag = np.linalg.norm(g[mask2], axis=1)
|
|
291
|
+
g[g_mag > 0.] /= g_mag[g_mag > 0, None]
|
|
292
|
+
# multiply displacement vector by the displacement magnitude for
|
|
293
|
+
# step
|
|
294
|
+
g *= (1. / steps) * d[:, None]
|
|
295
|
+
|
|
296
|
+
# apply displacement
|
|
297
|
+
newp[mask, :] += g
|
|
298
|
+
return newp
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# from .faulted_geological_feature import FaultedGeologicalFeature
|
|
2
|
+
from .geological_feature import GeologicalFeature
|
|
3
|
+
from .lambda_geological_feature import LambdaGeologicalFeature
|
|
4
|
+
from .geological_feature_builder import GeologicalFeatureInterpolator
|
|
5
|
+
from .region_feature import RegionFeature
|
|
6
|
+
from .structural_frame import StructuralFrame
|
|
7
|
+
from .structural_frame_builder import StructuralFrameBuilder
|
|
8
|
+
from .unconformity_feature import UnconformityFeature
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from LoopStructural.modelling.features.geological_feature import \
|
|
8
|
+
GeologicalFeature
|
|
9
|
+
|
|
10
|
+
from LoopStructural.utils import getLogger
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CrossProductGeologicalFeature(GeologicalFeature):
|
|
15
|
+
"""[summary]
|
|
16
|
+
|
|
17
|
+
[extended_summary]
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
GeologicalFeature : [type]
|
|
22
|
+
[description]
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, name, geological_feature_a, geological_feature_b):
|
|
25
|
+
"""
|
|
26
|
+
Create a geological feature for a vector field using the cross
|
|
27
|
+
product between
|
|
28
|
+
two existing features
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
name: feature name
|
|
32
|
+
geological_feature_a: first feature
|
|
33
|
+
geological_feature_b: second feature
|
|
34
|
+
"""
|
|
35
|
+
super().__init__(name, None)
|
|
36
|
+
self.geological_feature_a = geological_feature_a
|
|
37
|
+
self.geological_feature_b = geological_feature_b
|
|
38
|
+
self.value_feature = None
|
|
39
|
+
def evaluate_gradient(self, locations):
|
|
40
|
+
"""
|
|
41
|
+
Calculate the gradient of the geological feature by using numpy to
|
|
42
|
+
calculate the cross
|
|
43
|
+
product between the two existing feature gradients.
|
|
44
|
+
This means both features have to be evaluated for the locations
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
locations
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
v1 = self.geological_feature_a.evaluate_gradient(locations)
|
|
54
|
+
# v1 /= np.linalg.norm(v1,axis=1)[:,None]
|
|
55
|
+
v2 = self.geological_feature_b.evaluate_gradient(locations)
|
|
56
|
+
# v2 /= np.linalg.norm(v2,axis=1)[:,None]
|
|
57
|
+
return np.cross(v1,v2,
|
|
58
|
+
axisa=1,
|
|
59
|
+
axisb=1)
|
|
60
|
+
|
|
61
|
+
def evaluate_value(self, evaluation_points):
|
|
62
|
+
"""
|
|
63
|
+
Return 0 because there is no value for this feature
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
evaluation_points
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
values = np.zeros(evaluation_points.shape[0])
|
|
73
|
+
if self.value_feature is not None:
|
|
74
|
+
values[:] = self.value_feature.evaluate_value(evaluation_points)
|
|
75
|
+
return values
|
|
76
|
+
|
|
77
|
+
def mean(self):
|
|
78
|
+
if self.value_feature:
|
|
79
|
+
return self.value_feature.mean()
|
|
80
|
+
return 0.
|
|
81
|
+
|
|
82
|
+
def min(self):
|
|
83
|
+
if self.value_feature:
|
|
84
|
+
return self.value_feature.min()
|
|
85
|
+
return 0.
|
|
86
|
+
|
|
87
|
+
def max(self):
|
|
88
|
+
if self.value_feature:
|
|
89
|
+
return self.value_feature.max()
|
|
90
|
+
return 0.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geological features
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from LoopStructural.utils import getLogger
|
|
9
|
+
logger = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GeologicalFeature:
|
|
13
|
+
"""
|
|
14
|
+
Geological feature is class that is used to represent a geometrical element in a geological
|
|
15
|
+
model. For example foliations, fault planes, fold rotation angles etc.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
name : string
|
|
20
|
+
should be a unique name for the geological feature
|
|
21
|
+
support : a ScalarField
|
|
22
|
+
holds the property values for the feature and links to the
|
|
23
|
+
support geometry
|
|
24
|
+
data : list
|
|
25
|
+
list containing geological data
|
|
26
|
+
region : list
|
|
27
|
+
list of boolean functions defining whether the feature is
|
|
28
|
+
active
|
|
29
|
+
faults : list
|
|
30
|
+
list of FaultSegments that affect this feature
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, name, interpolator, builder=None, data=None, region=None, type=None,
|
|
33
|
+
faults=[], fold = None):
|
|
34
|
+
"""Default constructor for geological feature
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
name: string
|
|
39
|
+
interpolator : GeologicalInterpolator
|
|
40
|
+
builder : GeologicalFeatureBuilder
|
|
41
|
+
data :
|
|
42
|
+
region :
|
|
43
|
+
type :
|
|
44
|
+
faults : list
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
self.name = name
|
|
49
|
+
self.interpolator = interpolator
|
|
50
|
+
self.ndim = 1
|
|
51
|
+
self.builder = builder
|
|
52
|
+
self.region = region
|
|
53
|
+
self.regions = []
|
|
54
|
+
self.type = type
|
|
55
|
+
self.faults = faults
|
|
56
|
+
self.faults_enabled = True
|
|
57
|
+
self.fold=fold
|
|
58
|
+
self._attributes = {}
|
|
59
|
+
self._attributes['feature'] = self
|
|
60
|
+
self._attributes['builder'] = self.builder
|
|
61
|
+
self._attributes['faults'] = self.faults
|
|
62
|
+
if region is None:
|
|
63
|
+
self.region = 'everywhere'
|
|
64
|
+
self.model = None
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return self.name
|
|
68
|
+
|
|
69
|
+
def __getitem__(self,key):
|
|
70
|
+
return self._attributes[key]
|
|
71
|
+
|
|
72
|
+
def __setitem__(self, key, item):
|
|
73
|
+
self._attributes[key] = item
|
|
74
|
+
|
|
75
|
+
def set_model(self, model):
|
|
76
|
+
self.model = model
|
|
77
|
+
|
|
78
|
+
def toggle_faults(self):
|
|
79
|
+
"""
|
|
80
|
+
Turn the fault off for a feature
|
|
81
|
+
This function is only really used for debugging or creating methods
|
|
82
|
+
explanation figures
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
self.faults_enabled = ~self.faults_enabled
|
|
89
|
+
|
|
90
|
+
def add_region(self, region):
|
|
91
|
+
"""
|
|
92
|
+
Adds a region where the geological feature is active to the model.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
region : boolean function(x,y,z)
|
|
97
|
+
returns true if inside region, false if outside
|
|
98
|
+
can be passed as a lambda function e.g.
|
|
99
|
+
lambda pos : feature.evaluate_value(pos) > 0
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
self.regions.append(region)
|
|
106
|
+
|
|
107
|
+
def evaluate_value(self, evaluation_points):
|
|
108
|
+
"""
|
|
109
|
+
Evaluate the scalar field value of the geological feature at the locations
|
|
110
|
+
specified
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
evaluation_points : numpy array
|
|
115
|
+
location to evaluate the scalar value
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
values : numpy array
|
|
120
|
+
numpy array containing evaluated values
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
#TODO need to add a generic type checker for all methods
|
|
124
|
+
#if evaluation_points is not a numpy array try and convert
|
|
125
|
+
#otherwise error
|
|
126
|
+
self.builder.up_to_date()
|
|
127
|
+
# check if the points are within the display region
|
|
128
|
+
v = np.zeros(evaluation_points.shape[0])
|
|
129
|
+
v[:] = np.nan
|
|
130
|
+
mask = np.zeros(evaluation_points.shape[0]).astype(bool)
|
|
131
|
+
mask[:] = True
|
|
132
|
+
# check regions
|
|
133
|
+
for r in self.regions:
|
|
134
|
+
mask = np.logical_and(mask, r(evaluation_points))
|
|
135
|
+
# apply faulting after working out which regions are visible
|
|
136
|
+
if self.faults_enabled:
|
|
137
|
+
for f in self.faults:
|
|
138
|
+
evaluation_points = f.apply_to_points(evaluation_points)
|
|
139
|
+
v[mask] = self.interpolator.evaluate_value(evaluation_points[mask, :])
|
|
140
|
+
return v
|
|
141
|
+
|
|
142
|
+
def evaluate_gradient(self, evaluation_points):
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
locations : numpy array
|
|
148
|
+
location where the gradient is being evaluated
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
self.builder.up_to_date()
|
|
155
|
+
v = np.zeros(evaluation_points.shape)
|
|
156
|
+
v[:] = np.nan
|
|
157
|
+
mask = np.zeros(evaluation_points.shape[0]).astype(bool)
|
|
158
|
+
mask[:] = True
|
|
159
|
+
# check regions
|
|
160
|
+
for r in self.regions:
|
|
161
|
+
mask = np.logical_and(mask, r(evaluation_points))
|
|
162
|
+
|
|
163
|
+
# apply faulting after working out which regions are visible
|
|
164
|
+
if self.faults_enabled:
|
|
165
|
+
for f in self.faults:
|
|
166
|
+
evaluation_points = f.apply_to_points(evaluation_points)
|
|
167
|
+
v[mask, :] = self.interpolator.evaluate_gradient(evaluation_points)
|
|
168
|
+
|
|
169
|
+
return v
|
|
170
|
+
|
|
171
|
+
def evaluate_gradient_misfit(self):
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
misfit : np.array(N,dtype=double)
|
|
177
|
+
dot product between interpolated gradient and constraints
|
|
178
|
+
"""
|
|
179
|
+
self.builder.up_to_date()
|
|
180
|
+
grad = self.interpolator.get_gradient_constraints()
|
|
181
|
+
norm = self.interpolator.get_norm_constraints()
|
|
182
|
+
|
|
183
|
+
dot = []
|
|
184
|
+
if grad.shape[0] > 0:
|
|
185
|
+
grad /=np.linalg.norm(grad,axis=1)[:,None]
|
|
186
|
+
model_grad = self.evaluate_gradient(grad[:,:3])
|
|
187
|
+
dot.append(np.einsum('ij,ij->i',model_grad,grad[:,:3:6]).tolist())
|
|
188
|
+
|
|
189
|
+
if norm.shape[0] > 0:
|
|
190
|
+
norm /=np.linalg.norm(norm,axis=1)[:,None]
|
|
191
|
+
model_norm = self.evaluate_gradient(norm[:, :3])
|
|
192
|
+
dot.append(np.einsum('ij,ij->i', model_norm, norm[:,:3:6]))
|
|
193
|
+
|
|
194
|
+
return np.array(dot)
|
|
195
|
+
|
|
196
|
+
def evaluate_value_misfit(self):
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
misfit : np.array(N,dtype=double)
|
|
202
|
+
difference between interpolated scalar field and value constraints
|
|
203
|
+
"""
|
|
204
|
+
self.builder.up_to_date()
|
|
205
|
+
|
|
206
|
+
locations = self.interpolator.get_value_constraints()
|
|
207
|
+
diff = np.abs(locations[:, 3] - self.evaluate_value(locations[:, :3]))
|
|
208
|
+
diff/=(self.max()-self.min())
|
|
209
|
+
return diff
|
|
210
|
+
|
|
211
|
+
def mean(self):
|
|
212
|
+
"""
|
|
213
|
+
Calculate average of the support values
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
mean : double
|
|
218
|
+
average value of the feature evaluated on a (10,10,10) grid in model area
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
if self.model is None:
|
|
222
|
+
return 0
|
|
223
|
+
return np.mean(self.evaluate_value(self.model.regular_grid((10,10,10))))
|
|
224
|
+
|
|
225
|
+
def min(self):
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
min : double
|
|
231
|
+
min value of the feature evaluated on a (10,10,10) grid in model area
|
|
232
|
+
"""
|
|
233
|
+
if self.model is None:
|
|
234
|
+
return 0
|
|
235
|
+
return np.nanmin(
|
|
236
|
+
self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
237
|
+
|
|
238
|
+
def max(self):
|
|
239
|
+
"""
|
|
240
|
+
Calculate average of the support values
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
max : double
|
|
245
|
+
max value of the feature evaluated on a (10,10,10) grid in model area
|
|
246
|
+
"""
|
|
247
|
+
if self.model is None:
|
|
248
|
+
return 0
|
|
249
|
+
return np.nanmax(
|
|
250
|
+
self.evaluate_value(self.model.regular_grid((10, 10, 10))))
|
|
251
|
+
|
|
252
|
+
|