LoopStructural 1.6.7__py3-none-any.whl → 1.6.9__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 +1 -0
- LoopStructural/datatypes/_bounding_box.py +69 -10
- LoopStructural/datatypes/_point.py +18 -11
- LoopStructural/datatypes/_structured_grid.py +37 -9
- LoopStructural/datatypes/_surface.py +3 -3
- LoopStructural/export/geoh5.py +4 -2
- LoopStructural/interpolators/__init__.py +1 -0
- LoopStructural/interpolators/_discrete_interpolator.py +18 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +64 -11
- LoopStructural/interpolators/_geological_interpolator.py +9 -0
- LoopStructural/interpolators/_interpolator_builder.py +98 -19
- LoopStructural/interpolators/_interpolator_factory.py +2 -3
- LoopStructural/interpolators/_surfe_wrapper.py +3 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +3 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +3 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +28 -5
- LoopStructural/interpolators/supports/_3d_structured_grid.py +2 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +21 -13
- LoopStructural/interpolators/supports/_base_support.py +4 -0
- LoopStructural/interpolators/supports/_support_factory.py +12 -4
- LoopStructural/modelling/core/geological_model.py +5 -6
- LoopStructural/modelling/features/_base_geological_feature.py +9 -3
- LoopStructural/modelling/features/_cross_product_geological_feature.py +1 -2
- LoopStructural/modelling/features/_geological_feature.py +3 -5
- LoopStructural/modelling/features/_lambda_geological_feature.py +11 -1
- LoopStructural/modelling/features/_projected_vector_feature.py +1 -2
- LoopStructural/modelling/features/_unconformity_feature.py +0 -1
- LoopStructural/modelling/features/builders/_base_builder.py +4 -2
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +21 -25
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +9 -4
- LoopStructural/modelling/features/fault/_fault_segment.py +1 -1
- LoopStructural/modelling/features/fold/__init__.py +1 -3
- LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +0 -1
- LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +0 -1
- LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +0 -1
- LoopStructural/modelling/input/process_data.py +4 -2
- LoopStructural/utils/_surface.py +2 -2
- LoopStructural/utils/_transformation.py +28 -13
- LoopStructural/utils/colours.py +3 -1
- LoopStructural/version.py +1 -1
- {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/METADATA +7 -6
- {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/RECORD +45 -45
- {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/WHEEL +1 -1
- {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info/licenses}/LICENSE +0 -0
- {LoopStructural-1.6.7.dist-info → loopstructural-1.6.9.dist-info}/top_level.txt +0 -0
|
@@ -1,55 +1,134 @@
|
|
|
1
1
|
from LoopStructural.interpolators import (
|
|
2
|
-
GeologicalInterpolator,
|
|
3
2
|
InterpolatorFactory,
|
|
4
3
|
InterpolatorType,
|
|
5
4
|
)
|
|
6
5
|
from LoopStructural.datatypes import BoundingBox
|
|
7
|
-
from typing import Optional
|
|
6
|
+
from typing import Union, Optional
|
|
8
7
|
import numpy as np
|
|
9
8
|
|
|
9
|
+
from LoopStructural.interpolators._geological_interpolator import GeologicalInterpolator
|
|
10
|
+
|
|
10
11
|
|
|
11
12
|
class InterpolatorBuilder:
|
|
12
13
|
def __init__(
|
|
13
14
|
self,
|
|
14
15
|
interpolatortype: Union[str, InterpolatorType],
|
|
15
16
|
bounding_box: BoundingBox,
|
|
16
|
-
nelements: int =
|
|
17
|
-
buffer: float =
|
|
17
|
+
nelements: Optional[int] = None,
|
|
18
|
+
buffer: Optional[float] = None,
|
|
18
19
|
**kwargs,
|
|
19
20
|
):
|
|
21
|
+
"""This class helps initialise and setup a geological interpolator.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
interpolatortype : Union[str, InterpolatorType]
|
|
26
|
+
type of interpolator
|
|
27
|
+
bounding_box : BoundingBox
|
|
28
|
+
bounding box of the area to interpolate
|
|
29
|
+
nelements : int, optional
|
|
30
|
+
degrees of freedom of the interpolator, by default 1000
|
|
31
|
+
buffer : float, optional
|
|
32
|
+
how much of a buffer around the bounding box should be used, by default 0.2
|
|
33
|
+
"""
|
|
20
34
|
self.interpolatortype = interpolatortype
|
|
21
35
|
self.bounding_box = bounding_box
|
|
22
36
|
self.nelements = nelements
|
|
23
37
|
self.buffer = buffer
|
|
24
38
|
self.kwargs = kwargs
|
|
25
|
-
self.interpolator : Optional[GeologicalInterpolator]= None
|
|
26
|
-
|
|
27
|
-
def create_interpolator(self) -> 'InterpolatorBuilder':
|
|
28
39
|
self.interpolator = InterpolatorFactory.create_interpolator(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return self
|
|
40
|
+
interpolatortype=self.interpolatortype,
|
|
41
|
+
boundingbox=self.bounding_box,
|
|
42
|
+
nelements=self.nelements,
|
|
43
|
+
buffer=self.buffer,
|
|
44
|
+
**self.kwargs,
|
|
45
|
+
)
|
|
36
46
|
|
|
37
|
-
def
|
|
47
|
+
def add_value_constraints(self, value_constraints: np.ndarray) -> 'InterpolatorBuilder':
|
|
48
|
+
"""Add value constraints to the interpolator
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
value_constraints : np.ndarray
|
|
53
|
+
x,y,z,value of the constraints
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
InterpolatorBuilder
|
|
58
|
+
reference to the builder
|
|
59
|
+
"""
|
|
38
60
|
if self.interpolator:
|
|
39
61
|
self.interpolator.set_value_constraints(value_constraints)
|
|
40
62
|
return self
|
|
41
63
|
|
|
42
|
-
def
|
|
64
|
+
def add_gradient_constraints(self, gradient_constraints: np.ndarray) -> 'InterpolatorBuilder':
|
|
65
|
+
"""Add gradient constraints to the interpolator
|
|
66
|
+
Where g1 and g2 are two vectors that are orthogonal to the gradient
|
|
67
|
+
$'(X)\cdot g1 = 0$ and $'(X)\cdot g2 = 0$
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
gradient_constraints : np.ndarray
|
|
72
|
+
x,y,z,gradient_x,gradient_y,gradient_z of the constraints
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
InterpolatorBuilder
|
|
77
|
+
reference to the builder
|
|
78
|
+
"""
|
|
79
|
+
|
|
43
80
|
if self.interpolator:
|
|
44
81
|
self.interpolator.set_gradient_constraints(gradient_constraints)
|
|
45
82
|
return self
|
|
46
83
|
|
|
47
|
-
def
|
|
84
|
+
def add_normal_constraints(self, normal_constraints: np.ndarray) -> 'InterpolatorBuilder':
|
|
85
|
+
"""Add normal constraints to the interpolator
|
|
86
|
+
Where n is the normal vector to the surface
|
|
87
|
+
$f'(X).dx = nx$
|
|
88
|
+
$f'(X).dy = ny$
|
|
89
|
+
$f'(X).dz = nz$
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
normal_constraints : np.ndarray
|
|
93
|
+
x,y,z,nx,ny,nz of the constraints
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
InterpolatorBuilder
|
|
98
|
+
reference to the builder
|
|
99
|
+
"""
|
|
48
100
|
if self.interpolator:
|
|
49
101
|
self.interpolator.set_normal_constraints(normal_constraints)
|
|
50
102
|
return self
|
|
103
|
+
#TODO implement/check inequalities
|
|
104
|
+
# def add_inequality_constraints(self, inequality_constraints: np.ndarray) -> 'InterpolatorBuilder':
|
|
105
|
+
# if self.interpolator:
|
|
106
|
+
# self.interpolator.set_value_inequality_constraints(inequality_constraints)
|
|
107
|
+
# return self
|
|
108
|
+
# def add_inequality_pair_constraints(self, inequality_pair_constraints: np.ndarray) -> 'InterpolatorBuilder':
|
|
109
|
+
# if self.interpolator:
|
|
110
|
+
# self.interpolator.set_inequality_pairs_constraints(inequality_pair_constraints)
|
|
111
|
+
# return self
|
|
112
|
+
|
|
113
|
+
def setup_interpolator(self, **kwargs) -> 'InterpolatorBuilder':
|
|
114
|
+
"""This adds all of the constraints to the interpolator and
|
|
115
|
+
sets the regularisation constraints
|
|
51
116
|
|
|
52
|
-
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
InterpolatorBuilder
|
|
120
|
+
reference to the builder
|
|
121
|
+
"""
|
|
53
122
|
if self.interpolator:
|
|
54
123
|
self.interpolator.setup(**kwargs)
|
|
55
|
-
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
def build(self)->GeologicalInterpolator:
|
|
127
|
+
"""Builds the interpolator and returns it
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
GeologicalInterpolator
|
|
132
|
+
The interpolator fitting all of the constraints provided
|
|
133
|
+
"""
|
|
134
|
+
return self.interpolator
|
|
@@ -18,14 +18,13 @@ class InterpolatorFactory:
|
|
|
18
18
|
nelements: Optional[int] = None,
|
|
19
19
|
element_volume: Optional[float] = None,
|
|
20
20
|
support=None,
|
|
21
|
-
buffer: float =
|
|
21
|
+
buffer: Optional[float] = None,
|
|
22
22
|
):
|
|
23
23
|
if interpolatortype is None:
|
|
24
24
|
raise ValueError("No interpolator type specified")
|
|
25
25
|
if boundingbox is None:
|
|
26
26
|
raise ValueError("No bounding box specified")
|
|
27
|
-
|
|
28
|
-
raise ValueError("No number of elements specified")
|
|
27
|
+
|
|
29
28
|
if isinstance(interpolatortype, str):
|
|
30
29
|
interpolatortype = interpolator_string_map[interpolatortype]
|
|
31
30
|
if support is None:
|
|
@@ -35,6 +35,9 @@ class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
|
35
35
|
def set_region(self, **kwargs):
|
|
36
36
|
pass
|
|
37
37
|
|
|
38
|
+
def set_nelements(self, nelements) -> int:
|
|
39
|
+
return 0
|
|
40
|
+
|
|
38
41
|
def add_gradient_constraints(self, w=1):
|
|
39
42
|
points = self.get_gradient_constraints()
|
|
40
43
|
if points.shape[0] > 0:
|
|
@@ -66,6 +66,9 @@ class StructuredGrid2D(BaseSupport):
|
|
|
66
66
|
def n_nodes(self):
|
|
67
67
|
return self.nsteps[0] * self.nsteps[1]
|
|
68
68
|
|
|
69
|
+
def set_nelements(self, nelements) -> int:
|
|
70
|
+
raise NotImplementedError("Cannot set number of elements for 2D structured grid")
|
|
71
|
+
|
|
69
72
|
@property
|
|
70
73
|
def n_elements(self):
|
|
71
74
|
return self.nsteps_cells[0] * self.nsteps_cells[1]
|
|
@@ -3,6 +3,7 @@ from abc import abstractmethod
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from LoopStructural.utils import getLogger
|
|
5
5
|
from . import SupportType
|
|
6
|
+
from typing import Tuple
|
|
6
7
|
|
|
7
8
|
logger = getLogger(__name__)
|
|
8
9
|
|
|
@@ -34,6 +35,11 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
34
35
|
# we use property decorators to update these when different parts of
|
|
35
36
|
# the geometry need to change
|
|
36
37
|
# inisialise the private attributes
|
|
38
|
+
# cast to numpy array, to allow list like input
|
|
39
|
+
origin = np.array(origin)
|
|
40
|
+
nsteps = np.array(nsteps)
|
|
41
|
+
step_vector = np.array(step_vector)
|
|
42
|
+
|
|
37
43
|
self.type = SupportType.BaseStructured
|
|
38
44
|
if np.any(step_vector == 0):
|
|
39
45
|
logger.warning(f"Step vector {step_vector} has zero values")
|
|
@@ -41,10 +47,10 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
41
47
|
raise LoopException("nsteps cannot be zero")
|
|
42
48
|
if np.any(nsteps < 0):
|
|
43
49
|
raise LoopException("nsteps cannot be negative")
|
|
44
|
-
if np.any(nsteps < 3):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
# if np.any(nsteps < 3):
|
|
51
|
+
# raise LoopException(
|
|
52
|
+
# "step vector cannot be less than 3. Try increasing the resolution of the interpolator"
|
|
53
|
+
# )
|
|
48
54
|
self._nsteps = np.array(nsteps, dtype=int) + 1
|
|
49
55
|
self._step_vector = np.array(step_vector)
|
|
50
56
|
self._origin = np.array(origin)
|
|
@@ -56,6 +62,23 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
56
62
|
self.rotation_xy = rotation_xy
|
|
57
63
|
self.interpolator = None
|
|
58
64
|
|
|
65
|
+
@property
|
|
66
|
+
def volume(self):
|
|
67
|
+
return np.prod(self.maximum - self.origin)
|
|
68
|
+
|
|
69
|
+
def set_nelements(self, nelements) -> int:
|
|
70
|
+
box_vol = self.volume
|
|
71
|
+
ele_vol = box_vol / nelements
|
|
72
|
+
# calculate the step vector of a regular cube
|
|
73
|
+
step_vector = np.zeros(3)
|
|
74
|
+
|
|
75
|
+
step_vector[:] = ele_vol ** (1.0 / 3.0)
|
|
76
|
+
|
|
77
|
+
# number of steps is the length of the box / step vector
|
|
78
|
+
nsteps = np.ceil((self.maximum - self.origin) / step_vector).astype(int)
|
|
79
|
+
self.nsteps = nsteps
|
|
80
|
+
return self.n_elements
|
|
81
|
+
|
|
59
82
|
def to_dict(self):
|
|
60
83
|
return {
|
|
61
84
|
"origin": self.origin,
|
|
@@ -229,7 +252,7 @@ class BaseStructuredSupport(BaseSupport):
|
|
|
229
252
|
""" """
|
|
230
253
|
return np.einsum("ijk,ik->ij", self.rotation_xy[None, :, :], pos)
|
|
231
254
|
|
|
232
|
-
def position_to_cell_index(self, pos: np.ndarray) -> np.ndarray:
|
|
255
|
+
def position_to_cell_index(self, pos: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
233
256
|
"""Get the indexes (i,j,k) of a cell
|
|
234
257
|
that a point is inside
|
|
235
258
|
|
|
@@ -61,19 +61,24 @@ class UnStructuredTetMesh(BaseSupport):
|
|
|
61
61
|
length = self.maximum - self.minimum
|
|
62
62
|
self.minimum -= length * 0.1
|
|
63
63
|
self.maximum += length * 0.1
|
|
64
|
-
if
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
if self.elements.shape[0] < 2000:
|
|
65
|
+
self.aabb_grid = StructuredGrid(self.minimum, nsteps=[2, 2, 2], step_vector=[1, 1, 1])
|
|
66
|
+
else:
|
|
67
|
+
if aabb_nsteps is None:
|
|
68
|
+
box_vol = np.prod(self.maximum - self.minimum)
|
|
69
|
+
element_volume = box_vol / (len(self.elements) / 20)
|
|
70
|
+
# calculate the step vector of a regular cube
|
|
71
|
+
step_vector = np.zeros(3)
|
|
72
|
+
step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
73
|
+
# number of steps is the length of the box / step vector
|
|
74
|
+
aabb_nsteps = np.ceil((self.maximum - self.minimum) / step_vector).astype(int)
|
|
75
|
+
# make sure there is at least one cell in every dimension
|
|
76
|
+
aabb_nsteps[aabb_nsteps < 2] = 2
|
|
77
|
+
aabb_nsteps = np.array(aabb_nsteps, dtype=int)
|
|
78
|
+
step_vector = (self.maximum - self.minimum) / (aabb_nsteps - 1)
|
|
79
|
+
self.aabb_grid = StructuredGrid(
|
|
80
|
+
self.minimum, nsteps=aabb_nsteps, step_vector=step_vector
|
|
81
|
+
)
|
|
77
82
|
# make a big table to store which tetra are in which element.
|
|
78
83
|
# if this takes up too much memory it could be simplified by using sparse matrices or dict but
|
|
79
84
|
# at the expense of speed
|
|
@@ -87,6 +92,9 @@ class UnStructuredTetMesh(BaseSupport):
|
|
|
87
92
|
self._init_face_table()
|
|
88
93
|
self._initialise_aabb()
|
|
89
94
|
|
|
95
|
+
def set_nelements(self, nelements):
|
|
96
|
+
raise NotImplementedError("Cannot set number of elements for unstructured mesh")
|
|
97
|
+
|
|
90
98
|
@property
|
|
91
99
|
def nodes(self):
|
|
92
100
|
return self._nodes
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from LoopStructural.interpolators.supports import support_map, SupportType
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class SupportFactory:
|
|
@@ -20,13 +22,19 @@ class SupportFactory:
|
|
|
20
22
|
|
|
21
23
|
@staticmethod
|
|
22
24
|
def create_support_from_bbox(
|
|
23
|
-
support_type, bounding_box, nelements, element_volume=None, buffer: float =
|
|
25
|
+
support_type, bounding_box, nelements, element_volume=None, buffer: Optional[float] = None
|
|
24
26
|
):
|
|
25
27
|
if isinstance(support_type, str):
|
|
26
28
|
support_type = SupportType._member_map_[support_type].numerator
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
if buffer is not None:
|
|
30
|
+
bounding_box = bounding_box.with_buffer(buffer=buffer)
|
|
31
|
+
if element_volume is not None:
|
|
32
|
+
nelements = int(np.prod(bounding_box.length) / element_volume)
|
|
33
|
+
if nelements is not None:
|
|
34
|
+
bounding_box.nelements = nelements
|
|
29
35
|
|
|
30
36
|
return support_map[support_type](
|
|
31
|
-
origin=
|
|
37
|
+
origin=bounding_box.origin,
|
|
38
|
+
step_vector=bounding_box.step_vector,
|
|
39
|
+
nsteps=bounding_box.nsteps,
|
|
32
40
|
)
|
|
@@ -711,11 +711,10 @@ class GeologicalModel:
|
|
|
711
711
|
# build feature
|
|
712
712
|
# series_feature = series_builder.build(**kwargs)
|
|
713
713
|
series_feature = series_builder.feature
|
|
714
|
-
series_builder.
|
|
714
|
+
series_builder.update_build_arguments(kwargs | {"domain": True, 'tol': tol})
|
|
715
715
|
# this support is built for the entire model domain? Possibly would
|
|
716
716
|
# could just pass a regular grid of points - mask by any above unconformities??
|
|
717
|
-
|
|
718
|
-
series_builder.build_arguments["tol"] = tol
|
|
717
|
+
|
|
719
718
|
series_feature.type = FeatureType.INTERPOLATED
|
|
720
719
|
self._add_feature(series_feature)
|
|
721
720
|
return series_feature
|
|
@@ -850,7 +849,7 @@ class GeologicalModel:
|
|
|
850
849
|
|
|
851
850
|
# series_feature = series_builder.build(**kwargs)
|
|
852
851
|
series_feature = series_builder.feature
|
|
853
|
-
series_builder.
|
|
852
|
+
series_builder.update_build_arguments(kwargs)
|
|
854
853
|
series_feature.type = FeatureType.INTERPOLATED
|
|
855
854
|
series_feature.fold = fold
|
|
856
855
|
|
|
@@ -1264,7 +1263,7 @@ class GeologicalModel:
|
|
|
1264
1263
|
# build feature
|
|
1265
1264
|
# domain_fault = domain_fault_feature_builder.build(**kwargs)
|
|
1266
1265
|
domain_fault = domain_fault_feature_builder.feature
|
|
1267
|
-
domain_fault_feature_builder.
|
|
1266
|
+
domain_fault_feature_builder.update_build_arguments(kwargs)
|
|
1268
1267
|
domain_fault.type = FeatureType.DOMAINFAULT
|
|
1269
1268
|
self._add_feature(domain_fault)
|
|
1270
1269
|
self._add_domain_fault_below(domain_fault)
|
|
@@ -1809,7 +1808,7 @@ class GeologicalModel:
|
|
|
1809
1808
|
grid = self.bounding_box.structured_grid(name=name)
|
|
1810
1809
|
|
|
1811
1810
|
grid.cell_properties['stratigraphy'] = self.evaluate_model(
|
|
1812
|
-
self.rescale(self.bounding_box.
|
|
1811
|
+
self.rescale(self.bounding_box.cell_centres())
|
|
1813
1812
|
)
|
|
1814
1813
|
return grid, self.stratigraphic_ids()
|
|
1815
1814
|
|
|
@@ -334,11 +334,17 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
334
334
|
bounding_box = self.model.bounding_box
|
|
335
335
|
grid = bounding_box.structured_grid(name=self.name)
|
|
336
336
|
value = self.evaluate_value(
|
|
337
|
-
|
|
337
|
+
bounding_box.regular_grid(local=False, order='F')
|
|
338
338
|
)
|
|
339
|
+
if self.model is not None:
|
|
340
|
+
|
|
341
|
+
value = self.evaluate_value(
|
|
342
|
+
self.model.scale(bounding_box.regular_grid(local=False, order='F'))
|
|
343
|
+
)
|
|
344
|
+
|
|
339
345
|
grid.properties[self.name] = value
|
|
340
346
|
|
|
341
|
-
value = self.evaluate_value(bounding_box.
|
|
347
|
+
value = self.evaluate_value(bounding_box.cell_centres(order='F'))
|
|
342
348
|
grid.cell_properties[self.name] = value
|
|
343
349
|
return grid
|
|
344
350
|
|
|
@@ -359,7 +365,7 @@ class BaseFeature(metaclass=ABCMeta):
|
|
|
359
365
|
if self.model is None:
|
|
360
366
|
raise ValueError("Must specify bounding box")
|
|
361
367
|
bounding_box = self.model.bounding_box
|
|
362
|
-
points = bounding_box.
|
|
368
|
+
points = bounding_box.cell_centres()
|
|
363
369
|
value = self.evaluate_gradient(points)
|
|
364
370
|
if self.model is not None:
|
|
365
371
|
points = self.model.rescale(points)
|
|
@@ -6,7 +6,6 @@ from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient
|
|
|
6
6
|
from ...modelling.features import BaseFeature
|
|
7
7
|
from ...utils import getLogger
|
|
8
8
|
from ...modelling.features import FeatureType
|
|
9
|
-
from ...interpolators import GeologicalInterpolator
|
|
10
9
|
import numpy as np
|
|
11
10
|
from typing import Optional, List, Union
|
|
12
11
|
from ...datatypes import ValuePoints, VectorPoints
|
|
@@ -40,10 +39,10 @@ class GeologicalFeature(BaseFeature):
|
|
|
40
39
|
def __init__(
|
|
41
40
|
self,
|
|
42
41
|
name: str,
|
|
43
|
-
|
|
44
|
-
builder=None,
|
|
42
|
+
builder,
|
|
45
43
|
regions: list = [],
|
|
46
44
|
faults: list = [],
|
|
45
|
+
interpolator=None,
|
|
47
46
|
model=None,
|
|
48
47
|
):
|
|
49
48
|
"""Default constructor for geological feature
|
|
@@ -61,8 +60,8 @@ class GeologicalFeature(BaseFeature):
|
|
|
61
60
|
"""
|
|
62
61
|
BaseFeature.__init__(self, name, model, faults, regions, builder)
|
|
63
62
|
self.name = name
|
|
64
|
-
self.interpolator = interpolator
|
|
65
63
|
self.builder = builder
|
|
64
|
+
self.interpolator = self.builder.interpolator if self.builder is not None else interpolator
|
|
66
65
|
self.type = FeatureType.INTERPOLATED
|
|
67
66
|
|
|
68
67
|
def to_json(self):
|
|
@@ -262,7 +261,6 @@ class GeologicalFeature(BaseFeature):
|
|
|
262
261
|
regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
|
|
263
262
|
builder=self.builder,
|
|
264
263
|
model=self.model,
|
|
265
|
-
interpolator=self.interpolator,
|
|
266
264
|
)
|
|
267
265
|
return feature
|
|
268
266
|
|
|
@@ -12,7 +12,6 @@ logger = getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class LambdaGeologicalFeature(BaseFeature):
|
|
15
|
-
|
|
16
15
|
def __init__(
|
|
17
16
|
self,
|
|
18
17
|
function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
|
|
@@ -91,3 +90,14 @@ class LambdaGeologicalFeature(BaseFeature):
|
|
|
91
90
|
|
|
92
91
|
def get_data(self, value_map: Optional[dict] = None):
|
|
93
92
|
return
|
|
93
|
+
|
|
94
|
+
def copy(self, name: Optional[str] = None):
|
|
95
|
+
return LambdaGeologicalFeature(
|
|
96
|
+
self.function,
|
|
97
|
+
name if name is not None else f'{self.name}_copy',
|
|
98
|
+
self.gradient_function,
|
|
99
|
+
self.model,
|
|
100
|
+
self.regions,
|
|
101
|
+
self.faults,
|
|
102
|
+
self.builder,
|
|
103
|
+
)
|
|
@@ -24,7 +24,6 @@ class UnconformityFeature(GeologicalFeature):
|
|
|
24
24
|
regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
|
|
25
25
|
builder=feature.builder,
|
|
26
26
|
model=feature.model,
|
|
27
|
-
interpolator=feature.interpolator,
|
|
28
27
|
)
|
|
29
28
|
self.value = value
|
|
30
29
|
self.type = FeatureType.UNCONFORMITY if onlap is False else FeatureType.ONLAPUNCONFORMITY
|
|
@@ -47,10 +47,12 @@ class BaseBuilder:
|
|
|
47
47
|
def build_arguments(self):
|
|
48
48
|
return self._build_arguments
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
def build_arguments(self, build_arguments):
|
|
50
|
+
def update_build_arguments(self, build_arguments):
|
|
52
51
|
# self._build_arguments = {}
|
|
52
|
+
logger.info(f"Setting build arguments for {self.name}")
|
|
53
53
|
for k, i in build_arguments.items():
|
|
54
|
+
logger.info(f"Setting {k} to {i} for {self.name}")
|
|
55
|
+
logger.info(f"{k} is currrently {self._build_arguments.get(k, None)}")
|
|
54
56
|
if i != self._build_arguments.get(k, None):
|
|
55
57
|
logger.info(f"Setting {k} to {i} for {self.name}")
|
|
56
58
|
self._build_arguments[k] = i
|
|
@@ -25,7 +25,6 @@ from ....modelling.features.builders import BaseBuilder
|
|
|
25
25
|
from ....utils.helper import (
|
|
26
26
|
get_data_bounding_box_map as get_data_bounding_box,
|
|
27
27
|
)
|
|
28
|
-
from ....utils import RegionEverywhere
|
|
29
28
|
from ....interpolators import DiscreteInterpolator
|
|
30
29
|
from ....interpolators import InterpolatorFactory
|
|
31
30
|
|
|
@@ -39,7 +38,6 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
39
38
|
bounding_box,
|
|
40
39
|
nelements: int = 1000,
|
|
41
40
|
name="Feature",
|
|
42
|
-
interpolation_region=None,
|
|
43
41
|
model=None,
|
|
44
42
|
**kwargs,
|
|
45
43
|
):
|
|
@@ -61,7 +59,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
61
59
|
nelements=nelements,
|
|
62
60
|
buffer=kwargs.get("buffer", 0.2),
|
|
63
61
|
)
|
|
64
|
-
|
|
62
|
+
|
|
65
63
|
if not issubclass(type(interpolator), GeologicalInterpolator):
|
|
66
64
|
raise TypeError(
|
|
67
65
|
"interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
|
|
@@ -78,21 +76,17 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
78
76
|
)
|
|
79
77
|
self.data = pd.DataFrame(columns=header)
|
|
80
78
|
self.data_added = False
|
|
81
|
-
self._interpolation_region = None
|
|
82
|
-
self.interpolation_region = interpolation_region
|
|
83
|
-
if self.interpolation_region is not None:
|
|
84
|
-
self._interpolator.set_region(region=self.interpolation_region)
|
|
85
79
|
|
|
86
80
|
self._feature = GeologicalFeature(
|
|
87
81
|
self._name,
|
|
88
|
-
self._interpolator,
|
|
89
82
|
builder=self,
|
|
90
83
|
regions=[],
|
|
91
84
|
faults=self.faults,
|
|
92
85
|
)
|
|
93
86
|
self._orthogonal_features = {}
|
|
94
87
|
self._equality_constraints = {}
|
|
95
|
-
|
|
88
|
+
# add default parameters
|
|
89
|
+
self.update_build_arguments({'cpw':1.0,'npw':1.0,'regularisation':1.0,'nelements':self.interpolator.n_elements})
|
|
96
90
|
def set_not_up_to_date(self, caller):
|
|
97
91
|
logger.info(
|
|
98
92
|
f"Setting {self.name} to not up to date from an instance of {caller.__class__.__name__}"
|
|
@@ -104,20 +98,12 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
104
98
|
def interpolator(self):
|
|
105
99
|
return self._interpolator
|
|
106
100
|
|
|
107
|
-
@
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if interpolation_region is not None:
|
|
114
|
-
self._interpolation_region = interpolation_region
|
|
115
|
-
self._interpolator.set_region(region=self._interpolation_region)
|
|
116
|
-
else:
|
|
117
|
-
self._interpolation_region = RegionEverywhere()
|
|
118
|
-
self._interpolator.set_region(region=self._interpolation_region)
|
|
119
|
-
logger.info(f'Setting interpolation region {self.name}')
|
|
120
|
-
self._up_to_date = False
|
|
101
|
+
@interpolator.setter
|
|
102
|
+
def interpolator(self, interpolator):
|
|
103
|
+
if not issubclass(type(interpolator), GeologicalInterpolator):
|
|
104
|
+
raise TypeError(
|
|
105
|
+
"interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
|
|
106
|
+
)
|
|
121
107
|
|
|
122
108
|
def add_data_from_data_frame(self, data_frame, overwrite=False):
|
|
123
109
|
"""
|
|
@@ -183,6 +169,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
183
169
|
|
|
184
170
|
"""
|
|
185
171
|
if self.data_added:
|
|
172
|
+
logger.info("Data already added to interpolator")
|
|
186
173
|
return
|
|
187
174
|
# first move the data for the fault
|
|
188
175
|
logger.info(f"Adding {len(self.faults)} faults to {self.name}")
|
|
@@ -475,7 +462,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
475
462
|
is big enough to capture the faulted feature.
|
|
476
463
|
"""
|
|
477
464
|
if self.interpolator.support is not None:
|
|
478
|
-
|
|
465
|
+
logger.info(f"Checking interpolation geometry for {self.name}")
|
|
479
466
|
origin = self.interpolator.support.origin
|
|
480
467
|
maximum = self.interpolator.support.maximum
|
|
481
468
|
pts = self.model.bounding_box.with_buffer(buffer).regular_grid(local=True)
|
|
@@ -488,7 +475,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
488
475
|
]
|
|
489
476
|
self.interpolator.support.origin = origin
|
|
490
477
|
self.interpolator.support.maximum = maximum
|
|
491
|
-
|
|
478
|
+
self.update_build_arguments({'nelements':self.interpolator.n_elements})
|
|
492
479
|
def build(self, data_region=None, **kwargs):
|
|
493
480
|
"""
|
|
494
481
|
Runs the interpolation and builds the geological feature
|
|
@@ -511,6 +498,15 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
511
498
|
for f in self.faults:
|
|
512
499
|
f.builder.update()
|
|
513
500
|
domain = kwargs.get("domain", None)
|
|
501
|
+
if 'nelements' in kwargs:
|
|
502
|
+
# if the number of elements has changed then update the interpolator
|
|
503
|
+
logger.info(f'Interpolator has {self.interpolator.n_elements} elements')
|
|
504
|
+
logger.info(f'Kwargs has {kwargs["nelements"]} elements')
|
|
505
|
+
if self.interpolator.n_elements != kwargs['nelements']:
|
|
506
|
+
logger.info('Setting nelements to {} for {}'.format(kwargs['nelements'], self.name))
|
|
507
|
+
self.build_arguments['nelements'] = self.interpolator.set_nelements(kwargs['nelements'])
|
|
508
|
+
logger.info(f'Interpolator nelements {self.interpolator.n_elements}')
|
|
509
|
+
self._up_to_date = False
|
|
514
510
|
if domain:
|
|
515
511
|
self.check_interpolation_geometry(None)
|
|
516
512
|
self.add_data_to_interpolator(**kwargs)
|