LoopStructural 1.6.2__py3-none-any.whl → 1.6.3__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 +19 -4
- LoopStructural/datatypes/_point.py +31 -0
- 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/_discrete_fold_interpolator.py +11 -4
- LoopStructural/interpolators/_discrete_interpolator.py +100 -53
- LoopStructural/interpolators/_finite_difference_interpolator.py +68 -78
- LoopStructural/interpolators/_geological_interpolator.py +27 -10
- 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 +24 -7
- LoopStructural/interpolators/supports/_3d_structured_grid.py +38 -9
- 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 -1
- LoopStructural/modelling/core/geological_model.py +0 -2
- LoopStructural/modelling/features/_analytical_feature.py +25 -16
- LoopStructural/modelling/features/_geological_feature.py +47 -11
- LoopStructural/modelling/features/builders/_base_builder.py +8 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +45 -14
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +29 -13
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +5 -0
- LoopStructural/modelling/features/fault/__init__.py +1 -1
- LoopStructural/modelling/features/fault/_fault_function.py +19 -1
- LoopStructural/modelling/features/fault/_fault_segment.py +15 -49
- 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 +6 -0
- LoopStructural/modelling/input/project_file.py +24 -3
- LoopStructural/utils/_surface.py +5 -2
- LoopStructural/utils/colours.py +26 -0
- LoopStructural/utils/features.py +5 -0
- LoopStructural/utils/maths.py +51 -0
- LoopStructural/version.py +1 -1
- LoopStructural-1.6.3.dist-info/METADATA +146 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.dist-info}/RECORD +53 -48
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.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.3.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.3.dist-info}/top_level.txt +0 -0
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
Wrapper for using surfepy
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from ..utils.
|
|
5
|
+
from ..utils.maths import get_vectors
|
|
6
6
|
from ..interpolators import GeologicalInterpolator
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
10
|
from ..utils import getLogger
|
|
11
|
+
import surfepy
|
|
12
|
+
from typing import Optional
|
|
11
13
|
|
|
12
14
|
logger = getLogger(__name__)
|
|
13
|
-
import surfepy
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
@@ -19,6 +20,8 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
19
20
|
def __init__(self, method="single_surface"):
|
|
20
21
|
GeologicalInterpolator.__init__(self)
|
|
21
22
|
self.surfe = None
|
|
23
|
+
if not method:
|
|
24
|
+
method = "single_surface"
|
|
22
25
|
if method == "single_surface":
|
|
23
26
|
logger.info("Using single surface interpolator")
|
|
24
27
|
self.surfe = surfepy.Surfe_API(1)
|
|
@@ -29,7 +32,10 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
29
32
|
logger.info("Using surfe horizon")
|
|
30
33
|
self.surfe = surfepy.Surfe_API(4)
|
|
31
34
|
|
|
32
|
-
def
|
|
35
|
+
def set_region(self, **kwargs):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def add_gradient_constraints(self, w=1):
|
|
33
39
|
points = self.get_gradient_constraints()
|
|
34
40
|
if points.shape[0] > 0:
|
|
35
41
|
logger.info("Adding ")
|
|
@@ -40,12 +46,12 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
40
46
|
self.surfe.SetTangentConstraints(strike_vector)
|
|
41
47
|
self.surfe.SetTangentConstraints(dip_vector)
|
|
42
48
|
|
|
43
|
-
def
|
|
49
|
+
def add_norm_constraints(self, w=1):
|
|
44
50
|
points = self.get_norm_constraints()
|
|
45
51
|
if points.shape[0] > 0:
|
|
46
52
|
self.surfe.SetPlanarConstraints(points[:, :6])
|
|
47
53
|
|
|
48
|
-
def
|
|
54
|
+
def add_value_constraints(self, w=1):
|
|
49
55
|
|
|
50
56
|
points = self.get_value_constraints()
|
|
51
57
|
if points.shape[0] > 0:
|
|
@@ -59,15 +65,39 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
59
65
|
points[i, 3],
|
|
60
66
|
)
|
|
61
67
|
|
|
62
|
-
def
|
|
68
|
+
def add_interface_constraints(self, w=1):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def add_value_inequality_constraints(self, w=1):
|
|
72
|
+
## inequalities are causing a segfault
|
|
73
|
+
# points = self.get_value_inequality_constraints()
|
|
74
|
+
# if points.shape[0] > 0:
|
|
75
|
+
# #
|
|
76
|
+
# self.surfe.AddValueInequalityConstraints(points[:,0],points[:,1],points[:,2],points[:,3])
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def add_inequality_pairs_constraints(
|
|
80
|
+
self,
|
|
81
|
+
w: float = 1.0,
|
|
82
|
+
upper_bound=np.finfo(float).eps,
|
|
83
|
+
lower_bound=-np.inf,
|
|
84
|
+
pairs: Optional[list] = None,
|
|
85
|
+
):
|
|
86
|
+
# self.surfe.Add
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def reset(self):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def add_tangent_constraints(self, w=1):
|
|
63
93
|
points = self.get_tangent_constraints()
|
|
64
94
|
if points.shape[0] > 0:
|
|
65
95
|
self.surfe.SetTangentConstraints(points[:, :6])
|
|
66
96
|
|
|
67
|
-
def
|
|
97
|
+
def solve_system(self, **kwargs):
|
|
68
98
|
self.surfe.ComputeInterpolant()
|
|
69
99
|
|
|
70
|
-
def
|
|
100
|
+
def setup_interpolator(self, **kwargs):
|
|
71
101
|
"""
|
|
72
102
|
Setup the interpolator
|
|
73
103
|
|
|
@@ -90,10 +120,10 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
90
120
|
|
|
91
121
|
|
|
92
122
|
"""
|
|
93
|
-
self.
|
|
94
|
-
self.
|
|
95
|
-
self.
|
|
96
|
-
self.
|
|
123
|
+
self.add_gradient_constraints()
|
|
124
|
+
self.add_norm_constraints()
|
|
125
|
+
self.add_value_constraints()
|
|
126
|
+
self.add_tangent_constraints()
|
|
97
127
|
|
|
98
128
|
kernel = kwargs.get("kernel", "r3")
|
|
99
129
|
logger.info("Setting surfe RBF kernel to %s" % kernel)
|
|
@@ -338,3 +338,19 @@ class BaseUnstructured2d(BaseSupport):
|
|
|
338
338
|
"""
|
|
339
339
|
verts, c, tri, inside = self.get_element_for_location(pos, return_verts=False)
|
|
340
340
|
return self.evaluate_shape_derivatives(pos, tri)
|
|
341
|
+
|
|
342
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
343
|
+
"""
|
|
344
|
+
Create a vtk unstructured grid from the mesh
|
|
345
|
+
"""
|
|
346
|
+
import pyvista as pv
|
|
347
|
+
|
|
348
|
+
grid = pv.UnstructuredGrid()
|
|
349
|
+
grid.points = self.nodes
|
|
350
|
+
grid.cell_types = np.ones(self.elements.shape[0]) * pv.vtk.VTK_TRIANGLE
|
|
351
|
+
grid.cells = np.c_[np.ones(self.elements.shape[0]) * 3, self.elements]
|
|
352
|
+
for key, value in node_properties.items():
|
|
353
|
+
grid.point_data[key] = value
|
|
354
|
+
for key, value in cell_properties.items():
|
|
355
|
+
grid.cell_data[key] = value
|
|
356
|
+
return grid
|
|
@@ -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
|
|
@@ -134,7 +134,15 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
134
134
|
origin = np.array(origin)
|
|
135
135
|
length = self.maximum - origin
|
|
136
136
|
length /= self.step_vector
|
|
137
|
-
self._nsteps = np.ceil(length).astype(
|
|
137
|
+
self._nsteps = np.ceil(length).astype(np.int64)
|
|
138
|
+
self._nsteps[self._nsteps == 0] = (
|
|
139
|
+
3 # need to have a minimum of 3 elements to apply the finite difference mask
|
|
140
|
+
)
|
|
141
|
+
if np.any(~(self._nsteps > 0)):
|
|
142
|
+
logger.error(
|
|
143
|
+
f"Cannot resize the interpolation support. The proposed number of steps is {self._nsteps}, these must be all > 0"
|
|
144
|
+
)
|
|
145
|
+
raise ValueError("Cannot resize the interpolation support.")
|
|
138
146
|
self._origin = origin
|
|
139
147
|
self.onGeometryChange()
|
|
140
148
|
|
|
@@ -150,7 +158,13 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
150
158
|
maximum = np.array(maximum, dtype=float)
|
|
151
159
|
length = maximum - self.origin
|
|
152
160
|
length /= self.step_vector
|
|
153
|
-
self._nsteps = np.ceil(length).astype(
|
|
161
|
+
self._nsteps = np.ceil(length).astype(np.int64)
|
|
162
|
+
self._nsteps[self._nsteps == 0] = 3
|
|
163
|
+
if np.any(~(self._nsteps > 0)):
|
|
164
|
+
logger.error(
|
|
165
|
+
f"Cannot resize the interpolation support. The proposed number of steps is {self._nsteps}, these must be all > 0"
|
|
166
|
+
)
|
|
167
|
+
raise ValueError("Cannot resize the interpolation support.")
|
|
154
168
|
self.onGeometryChange()
|
|
155
169
|
|
|
156
170
|
@property
|
|
@@ -236,6 +250,7 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
236
250
|
cell_indexes[inside, 0] = x[inside] // self.step_vector[None, 0]
|
|
237
251
|
cell_indexes[inside, 1] = y[inside] // self.step_vector[None, 1]
|
|
238
252
|
cell_indexes[inside, 2] = z[inside] // self.step_vector[None, 2]
|
|
253
|
+
|
|
239
254
|
return cell_indexes, inside
|
|
240
255
|
|
|
241
256
|
def position_to_cell_global_index(self, pos):
|
|
@@ -331,12 +346,9 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
331
346
|
return corner_indexes
|
|
332
347
|
|
|
333
348
|
def position_to_cell_corners(self, pos):
|
|
334
|
-
|
|
335
349
|
cell_indexes, inside = self.position_to_cell_index(pos)
|
|
336
350
|
corner_indexes = self.cell_corner_indexes(cell_indexes)
|
|
337
|
-
|
|
338
351
|
globalidx = self.global_node_indices(corner_indexes)
|
|
339
|
-
|
|
340
352
|
# if global index is not inside the support set to -1
|
|
341
353
|
globalidx[~inside] = -1
|
|
342
354
|
return globalidx, inside
|
|
@@ -451,7 +463,7 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
451
463
|
# all elements are the same size
|
|
452
464
|
return 1.0
|
|
453
465
|
|
|
454
|
-
def vtk(self):
|
|
466
|
+
def vtk(self, node_properties={}, cell_properties={}):
|
|
455
467
|
try:
|
|
456
468
|
import pyvista as pv
|
|
457
469
|
except ImportError:
|
|
@@ -464,4 +476,9 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
464
476
|
[np.zeros(self.elements.shape[0], dtype=int)[:, None] + 8, self.elements]
|
|
465
477
|
)
|
|
466
478
|
elements = elements.flatten()
|
|
467
|
-
|
|
479
|
+
grid = pv.UnstructuredGrid(elements, celltype, self.nodes)
|
|
480
|
+
for key, value in node_properties.items():
|
|
481
|
+
grid[key] = value
|
|
482
|
+
for key, value in cell_properties.items():
|
|
483
|
+
grid.cell_arrays[key] = value
|
|
484
|
+
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)
|
|
@@ -468,3 +474,26 @@ class StructuredGrid(BaseStructuredSupport):
|
|
|
468
474
|
"type": self.type.numerator,
|
|
469
475
|
**super().to_dict(),
|
|
470
476
|
}
|
|
477
|
+
|
|
478
|
+
def get_operators(self, weights: Dict[str, float]) -> Dict[str, Tuple[np.ndarray, float]]:
|
|
479
|
+
"""Gets the operators specific to this support
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
weights : Dict[str, float]
|
|
484
|
+
weight value per operator
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
operators
|
|
489
|
+
A dictionary with a numpy array and float weight
|
|
490
|
+
"""
|
|
491
|
+
operators = {
|
|
492
|
+
'dxy': (Operator.Dxy_mask, weights['dxy'] / 4),
|
|
493
|
+
'dyz': (Operator.Dyz_mask, weights['dyz'] / 4),
|
|
494
|
+
'dxz': (Operator.Dxz_mask, weights['dxz'] / 4),
|
|
495
|
+
'dxx': (Operator.Dxx_mask, weights['dxx'] / 1),
|
|
496
|
+
'dyy': (Operator.Dyy_mask, weights['dyy'] / 1),
|
|
497
|
+
'dzz': (Operator.Dzz_mask, weights['dzz'] / 1),
|
|
498
|
+
}
|
|
499
|
+
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 (
|
|
@@ -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"
|
|
@@ -28,7 +28,16 @@ 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
42
|
self.vector = np.array(vector, dtype=float)
|
|
34
43
|
self.origin = np.array(origin, dtype=float)
|
|
@@ -48,16 +57,16 @@ class AnalyticalGeologicalFeature(BaseFeature):
|
|
|
48
57
|
json["origin"] = self.origin.tolist()
|
|
49
58
|
return json
|
|
50
59
|
|
|
51
|
-
def evaluate_value(self,
|
|
52
|
-
|
|
53
|
-
if len(
|
|
54
|
-
|
|
55
|
-
if len(
|
|
60
|
+
def evaluate_value(self, pos: np.ndarray, ignore_regions=False):
|
|
61
|
+
pos = np.array(pos)
|
|
62
|
+
if len(pos.shape) == 1:
|
|
63
|
+
pos = pos[None, :]
|
|
64
|
+
if len(pos.shape) != 2:
|
|
56
65
|
raise ValueError("xyz must be a 1D or 2D array")
|
|
57
|
-
xyz2 = np.zeros(
|
|
58
|
-
xyz2[:] =
|
|
66
|
+
xyz2 = np.zeros(pos.shape)
|
|
67
|
+
xyz2[:] = pos[:]
|
|
59
68
|
for f in self.faults:
|
|
60
|
-
xyz2[:] = f.apply_to_points(
|
|
69
|
+
xyz2[:] = f.apply_to_points(pos)
|
|
61
70
|
if self.model is not None:
|
|
62
71
|
xyz2[:] = self.model.rescale(xyz2, inplace=False)
|
|
63
72
|
xyz2[:] = xyz2 - self.origin
|
|
@@ -65,13 +74,13 @@ class AnalyticalGeologicalFeature(BaseFeature):
|
|
|
65
74
|
distance = normal[0] * xyz2[:, 0] + normal[1] * xyz2[:, 1] + normal[2] * xyz2[:, 2]
|
|
66
75
|
return distance / np.linalg.norm(self.vector)
|
|
67
76
|
|
|
68
|
-
def evaluate_gradient(self,
|
|
69
|
-
|
|
70
|
-
if len(
|
|
71
|
-
|
|
72
|
-
if len(
|
|
73
|
-
raise ValueError("
|
|
74
|
-
v = np.zeros(
|
|
77
|
+
def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False):
|
|
78
|
+
pos = np.array(pos)
|
|
79
|
+
if len(pos.shape) == 1:
|
|
80
|
+
pos = pos[None, :]
|
|
81
|
+
if len(pos.shape) != 2:
|
|
82
|
+
raise ValueError("pos must be a 1D or 2D array")
|
|
83
|
+
v = np.zeros(pos.shape)
|
|
75
84
|
v[:, :] = self.vector[None, :]
|
|
76
85
|
return v
|
|
77
86
|
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Geological features
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
|
|
5
6
|
from ...modelling.features import BaseFeature
|
|
6
7
|
from ...utils import getLogger
|
|
7
8
|
from ...modelling.features import FeatureType
|
|
8
|
-
from ...interpolators import GeologicalInterpolator
|
|
9
|
+
from ...interpolators import GeologicalInterpolator
|
|
9
10
|
import numpy as np
|
|
10
11
|
from typing import Optional, List, Union
|
|
11
12
|
from ...datatypes import ValuePoints, VectorPoints
|
|
@@ -131,7 +132,9 @@ class GeologicalFeature(BaseFeature):
|
|
|
131
132
|
v[nanmask] = v[~nanmask][i]
|
|
132
133
|
return v
|
|
133
134
|
|
|
134
|
-
def evaluate_gradient(
|
|
135
|
+
def evaluate_gradient(
|
|
136
|
+
self, pos: np.ndarray, ignore_regions=False, element_scale_parameter=None
|
|
137
|
+
) -> np.ndarray:
|
|
135
138
|
"""
|
|
136
139
|
|
|
137
140
|
Parameters
|
|
@@ -147,6 +150,17 @@ class GeologicalFeature(BaseFeature):
|
|
|
147
150
|
if pos.shape[1] != 3:
|
|
148
151
|
raise LoopValueError("Need Nx3 array of xyz points to evaluate gradient")
|
|
149
152
|
logger.info(f'Calculating gradient for {self.name}')
|
|
153
|
+
if element_scale_parameter is None:
|
|
154
|
+
if self.model is not None:
|
|
155
|
+
element_scale_parameter = np.min(self.model.bounding_box.step_vector) / 10
|
|
156
|
+
else:
|
|
157
|
+
element_scale_parameter = 1
|
|
158
|
+
else:
|
|
159
|
+
try:
|
|
160
|
+
element_scale_parameter = float(element_scale_parameter)
|
|
161
|
+
except ValueError:
|
|
162
|
+
logger.error("element_scale_parameter must be a float")
|
|
163
|
+
element_scale_parameter = 1
|
|
150
164
|
|
|
151
165
|
self.builder.up_to_date()
|
|
152
166
|
|
|
@@ -156,16 +170,38 @@ class GeologicalFeature(BaseFeature):
|
|
|
156
170
|
# evaluate the faults on the nodes of the faulted feature support
|
|
157
171
|
# then evaluate the gradient at these points
|
|
158
172
|
if len(self.faults) > 0:
|
|
173
|
+
# generate a regular tetrahedron for each point
|
|
174
|
+
# we will then move these points by the fault and then recalculate the gradient.
|
|
175
|
+
# this should work...
|
|
176
|
+
resolved = False
|
|
177
|
+
tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
|
|
178
|
+
|
|
179
|
+
while resolved:
|
|
180
|
+
for f in self.faults:
|
|
181
|
+
v = (
|
|
182
|
+
f[0]
|
|
183
|
+
.evaluate_value(tetrahedron.reshape(-1, 3), fillnan='nearest')
|
|
184
|
+
.reshape(tetrahedron.shape[0], 4)
|
|
185
|
+
)
|
|
186
|
+
flag = np.logical_or(np.all(v > 0, axis=1), np.all(v < 0, axis=1))
|
|
187
|
+
if np.any(~flag):
|
|
188
|
+
logger.warning(
|
|
189
|
+
f"Points are too close to fault {f[0].name}. Refining the tetrahedron"
|
|
190
|
+
)
|
|
191
|
+
element_scale_parameter *= 0.5
|
|
192
|
+
tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
|
|
193
|
+
|
|
194
|
+
resolved = True
|
|
195
|
+
|
|
196
|
+
tetrahedron_faulted = self._apply_faults(np.array(tetrahedron.reshape(-1, 3))).reshape(
|
|
197
|
+
tetrahedron.shape
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
values = self.interpolator.evaluate_value(tetrahedron_faulted.reshape(-1, 3)).reshape(
|
|
201
|
+
(-1, 4)
|
|
202
|
+
)
|
|
203
|
+
v[mask, :] = gradient_from_tetrahedron(tetrahedron[mask, :, :], values[mask])
|
|
159
204
|
|
|
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
205
|
return v
|
|
170
206
|
pos = self._apply_faults(pos)
|
|
171
207
|
if mask.dtype not in [int, bool]:
|
|
@@ -29,6 +29,12 @@ class BaseBuilder:
|
|
|
29
29
|
self._build_arguments = {}
|
|
30
30
|
self.faults = []
|
|
31
31
|
|
|
32
|
+
def set_not_up_to_date(self, caller):
|
|
33
|
+
logger.info(
|
|
34
|
+
f"Setting {self.name} to not up to date from an instance of {caller.__class__.__name__}"
|
|
35
|
+
)
|
|
36
|
+
self._up_to_date = False
|
|
37
|
+
|
|
32
38
|
@property
|
|
33
39
|
def model(self):
|
|
34
40
|
return self._model
|
|
@@ -46,6 +52,7 @@ class BaseBuilder:
|
|
|
46
52
|
# self._build_arguments = {}
|
|
47
53
|
for k, i in build_arguments.items():
|
|
48
54
|
if i != self._build_arguments.get(k, None):
|
|
55
|
+
logger.info(f"Setting {k} to {i} for {self.name}")
|
|
49
56
|
self._build_arguments[k] = i
|
|
50
57
|
## if build_arguments change then flag to reinterpolate
|
|
51
58
|
self._up_to_date = False
|
|
@@ -107,5 +114,6 @@ class BaseBuilder:
|
|
|
107
114
|
-------
|
|
108
115
|
|
|
109
116
|
"""
|
|
117
|
+
logger.info(f'Adding fault {fault.name} to {self.name}')
|
|
110
118
|
self._up_to_date = False
|
|
111
119
|
self.faults.append(fault)
|