LoopStructural 1.6.2__py3-none-any.whl → 1.6.6__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/datatypes/_bounding_box.py +77 -7
- LoopStructural/datatypes/_point.py +67 -7
- LoopStructural/datatypes/_structured_grid.py +17 -0
- LoopStructural/datatypes/_surface.py +17 -0
- LoopStructural/export/omf_wrapper.py +49 -21
- LoopStructural/interpolators/__init__.py +13 -0
- LoopStructural/interpolators/_api.py +81 -13
- LoopStructural/interpolators/_builders.py +141 -141
- LoopStructural/interpolators/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +78 -88
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- LoopStructural/interpolators/_interpolator_builder.py +55 -0
- LoopStructural/interpolators/_interpolator_factory.py +7 -18
- LoopStructural/interpolators/_p1interpolator.py +3 -3
- LoopStructural/interpolators/_surfe_wrapper.py +42 -12
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +16 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +44 -9
- LoopStructural/interpolators/supports/_3d_base_structured.py +28 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -12
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +8 -2
- LoopStructural/interpolators/supports/__init__.py +7 -0
- LoopStructural/interpolators/supports/_base_support.py +7 -0
- LoopStructural/modelling/__init__.py +1 -3
- LoopStructural/modelling/core/geological_model.py +11 -12
- LoopStructural/modelling/features/__init__.py +1 -0
- LoopStructural/modelling/features/_analytical_feature.py +48 -18
- LoopStructural/modelling/features/_base_geological_feature.py +37 -8
- LoopStructural/modelling/features/_cross_product_geological_feature.py +7 -0
- LoopStructural/modelling/features/_geological_feature.py +50 -12
- LoopStructural/modelling/features/_projected_vector_feature.py +112 -0
- LoopStructural/modelling/features/_structural_frame.py +16 -18
- LoopStructural/modelling/features/_unconformity_feature.py +3 -3
- LoopStructural/modelling/features/builders/_base_builder.py +8 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +47 -16
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +7 -2
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_function_feature.py +3 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +50 -53
- LoopStructural/modelling/features/fold/__init__.py +1 -2
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -23
- LoopStructural/modelling/features/fold/_foldframe.py +4 -4
- LoopStructural/modelling/features/fold/_svariogram.py +81 -46
- LoopStructural/modelling/features/fold/fold_function/__init__.py +27 -0
- LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +253 -0
- LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +153 -0
- LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +46 -0
- LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +151 -0
- LoopStructural/modelling/input/process_data.py +47 -26
- LoopStructural/modelling/input/project_file.py +49 -23
- LoopStructural/modelling/intrusions/intrusion_feature.py +3 -0
- LoopStructural/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +17 -5
- LoopStructural/utils/_transformation.py +98 -14
- LoopStructural/utils/colours.py +50 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +51 -0
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.6.dist-info/METADATA +160 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/RECORD +66 -59
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/WHEEL +1 -1
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +0 -149
- LoopStructural-1.6.2.dist-info/METADATA +0 -81
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.6.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,11 @@ Cartesian grid for fold interpolator
|
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
|
-
from typing import Tuple
|
|
9
8
|
import numpy as np
|
|
10
9
|
from . import SupportType
|
|
11
10
|
from ._base_support import BaseSupport
|
|
11
|
+
from typing import Dict, Tuple
|
|
12
|
+
from .._operator import Operator
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
@@ -33,7 +34,7 @@ class StructuredGrid2D(BaseSupport):
|
|
|
33
34
|
step_vector - 2d list or numpy array of int
|
|
34
35
|
"""
|
|
35
36
|
self.type = SupportType.StructuredGrid2D
|
|
36
|
-
self.nsteps = np.array(nsteps)
|
|
37
|
+
self.nsteps = np.ceil(np.array(nsteps)).astype(int)
|
|
37
38
|
self.step_vector = np.array(step_vector)
|
|
38
39
|
self.origin = np.array(origin)
|
|
39
40
|
self.maximum = origin + self.nsteps * self.step_vector
|
|
@@ -261,13 +262,22 @@ class StructuredGrid2D(BaseSupport):
|
|
|
261
262
|
if "indexes" in kwargs:
|
|
262
263
|
indexes = kwargs["indexes"]
|
|
263
264
|
if "indexes" not in kwargs:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
265
|
+
gi = np.arange(self.n_nodes)
|
|
266
|
+
indexes = self.global_index_to_node_index(gi)
|
|
267
|
+
edge_mask = (
|
|
268
|
+
(indexes[:, 0] > 0)
|
|
269
|
+
& (indexes[:, 0] < self.nsteps[0] - 1)
|
|
270
|
+
& (indexes[:, 1] > 0)
|
|
271
|
+
& (indexes[:, 1] < self.nsteps[1] - 1)
|
|
272
|
+
)
|
|
273
|
+
indexes = indexes[edge_mask, :].T
|
|
274
|
+
# ii = []
|
|
275
|
+
# jj = []
|
|
276
|
+
# for i in range(1, self.nsteps[0] - 1):
|
|
277
|
+
# for j in range(1, self.nsteps[1] - 1):
|
|
278
|
+
# ii.append(i)
|
|
279
|
+
# jj.append(j)
|
|
280
|
+
# indexes = np.array([ii, jj])
|
|
271
281
|
# indexes = np.array(indexes).T
|
|
272
282
|
if indexes.ndim != 2:
|
|
273
283
|
print(indexes.ndim)
|
|
@@ -460,3 +470,28 @@ class StructuredGrid2D(BaseSupport):
|
|
|
460
470
|
|
|
461
471
|
def onGeometryChange(self):
|
|
462
472
|
pass
|
|
473
|
+
|
|
474
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
475
|
+
raise NotImplementedError("VTK output not implemented for structured grid")
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
def get_operators(self, weights: Dict[str, float]) -> Dict[str, Tuple[np.ndarray, float]]:
|
|
479
|
+
"""Get
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
weights : Dict[str, float]
|
|
484
|
+
_description_
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
Dict[str, Tuple[np.ndarray, float]]
|
|
489
|
+
_description_
|
|
490
|
+
"""
|
|
491
|
+
# in a map we only want the xy operators
|
|
492
|
+
operators = {
|
|
493
|
+
'dxy': (Operator.Dxy_mask[1, :, :], weights['dxy'] * 2),
|
|
494
|
+
'dxx': (Operator.Dxx_mask[1, :, :], weights['dxx']),
|
|
495
|
+
'dyy': (Operator.Dyy_mask[1, :, :], weights['dyy']),
|
|
496
|
+
}
|
|
497
|
+
return operators
|
|
@@ -41,6 +41,10 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
41
41
|
raise LoopException("nsteps cannot be zero")
|
|
42
42
|
if np.any(nsteps < 0):
|
|
43
43
|
raise LoopException("nsteps cannot be negative")
|
|
44
|
+
if np.any(nsteps < 3):
|
|
45
|
+
raise LoopException(
|
|
46
|
+
"step vector cannot be less than 3. Try increasing the resolution of the interpolator"
|
|
47
|
+
)
|
|
44
48
|
self._nsteps = np.array(nsteps, dtype=int) + 1
|
|
45
49
|
self._step_vector = np.array(step_vector)
|
|
46
50
|
self._origin = np.array(origin)
|
|
@@ -134,7 +138,15 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
134
138
|
origin = np.array(origin)
|
|
135
139
|
length = self.maximum - origin
|
|
136
140
|
length /= self.step_vector
|
|
137
|
-
self._nsteps = np.ceil(length).astype(
|
|
141
|
+
self._nsteps = np.ceil(length).astype(np.int64)
|
|
142
|
+
self._nsteps[self._nsteps == 0] = (
|
|
143
|
+
3 # need to have a minimum of 3 elements to apply the finite difference mask
|
|
144
|
+
)
|
|
145
|
+
if np.any(~(self._nsteps > 0)):
|
|
146
|
+
logger.error(
|
|
147
|
+
f"Cannot resize the interpolation support. The proposed number of steps is {self._nsteps}, these must be all > 0"
|
|
148
|
+
)
|
|
149
|
+
raise ValueError("Cannot resize the interpolation support.")
|
|
138
150
|
self._origin = origin
|
|
139
151
|
self.onGeometryChange()
|
|
140
152
|
|
|
@@ -150,7 +162,13 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
150
162
|
maximum = np.array(maximum, dtype=float)
|
|
151
163
|
length = maximum - self.origin
|
|
152
164
|
length /= self.step_vector
|
|
153
|
-
self._nsteps = np.ceil(length).astype(
|
|
165
|
+
self._nsteps = np.ceil(length).astype(np.int64)
|
|
166
|
+
self._nsteps[self._nsteps == 0] = 3
|
|
167
|
+
if np.any(~(self._nsteps > 0)):
|
|
168
|
+
logger.error(
|
|
169
|
+
f"Cannot resize the interpolation support. The proposed number of steps is {self._nsteps}, these must be all > 0"
|
|
170
|
+
)
|
|
171
|
+
raise ValueError("Cannot resize the interpolation support.")
|
|
154
172
|
self.onGeometryChange()
|
|
155
173
|
|
|
156
174
|
@property
|
|
@@ -236,6 +254,7 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
236
254
|
cell_indexes[inside, 0] = x[inside] // self.step_vector[None, 0]
|
|
237
255
|
cell_indexes[inside, 1] = y[inside] // self.step_vector[None, 1]
|
|
238
256
|
cell_indexes[inside, 2] = z[inside] // self.step_vector[None, 2]
|
|
257
|
+
|
|
239
258
|
return cell_indexes, inside
|
|
240
259
|
|
|
241
260
|
def position_to_cell_global_index(self, pos):
|
|
@@ -331,12 +350,9 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
331
350
|
return corner_indexes
|
|
332
351
|
|
|
333
352
|
def position_to_cell_corners(self, pos):
|
|
334
|
-
|
|
335
353
|
cell_indexes, inside = self.position_to_cell_index(pos)
|
|
336
354
|
corner_indexes = self.cell_corner_indexes(cell_indexes)
|
|
337
|
-
|
|
338
355
|
globalidx = self.global_node_indices(corner_indexes)
|
|
339
|
-
|
|
340
356
|
# if global index is not inside the support set to -1
|
|
341
357
|
globalidx[~inside] = -1
|
|
342
358
|
return globalidx, inside
|
|
@@ -451,7 +467,7 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
451
467
|
# all elements are the same size
|
|
452
468
|
return 1.0
|
|
453
469
|
|
|
454
|
-
def vtk(self):
|
|
470
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
455
471
|
try:
|
|
456
472
|
import pyvista as pv
|
|
457
473
|
except ImportError:
|
|
@@ -464,4 +480,9 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
464
480
|
[np.zeros(self.elements.shape[0], dtype=int)[:, None] + 8, self.elements]
|
|
465
481
|
)
|
|
466
482
|
elements = elements.flatten()
|
|
467
|
-
|
|
483
|
+
grid = pv.UnstructuredGrid(elements, celltype, self.nodes)
|
|
484
|
+
for key, value in node_properties.items():
|
|
485
|
+
grid[key] = value
|
|
486
|
+
for key, value in cell_properties.items():
|
|
487
|
+
grid.cell_arrays[key] = value
|
|
488
|
+
return grid
|
|
@@ -5,8 +5,10 @@ Cartesian grid for fold interpolator
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
-
from .
|
|
8
|
+
from LoopStructural.interpolators._operator import Operator
|
|
9
9
|
|
|
10
|
+
from ._3d_base_structured import BaseStructuredSupport
|
|
11
|
+
from typing import Dict, Tuple
|
|
10
12
|
from . import SupportType
|
|
11
13
|
|
|
12
14
|
from LoopStructural.utils import getLogger
|
|
@@ -158,13 +160,18 @@ class StructuredGrid(BaseStructuredSupport):
|
|
|
158
160
|
if "indexes" in kwargs:
|
|
159
161
|
indexes = kwargs["indexes"]
|
|
160
162
|
if "indexes" not in kwargs:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
167
|
-
|
|
163
|
+
gi = np.arange(self.n_nodes)
|
|
164
|
+
indexes = self.global_index_to_node_index(gi)
|
|
165
|
+
edge_mask = (
|
|
166
|
+
(indexes[:, 0] > 0)
|
|
167
|
+
& (indexes[:, 0] < self.nsteps[0] - 1)
|
|
168
|
+
& (indexes[:, 1] > 0)
|
|
169
|
+
& (indexes[:, 1] < self.nsteps[1] - 1)
|
|
170
|
+
& (indexes[:, 2] > 0)
|
|
171
|
+
& (indexes[:, 2] < self.nsteps[2] - 1)
|
|
172
|
+
)
|
|
173
|
+
indexes = indexes[edge_mask, :].T
|
|
174
|
+
|
|
168
175
|
# indexes = np.array(indexes).T
|
|
169
176
|
if indexes.ndim != 2:
|
|
170
177
|
return
|
|
@@ -291,7 +298,6 @@ class StructuredGrid(BaseStructuredSupport):
|
|
|
291
298
|
)
|
|
292
299
|
idc, inside = self.position_to_cell_corners(evaluation_points)
|
|
293
300
|
# print(idc[inside,:], self.n_nodes,inside)
|
|
294
|
-
|
|
295
301
|
if idc.shape[0] != inside.shape[0]:
|
|
296
302
|
raise ValueError("index does not match number of nodes")
|
|
297
303
|
v = np.zeros(idc.shape)
|
|
@@ -336,9 +342,6 @@ class StructuredGrid(BaseStructuredSupport):
|
|
|
336
342
|
idc, inside = self.position_to_cell_corners(evaluation_points)
|
|
337
343
|
T = np.zeros((idc.shape[0], 3, 8))
|
|
338
344
|
T[inside, :, :] = self.get_element_gradient_for_location(evaluation_points[inside, :])[1]
|
|
339
|
-
# indices = np.array([self.position_to_cell_index(evaluation_points)])
|
|
340
|
-
# idc = self.global_indicies(indices.swapaxes(0,1))
|
|
341
|
-
# print(idc)
|
|
342
345
|
if np.max(idc[inside, :]) > property_array.shape[0]:
|
|
343
346
|
cix, ciy, ciz = self.position_to_cell_index(evaluation_points)
|
|
344
347
|
if not np.all(cix[inside] < self.nsteps_cells[0]):
|
|
@@ -468,3 +471,26 @@ class StructuredGrid(BaseStructuredSupport):
|
|
|
468
471
|
"type": self.type.numerator,
|
|
469
472
|
**super().to_dict(),
|
|
470
473
|
}
|
|
474
|
+
|
|
475
|
+
def get_operators(self, weights: Dict[str, float]) -> Dict[str, Tuple[np.ndarray, float]]:
|
|
476
|
+
"""Gets the operators specific to this support
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
weights : Dict[str, float]
|
|
481
|
+
weight value per operator
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
operators
|
|
486
|
+
A dictionary with a numpy array and float weight
|
|
487
|
+
"""
|
|
488
|
+
operators = {
|
|
489
|
+
'dxy': (Operator.Dxy_mask, weights['dxy'] / 4),
|
|
490
|
+
'dyz': (Operator.Dyz_mask, weights['dyz'] / 4),
|
|
491
|
+
'dxz': (Operator.Dxz_mask, weights['dxz'] / 4),
|
|
492
|
+
'dxx': (Operator.Dxx_mask, weights['dxx'] / 1),
|
|
493
|
+
'dyy': (Operator.Dyy_mask, weights['dyy'] / 1),
|
|
494
|
+
'dzz': (Operator.Dzz_mask, weights['dzz'] / 1),
|
|
495
|
+
}
|
|
496
|
+
return operators
|
|
@@ -729,8 +729,7 @@ class TetMesh(BaseStructuredSupport):
|
|
|
729
729
|
|
|
730
730
|
return neighbours
|
|
731
731
|
|
|
732
|
-
|
|
733
|
-
def vtk(self):
|
|
732
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
734
733
|
try:
|
|
735
734
|
import pyvista as pv
|
|
736
735
|
except ImportError:
|
|
@@ -743,4 +742,9 @@ class TetMesh(BaseStructuredSupport):
|
|
|
743
742
|
[np.zeros(self.elements.shape[0], dtype=int)[:, None] + 4, self.elements]
|
|
744
743
|
)
|
|
745
744
|
elements = elements.flatten()
|
|
746
|
-
|
|
745
|
+
grid = pv.UnstructuredGrid(elements, celltype, self.nodes)
|
|
746
|
+
for prop in node_properties:
|
|
747
|
+
grid[prop] = node_properties[prop]
|
|
748
|
+
for prop in cell_properties:
|
|
749
|
+
grid.cell_arrays[prop] = cell_properties[prop]
|
|
750
|
+
return grid
|
|
@@ -621,7 +621,7 @@ class UnStructuredTetMesh(BaseSupport):
|
|
|
621
621
|
"""
|
|
622
622
|
return self.neighbours
|
|
623
623
|
|
|
624
|
-
def vtk(self):
|
|
624
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
625
625
|
try:
|
|
626
626
|
import pyvista as pv
|
|
627
627
|
except ImportError:
|
|
@@ -634,4 +634,10 @@ class UnStructuredTetMesh(BaseSupport):
|
|
|
634
634
|
[np.zeros(self.elements.shape[0], dtype=int)[:, None] + 4, self.elements]
|
|
635
635
|
)
|
|
636
636
|
elements = elements.flatten()
|
|
637
|
-
|
|
637
|
+
grid = pv.UnstructuredGrid(elements, celltype, self.nodes)
|
|
638
|
+
for key, value in node_properties.items():
|
|
639
|
+
grid[key] = value
|
|
640
|
+
for key, value in cell_properties.items():
|
|
641
|
+
grid.cell_arrays[key] = value
|
|
642
|
+
|
|
643
|
+
return grid
|
|
@@ -18,6 +18,7 @@ class SupportType(IntEnum):
|
|
|
18
18
|
BaseStructured = 6
|
|
19
19
|
TetMesh = 10
|
|
20
20
|
P2UnstructuredTetMesh = 11
|
|
21
|
+
DataSupported = 12
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
from ._2d_base_unstructured import BaseUnstructured2d
|
|
@@ -29,6 +30,11 @@ from ._3d_unstructured_tetra import UnStructuredTetMesh
|
|
|
29
30
|
from ._3d_structured_tetra import TetMesh
|
|
30
31
|
from ._3d_p2_tetra import P2UnstructuredTetMesh
|
|
31
32
|
|
|
33
|
+
|
|
34
|
+
def no_support(*args, **kwargs):
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
32
38
|
support_map = {
|
|
33
39
|
SupportType.StructuredGrid2D: StructuredGrid2D,
|
|
34
40
|
SupportType.StructuredGrid: StructuredGrid,
|
|
@@ -37,6 +43,7 @@ support_map = {
|
|
|
37
43
|
SupportType.P2Unstructured2d: P2Unstructured2d,
|
|
38
44
|
SupportType.TetMesh: TetMesh,
|
|
39
45
|
SupportType.P2UnstructuredTetMesh: P2UnstructuredTetMesh,
|
|
46
|
+
SupportType.DataSupported: no_support,
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
from ._support_factory import SupportFactory
|
|
@@ -6,12 +6,12 @@ Geological modelling classes and functions
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"GeologicalModel",
|
|
8
8
|
"ProcessInputData",
|
|
9
|
-
"Loop3DView",
|
|
10
9
|
"Map2LoopProcessor",
|
|
11
10
|
"LoopProjectfileProcessor",
|
|
12
11
|
]
|
|
13
12
|
from ..utils import getLogger
|
|
14
13
|
from ..utils import LoopImportError
|
|
14
|
+
from .core.geological_model import GeologicalModel
|
|
15
15
|
|
|
16
16
|
logger = getLogger(__name__)
|
|
17
17
|
from ..modelling.input import (
|
|
@@ -25,5 +25,3 @@ except (LoopImportError, ImportError):
|
|
|
25
25
|
logger.warning(
|
|
26
26
|
"Cannot use LoopProjectfileProcessor: Loop project file cannot be imported, try installing LoopProjectFile"
|
|
27
27
|
)
|
|
28
|
-
# from LoopStructural.modelling.features import StructuralFrame
|
|
29
|
-
# from LoopStructural.modelling.features.fault import FaultSegment
|
|
@@ -649,7 +649,7 @@ class GeologicalModel:
|
|
|
649
649
|
for g in stratigraphic_column.keys():
|
|
650
650
|
for u in stratigraphic_column[g].keys():
|
|
651
651
|
stratigraphic_column[g][u]["colour"] = cmap_colours[ci, :]
|
|
652
|
-
|
|
652
|
+
ci += 1
|
|
653
653
|
self.stratigraphic_column = stratigraphic_column
|
|
654
654
|
|
|
655
655
|
def create_and_add_foliation(
|
|
@@ -825,8 +825,6 @@ class GeologicalModel:
|
|
|
825
825
|
|
|
826
826
|
fold = FoldEvent(fold_frame, name=f"Fold_{foliation_data}", invert_norm=invert_fold_norm)
|
|
827
827
|
|
|
828
|
-
if "fold_weights" not in kwargs:
|
|
829
|
-
kwargs["fold_weights"] = {}
|
|
830
828
|
if interpolatortype != "DFI":
|
|
831
829
|
logger.warning("Folded foliation only supports DFI interpolator, changing to DFI")
|
|
832
830
|
interpolatortype = "DFI"
|
|
@@ -1184,7 +1182,7 @@ class GeologicalModel:
|
|
|
1184
1182
|
logger.debug(f"Reached unconformity {f.name}")
|
|
1185
1183
|
break
|
|
1186
1184
|
logger.debug(f"Adding {uc_feature.name} as unconformity to {f.name}")
|
|
1187
|
-
if f.type == FeatureType.FAULT:
|
|
1185
|
+
if f.type == FeatureType.FAULT or f.type == FeatureType.INACTIVEFAULT:
|
|
1188
1186
|
continue
|
|
1189
1187
|
if f == feature:
|
|
1190
1188
|
continue
|
|
@@ -1419,7 +1417,7 @@ class GeologicalModel:
|
|
|
1419
1417
|
return fault
|
|
1420
1418
|
|
|
1421
1419
|
# TODO move rescale to bounding box/transformer
|
|
1422
|
-
def rescale(self, points: np.ndarray, inplace: bool =
|
|
1420
|
+
def rescale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1423
1421
|
"""
|
|
1424
1422
|
Convert from model scale to real world scale - in the future this
|
|
1425
1423
|
should also do transformations?
|
|
@@ -1442,7 +1440,7 @@ class GeologicalModel:
|
|
|
1442
1440
|
return points
|
|
1443
1441
|
|
|
1444
1442
|
# TODO move scale to bounding box/transformer
|
|
1445
|
-
def scale(self, points: np.ndarray, inplace: bool =
|
|
1443
|
+
def scale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1446
1444
|
"""Take points in UTM coordinates and reproject
|
|
1447
1445
|
into scaled model space
|
|
1448
1446
|
|
|
@@ -1811,7 +1809,7 @@ class GeologicalModel:
|
|
|
1811
1809
|
grid = self.bounding_box.structured_grid(name=name)
|
|
1812
1810
|
|
|
1813
1811
|
grid.cell_properties['stratigraphy'] = self.evaluate_model(
|
|
1814
|
-
self.bounding_box.cell_centers()
|
|
1812
|
+
self.rescale(self.bounding_box.cell_centers())
|
|
1815
1813
|
)
|
|
1816
1814
|
return grid, self.stratigraphic_ids()
|
|
1817
1815
|
|
|
@@ -1826,6 +1824,7 @@ class GeologicalModel:
|
|
|
1826
1824
|
):
|
|
1827
1825
|
path = pathlib.Path(filename)
|
|
1828
1826
|
extension = path.suffix
|
|
1827
|
+
parent = path.parent
|
|
1829
1828
|
name = path.stem
|
|
1830
1829
|
stratigraphic_surfaces = self.get_stratigraphic_surfaces()
|
|
1831
1830
|
if fault_surfaces:
|
|
@@ -1834,19 +1833,19 @@ class GeologicalModel:
|
|
|
1834
1833
|
if extension == ".geoh5" or extension == '.omf':
|
|
1835
1834
|
s.save(filename)
|
|
1836
1835
|
else:
|
|
1837
|
-
s.save(f'{name}_{s.name}
|
|
1836
|
+
s.save(f'{parent}/{name}_{s.name}{extension}')
|
|
1838
1837
|
if stratigraphic_surfaces:
|
|
1839
1838
|
for s in self.get_stratigraphic_surfaces():
|
|
1840
1839
|
if extension == ".geoh5" or extension == '.omf':
|
|
1841
1840
|
s.save(filename)
|
|
1842
1841
|
else:
|
|
1843
|
-
s.save(f'{name}_{s.name}
|
|
1842
|
+
s.save(f'{parent}/{name}_{s.name}{extension}')
|
|
1844
1843
|
if block_model:
|
|
1845
1844
|
grid, ids = self.get_block_model()
|
|
1846
1845
|
if extension == ".geoh5" or extension == '.omf':
|
|
1847
1846
|
grid.save(filename)
|
|
1848
1847
|
else:
|
|
1849
|
-
grid.save(f'{name}_block_model
|
|
1848
|
+
grid.save(f'{parent}/{name}_block_model{extension}')
|
|
1850
1849
|
if stratigraphic_data:
|
|
1851
1850
|
if self.stratigraphic_column is not None:
|
|
1852
1851
|
for group in self.stratigraphic_column.keys():
|
|
@@ -1856,7 +1855,7 @@ class GeologicalModel:
|
|
|
1856
1855
|
if extension == ".geoh5" or extension == '.omf':
|
|
1857
1856
|
data.save(filename)
|
|
1858
1857
|
else:
|
|
1859
|
-
data.save(f'{name}_{group}_data
|
|
1858
|
+
data.save(f'{parent}/{name}_{group}_data{extension}')
|
|
1860
1859
|
if fault_data:
|
|
1861
1860
|
for f in self.fault_names():
|
|
1862
1861
|
for d in self.__getitem__(f).get_data():
|
|
@@ -1864,4 +1863,4 @@ class GeologicalModel:
|
|
|
1864
1863
|
|
|
1865
1864
|
d.save(filename)
|
|
1866
1865
|
else:
|
|
1867
|
-
d.save(f'{name}_{group}
|
|
1866
|
+
d.save(f'{parent}/{name}_{group}{extension}')
|
|
@@ -28,10 +28,27 @@ class AnalyticalGeologicalFeature(BaseFeature):
|
|
|
28
28
|
list of FaultSegments that affect this feature
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
vector: np.ndarray,
|
|
35
|
+
origin: np.ndarray,
|
|
36
|
+
regions=[],
|
|
37
|
+
faults=[],
|
|
38
|
+
model=None,
|
|
39
|
+
builder=None,
|
|
40
|
+
):
|
|
32
41
|
BaseFeature.__init__(self, name, model, faults, regions, builder)
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
try:
|
|
43
|
+
self.vector = np.array(vector, dtype=float).reshape(3)
|
|
44
|
+
except ValueError:
|
|
45
|
+
logger.error("AnalyticalGeologicalFeature: vector must be a 3 element array")
|
|
46
|
+
raise ValueError("vector must be a 3 element array")
|
|
47
|
+
try:
|
|
48
|
+
self.origin = np.array(origin, dtype=float).reshape(3)
|
|
49
|
+
except ValueError:
|
|
50
|
+
logger.error("AnalyticalGeologicalFeature: origin must be a 3 element array")
|
|
51
|
+
raise ValueError("origin must be a 3 element array")
|
|
35
52
|
self.type = FeatureType.ANALYTICAL
|
|
36
53
|
|
|
37
54
|
def to_json(self):
|
|
@@ -48,16 +65,16 @@ class AnalyticalGeologicalFeature(BaseFeature):
|
|
|
48
65
|
json["origin"] = self.origin.tolist()
|
|
49
66
|
return json
|
|
50
67
|
|
|
51
|
-
def evaluate_value(self,
|
|
52
|
-
|
|
53
|
-
if len(
|
|
54
|
-
|
|
55
|
-
if len(
|
|
68
|
+
def evaluate_value(self, pos: np.ndarray, ignore_regions=False):
|
|
69
|
+
pos = np.array(pos)
|
|
70
|
+
if len(pos.shape) == 1:
|
|
71
|
+
pos = pos[None, :]
|
|
72
|
+
if len(pos.shape) != 2:
|
|
56
73
|
raise ValueError("xyz must be a 1D or 2D array")
|
|
57
|
-
xyz2 = np.zeros(
|
|
58
|
-
xyz2[:] =
|
|
74
|
+
xyz2 = np.zeros(pos.shape)
|
|
75
|
+
xyz2[:] = pos[:]
|
|
59
76
|
for f in self.faults:
|
|
60
|
-
xyz2[:] = f.apply_to_points(
|
|
77
|
+
xyz2[:] = f.apply_to_points(pos)
|
|
61
78
|
if self.model is not None:
|
|
62
79
|
xyz2[:] = self.model.rescale(xyz2, inplace=False)
|
|
63
80
|
xyz2[:] = xyz2 - self.origin
|
|
@@ -65,15 +82,28 @@ class AnalyticalGeologicalFeature(BaseFeature):
|
|
|
65
82
|
distance = normal[0] * xyz2[:, 0] + normal[1] * xyz2[:, 1] + normal[2] * xyz2[:, 2]
|
|
66
83
|
return distance / np.linalg.norm(self.vector)
|
|
67
84
|
|
|
68
|
-
def evaluate_gradient(self,
|
|
69
|
-
|
|
70
|
-
if len(
|
|
71
|
-
|
|
72
|
-
if len(
|
|
73
|
-
raise ValueError("
|
|
74
|
-
v = np.zeros(
|
|
85
|
+
def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False):
|
|
86
|
+
pos = np.array(pos)
|
|
87
|
+
if len(pos.shape) == 1:
|
|
88
|
+
pos = pos[None, :]
|
|
89
|
+
if len(pos.shape) != 2:
|
|
90
|
+
raise ValueError("pos must be a 1D or 2D array")
|
|
91
|
+
v = np.zeros(pos.shape)
|
|
75
92
|
v[:, :] = self.vector[None, :]
|
|
76
93
|
return v
|
|
77
94
|
|
|
78
95
|
def get_data(self, value_map: Optional[dict] = None):
|
|
79
96
|
return
|
|
97
|
+
|
|
98
|
+
def copy(self, name: Optional[str] = None):
|
|
99
|
+
if name is None:
|
|
100
|
+
name = self.name
|
|
101
|
+
return AnalyticalGeologicalFeature(
|
|
102
|
+
name,
|
|
103
|
+
self.vector.copy(),
|
|
104
|
+
self.origin.copy(),
|
|
105
|
+
list(self.regions),
|
|
106
|
+
list(self.faults),
|
|
107
|
+
self.model,
|
|
108
|
+
self.builder,
|
|
109
|
+
)
|
|
@@ -291,11 +291,29 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
291
291
|
if self.model is None:
|
|
292
292
|
raise ValueError("Must specify bounding box")
|
|
293
293
|
bounding_box = self.model.bounding_box
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
294
|
+
regions = self.regions
|
|
295
|
+
try:
|
|
296
|
+
self.regions = [
|
|
297
|
+
r for r in self.regions if r.name != self.name and r.parent.name != self.name
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
callable = lambda xyz: (
|
|
301
|
+
self.evaluate_value(self.model.scale(xyz))
|
|
302
|
+
if self.model is not None
|
|
303
|
+
else self.evaluate_value(xyz)
|
|
304
|
+
)
|
|
305
|
+
isosurfacer = LoopIsosurfacer(bounding_box, callable=callable)
|
|
306
|
+
if name is None and self.name is not None:
|
|
307
|
+
name = self.name
|
|
308
|
+
surfaces = isosurfacer.fit(value, name)
|
|
309
|
+
except Exception as e:
|
|
310
|
+
logger.error(f"Failed to create surface for {self.name} at value {value}")
|
|
311
|
+
logger.error(e)
|
|
312
|
+
surfaces = []
|
|
313
|
+
finally:
|
|
314
|
+
self.regions = regions
|
|
315
|
+
|
|
316
|
+
return surfaces
|
|
299
317
|
|
|
300
318
|
def scalar_field(self, bounding_box=None):
|
|
301
319
|
"""Create a scalar field for the feature
|
|
@@ -341,10 +359,10 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
341
359
|
if self.model is None:
|
|
342
360
|
raise ValueError("Must specify bounding box")
|
|
343
361
|
bounding_box = self.model.bounding_box
|
|
344
|
-
|
|
345
|
-
points = grid.points
|
|
362
|
+
points = bounding_box.cell_centers()
|
|
346
363
|
value = self.evaluate_gradient(points)
|
|
347
|
-
|
|
364
|
+
if self.model is not None:
|
|
365
|
+
points = self.model.rescale(points)
|
|
348
366
|
return VectorPoints(points, value, self.name)
|
|
349
367
|
|
|
350
368
|
@abstractmethod
|
|
@@ -362,3 +380,14 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
362
380
|
dictionary of data
|
|
363
381
|
"""
|
|
364
382
|
raise NotImplementedError
|
|
383
|
+
|
|
384
|
+
@abstractmethod
|
|
385
|
+
def copy(self, name: Optional[str] = None):
|
|
386
|
+
"""Copy the feature
|
|
387
|
+
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
BaseFeature
|
|
391
|
+
copied feature
|
|
392
|
+
"""
|
|
393
|
+
raise NotImplementedError
|
|
@@ -98,3 +98,10 @@ class CrossProductGeologicalFeature(BaseFeature):
|
|
|
98
98
|
|
|
99
99
|
def get_data(self, value_map: Optional[dict] = None):
|
|
100
100
|
return
|
|
101
|
+
|
|
102
|
+
def copy(self, name: Optional[str] = None):
|
|
103
|
+
if name is None:
|
|
104
|
+
name = f'{self.name}_copy'
|
|
105
|
+
return CrossProductGeologicalFeature(
|
|
106
|
+
name, self.geological_feature_a, self.geological_feature_b
|
|
107
|
+
)
|