LoopStructural 1.6.2__py3-none-any.whl → 1.6.5__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 +36 -2
- 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 -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 +2 -4
- LoopStructural/modelling/features/_analytical_feature.py +25 -16
- LoopStructural/modelling/features/_base_geological_feature.py +21 -8
- LoopStructural/modelling/features/_geological_feature.py +47 -11
- LoopStructural/modelling/features/_structural_frame.py +10 -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 +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 +40 -51
- 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/utils/__init__.py +1 -0
- LoopStructural/utils/_surface.py +11 -4
- 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.5.dist-info/METADATA +146 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.dist-info}/RECORD +57 -52
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.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.5.dist-info}/LICENSE +0 -0
- {LoopStructural-1.6.2.dist-info → LoopStructural-1.6.5.dist-info}/top_level.txt +0 -0
|
@@ -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]:
|
|
@@ -115,7 +115,7 @@ class StructuralFrame(BaseFeature):
|
|
|
115
115
|
"""
|
|
116
116
|
return self.features[i]
|
|
117
117
|
|
|
118
|
-
def evaluate_value(self,
|
|
118
|
+
def evaluate_value(self, pos, ignore_regions=False):
|
|
119
119
|
"""
|
|
120
120
|
Evaluate the value of the structural frame for the points.
|
|
121
121
|
Can optionally only evaluate one coordinate
|
|
@@ -129,14 +129,14 @@ class StructuralFrame(BaseFeature):
|
|
|
129
129
|
-------
|
|
130
130
|
|
|
131
131
|
"""
|
|
132
|
-
v = np.zeros(
|
|
132
|
+
v = np.zeros(pos.shape) # create new 3d array of correct length
|
|
133
133
|
v[:] = np.nan
|
|
134
|
-
v[:, 0] = self.features[0].evaluate_value(
|
|
135
|
-
v[:, 1] = self.features[1].evaluate_value(
|
|
136
|
-
v[:, 2] = self.features[2].evaluate_value(
|
|
134
|
+
v[:, 0] = self.features[0].evaluate_value(pos, ignore_regions=ignore_regions)
|
|
135
|
+
v[:, 1] = self.features[1].evaluate_value(pos, ignore_regions=ignore_regions)
|
|
136
|
+
v[:, 2] = self.features[2].evaluate_value(pos, ignore_regions=ignore_regions)
|
|
137
137
|
return v
|
|
138
138
|
|
|
139
|
-
def evaluate_gradient(self,
|
|
139
|
+
def evaluate_gradient(self, pos, i=None, ignore_regions=False):
|
|
140
140
|
"""
|
|
141
141
|
Evaluate the gradient of the structural frame.
|
|
142
142
|
Can optionally only evaluate the ith coordinate
|
|
@@ -151,19 +151,11 @@ class StructuralFrame(BaseFeature):
|
|
|
151
151
|
|
|
152
152
|
"""
|
|
153
153
|
if i is not None:
|
|
154
|
-
return self.features[i].support.evaluate_gradient(
|
|
155
|
-
evaluation_points, ignore_regions=ignore_regions
|
|
156
|
-
)
|
|
154
|
+
return self.features[i].support.evaluate_gradient(pos, ignore_regions=ignore_regions)
|
|
157
155
|
return (
|
|
158
|
-
self.features[0].support.evaluate_gradient(
|
|
159
|
-
|
|
160
|
-
),
|
|
161
|
-
self.features[1].support.evaluate_gradient(
|
|
162
|
-
evaluation_points, ignore_regions=ignore_regions
|
|
163
|
-
),
|
|
164
|
-
self.features[2].support.evaluate_gradient(
|
|
165
|
-
evaluation_points, ignore_regions=ignore_regions
|
|
166
|
-
),
|
|
156
|
+
self.features[0].support.evaluate_gradient(pos, ignore_regions=ignore_regions),
|
|
157
|
+
self.features[1].support.evaluate_gradient(pos, ignore_regions=ignore_regions),
|
|
158
|
+
self.features[2].support.evaluate_gradient(pos, ignore_regions=ignore_regions),
|
|
167
159
|
)
|
|
168
160
|
|
|
169
161
|
def get_data(self, value_map: Optional[dict] = None) -> List[Union[ValuePoints, VectorPoints]]:
|
|
@@ -19,7 +19,7 @@ class UnconformityFeature(GeologicalFeature):
|
|
|
19
19
|
# just don't link the regions
|
|
20
20
|
GeologicalFeature.__init__(
|
|
21
21
|
self,
|
|
22
|
-
name=f"{feature.name}_unconformity",
|
|
22
|
+
name=f"__{feature.name}_unconformity",
|
|
23
23
|
faults=feature.faults,
|
|
24
24
|
regions=[], # feature.regions.copy(), # don't want to share regionsbetween unconformity and # feature.regions,
|
|
25
25
|
builder=feature.builder,
|
|
@@ -75,9 +75,9 @@ class UnconformityFeature(GeologicalFeature):
|
|
|
75
75
|
true if above the unconformity, false if below
|
|
76
76
|
"""
|
|
77
77
|
if self.sign:
|
|
78
|
-
return self.evaluate_value(pos)
|
|
78
|
+
return self.evaluate_value(pos) <= self.value
|
|
79
79
|
if not self.sign:
|
|
80
|
-
return self.evaluate_value(pos)
|
|
80
|
+
return self.evaluate_value(pos) >= self.value
|
|
81
81
|
|
|
82
82
|
def __call__(self, pos) -> np.ndarray:
|
|
83
83
|
return self.evaluate(pos)
|
|
@@ -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)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from ....modelling.features.builders import GeologicalFeatureBuilder
|
|
2
|
-
from ....modelling.features.fold import
|
|
2
|
+
from ....modelling.features.fold.fold_function import FoldRotationType, get_fold_rotation_profile
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
from ....utils import getLogger, InterpolatorError
|
|
@@ -19,6 +19,8 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
19
19
|
name="Feature",
|
|
20
20
|
region=None,
|
|
21
21
|
svario=True,
|
|
22
|
+
axis_profile_type=FoldRotationType.FOURIER_SERIES,
|
|
23
|
+
limb_profile_type=FoldRotationType.FOURIER_SERIES,
|
|
22
24
|
**kwargs,
|
|
23
25
|
):
|
|
24
26
|
"""Builder for creating a geological feature using fold constraints
|
|
@@ -36,6 +38,7 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
36
38
|
region : _type_, optional
|
|
37
39
|
_description_, by default None
|
|
38
40
|
"""
|
|
41
|
+
# create the feature builder, this intialises the interpolator
|
|
39
42
|
GeologicalFeatureBuilder.__init__(
|
|
40
43
|
self,
|
|
41
44
|
interpolatortype=interpolatortype,
|
|
@@ -45,11 +48,27 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
45
48
|
region=region,
|
|
46
49
|
**kwargs,
|
|
47
50
|
)
|
|
51
|
+
# link the interpolator to the fold object
|
|
48
52
|
self.interpolator.fold = fold
|
|
49
53
|
self.fold = fold
|
|
50
54
|
self.fold_weights = fold_weights
|
|
51
55
|
self.kwargs = kwargs
|
|
52
56
|
self.svario = svario
|
|
57
|
+
self.axis_profile_type = axis_profile_type
|
|
58
|
+
self.limb_profile_type = limb_profile_type
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def fold_axis_rotation(self):
|
|
62
|
+
if self.fold.fold_axis_rotation is None:
|
|
63
|
+
self.set_fold_axis()
|
|
64
|
+
return self.fold.fold_axis_rotation
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def fold_limb_rotation(self):
|
|
68
|
+
_axis = self.fold.fold_axis # get axis to make sure its initialised
|
|
69
|
+
if self.fold.fold_limb_rotation is None:
|
|
70
|
+
self.set_fold_limb_rotation()
|
|
71
|
+
return self.fold.fold_limb_rotation
|
|
53
72
|
|
|
54
73
|
def set_fold_axis(self):
|
|
55
74
|
"""calculates the fold axis/ fold axis rotation and adds this to the fold"""
|
|
@@ -67,31 +86,41 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
67
86
|
if not self.fold.foldframe[1].is_valid():
|
|
68
87
|
raise InterpolatorError("Fold frame direction coordinate is not valid")
|
|
69
88
|
far, fad = self.fold.foldframe.calculate_fold_axis_rotation(self)
|
|
70
|
-
fold_axis_rotation =
|
|
71
|
-
a_wl = kwargs.get("axis_wl", None)
|
|
89
|
+
fold_axis_rotation = get_fold_rotation_profile(self.axis_profile_type, far, fad)
|
|
72
90
|
if "axis_function" in kwargs:
|
|
73
91
|
# allow predefined function to be used
|
|
74
92
|
fold_axis_rotation.set_function(kwargs["axis_function"])
|
|
75
93
|
else:
|
|
76
|
-
fold_axis_rotation.
|
|
94
|
+
fold_axis_rotation.fit(params={'wavelength': kwargs.get("axis_wl", None)})
|
|
77
95
|
self.fold.fold_axis_rotation = fold_axis_rotation
|
|
96
|
+
fold_axis_rotation.add_observer(self)
|
|
78
97
|
|
|
79
98
|
def set_fold_limb_rotation(self):
|
|
80
99
|
"""Calculates the limb rotation of the fold and adds it to the fold object"""
|
|
81
100
|
kwargs = self.kwargs
|
|
101
|
+
# need to calculate the fold axis before the fold limb rotation angle
|
|
102
|
+
if self.fold.fold_axis is None:
|
|
103
|
+
self.set_fold_axis()
|
|
82
104
|
# give option of passing own fold limb rotation function
|
|
83
|
-
flr, fld = self.
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
fold_limb_rotation = FoldRotationAngle(flr, fld, svario=self.svario)
|
|
87
|
-
l_wl = kwargs.get("limb_wl", None)
|
|
105
|
+
flr, fld = self.calculate_fold_limb_rotation_angle()
|
|
106
|
+
|
|
107
|
+
fold_limb_rotation = get_fold_rotation_profile(self.limb_profile_type, flr, fld)
|
|
88
108
|
if "limb_function" in kwargs:
|
|
89
109
|
# allow for predefined functions to be used
|
|
90
110
|
fold_limb_rotation.set_function(kwargs["limb_function"])
|
|
91
111
|
else:
|
|
92
|
-
fold_limb_rotation.
|
|
112
|
+
fold_limb_rotation.fit(params={'wavelength': kwargs.get("limb_wl", None)})
|
|
113
|
+
|
|
93
114
|
self.fold.fold_limb_rotation = fold_limb_rotation
|
|
115
|
+
fold_limb_rotation.add_observer(self)
|
|
116
|
+
|
|
117
|
+
def calculate_fold_limb_rotation_angle(self):
|
|
118
|
+
flr, fld = self.fold.foldframe.calculate_fold_limb_rotation(
|
|
119
|
+
self, self.fold.get_fold_axis_orientation
|
|
120
|
+
)
|
|
121
|
+
return flr, fld
|
|
94
122
|
|
|
123
|
+
# def
|
|
95
124
|
def build(self, data_region=None, constrained=None, **kwargs):
|
|
96
125
|
"""the main function to run the interpolation and set up the parameters
|
|
97
126
|
|
|
@@ -115,15 +144,17 @@ class FoldedFeatureBuilder(GeologicalFeatureBuilder):
|
|
|
115
144
|
self.add_data_to_interpolator(constrained=constrained)
|
|
116
145
|
if not self.fold.foldframe[0].is_valid():
|
|
117
146
|
raise InterpolatorError("Fold frame main coordinate is not valid")
|
|
118
|
-
self.
|
|
119
|
-
|
|
147
|
+
if self.fold.fold_axis is None:
|
|
148
|
+
self.set_fold_axis()
|
|
149
|
+
if self.fold.fold_limb_rotation is None:
|
|
150
|
+
self.set_fold_limb_rotation()
|
|
120
151
|
logger.info("Adding fold to {}".format(self.name))
|
|
121
152
|
self.interpolator.fold = self.fold
|
|
122
153
|
# if we have fold weights use those, otherwise just use default
|
|
123
154
|
# self.interpolator.add_fold_constraints(**self.fold_weights)
|
|
124
|
-
kwargs["fold_weights"] = self.fold_weights
|
|
155
|
+
# kwargs["fold_weights"] = self.fold_weights
|
|
125
156
|
if "cgw" not in kwargs:
|
|
126
157
|
# try adding very small cg
|
|
127
158
|
kwargs["cgw"] = 0.0
|
|
128
159
|
# now the fold is set up run the standard interpolation
|
|
129
|
-
super().build(
|
|
160
|
+
return super().build(data_region=data_region, **kwargs)
|
|
@@ -67,7 +67,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
67
67
|
"interpolator is {} and must be a GeologicalInterpolator".format(type(interpolator))
|
|
68
68
|
)
|
|
69
69
|
self._interpolator = interpolator
|
|
70
|
-
|
|
70
|
+
self._up_to_date = self._interpolator.up_to_date
|
|
71
71
|
header = (
|
|
72
72
|
xyz_names()
|
|
73
73
|
+ val_name()
|
|
@@ -93,6 +93,13 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
93
93
|
self._orthogonal_features = {}
|
|
94
94
|
self._equality_constraints = {}
|
|
95
95
|
|
|
96
|
+
def set_not_up_to_date(self, caller):
|
|
97
|
+
logger.info(
|
|
98
|
+
f"Setting {self.name} to not up to date from an instance of {caller.__class__.__name__}"
|
|
99
|
+
)
|
|
100
|
+
self._up_to_date = False
|
|
101
|
+
self._interpolator.up_to_date = False
|
|
102
|
+
|
|
96
103
|
@property
|
|
97
104
|
def interpolator(self):
|
|
98
105
|
return self._interpolator
|
|
@@ -109,6 +116,7 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
109
116
|
else:
|
|
110
117
|
self._interpolation_region = RegionEverywhere()
|
|
111
118
|
self._interpolator.set_region(region=self._interpolation_region)
|
|
119
|
+
logger.info(f'Setting interpolation region {self.name}')
|
|
112
120
|
self._up_to_date = False
|
|
113
121
|
|
|
114
122
|
def add_data_from_data_frame(self, data_frame, overwrite=False):
|
|
@@ -156,6 +164,8 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
156
164
|
logger.error("Cannot cast {} as integer, setting step to 1".format(step))
|
|
157
165
|
step = 1
|
|
158
166
|
self._orthogonal_features[feature.name] = [feature, w, region, step, B]
|
|
167
|
+
|
|
168
|
+
logger.info(f"Adding orthogonal constraint {feature.name} to {self.name}")
|
|
159
169
|
self._up_to_date = False
|
|
160
170
|
|
|
161
171
|
def add_data_to_interpolator(self, constrained=False, force_constrained=False, **kwargs):
|
|
@@ -315,12 +325,13 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
315
325
|
self.interpolator.support.barycentre[element_idx[::step], :],
|
|
316
326
|
vector[element_idx[::step], :],
|
|
317
327
|
w=w,
|
|
318
|
-
|
|
328
|
+
b=B,
|
|
319
329
|
name=f'{feature.name}_orthogonal',
|
|
320
330
|
)
|
|
321
331
|
|
|
322
332
|
def add_equality_constraints(self, feature, region, scalefactor=1.0):
|
|
323
333
|
self._equality_constraints[feature.name] = [feature, region, scalefactor]
|
|
334
|
+
logger.info(f'Adding equality constraints to {self.name}')
|
|
324
335
|
self._up_to_date = False
|
|
325
336
|
|
|
326
337
|
def install_equality_constraints(self):
|
|
@@ -463,19 +474,22 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
463
474
|
Apply the fault to the model grid to ensure that the support
|
|
464
475
|
is big enough to capture the faulted feature.
|
|
465
476
|
"""
|
|
477
|
+
if self.interpolator.support is not None:
|
|
466
478
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
479
|
+
origin = self.interpolator.support.origin
|
|
480
|
+
maximum = self.interpolator.support.maximum
|
|
481
|
+
pts = self.model.bounding_box.with_buffer(buffer).regular_grid(local=True)
|
|
482
|
+
for f in self.faults:
|
|
483
|
+
pts = f.apply_to_points(pts)
|
|
472
484
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
485
|
+
origin[origin > np.min(pts, axis=0)] = np.min(pts, axis=0)[origin > np.min(pts, axis=0)]
|
|
486
|
+
maximum[maximum < np.max(pts, axis=0)] = np.max(pts, axis=0)[
|
|
487
|
+
maximum < np.max(pts, axis=0)
|
|
488
|
+
]
|
|
489
|
+
self.interpolator.support.origin = origin
|
|
490
|
+
self.interpolator.support.maximum = maximum
|
|
477
491
|
|
|
478
|
-
def build(self,
|
|
492
|
+
def build(self, data_region=None, **kwargs):
|
|
479
493
|
"""
|
|
480
494
|
Runs the interpolation and builds the geological feature
|
|
481
495
|
|
|
@@ -536,7 +550,9 @@ class GeologicalFeatureBuilder(BaseBuilder):
|
|
|
536
550
|
logger.info(f'running interpolation for {self.name}')
|
|
537
551
|
|
|
538
552
|
self.interpolator.solve_system(
|
|
539
|
-
solver=kwargs.get('solver',
|
|
553
|
+
solver=kwargs.get('solver', 'cg'),
|
|
554
|
+
tol=kwargs.get('tol', None),
|
|
555
|
+
solver_kwargs=kwargs.get('solver_kwargs', {}),
|
|
540
556
|
)
|
|
541
557
|
logger.info(f'Finished building {self.name}')
|
|
542
558
|
self._up_to_date = True
|
|
@@ -235,3 +235,8 @@ class StructuralFrameBuilder:
|
|
|
235
235
|
def up_to_date(self, callback=None):
|
|
236
236
|
for i in range(3):
|
|
237
237
|
self.builders[i].up_to_date(callback=callback)
|
|
238
|
+
|
|
239
|
+
def set_not_up_to_date(self, caller):
|
|
240
|
+
|
|
241
|
+
for i in range(3):
|
|
242
|
+
self.builders[i].set_not_up_to_date(caller)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod, ABCMeta
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional, List
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
7
|
from ....utils import getLogger
|
|
@@ -367,6 +367,24 @@ class FaultDisplacement:
|
|
|
367
367
|
gz = CubicFunction.from_dict(data["gz"])
|
|
368
368
|
return cls(gx=gx, gy=gy, gz=gz)
|
|
369
369
|
|
|
370
|
+
def plot(self, range=(-1, 1), axs: Optional[List] = None):
|
|
371
|
+
try:
|
|
372
|
+
import matplotlib.pyplot as plt
|
|
373
|
+
|
|
374
|
+
if axs is None:
|
|
375
|
+
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
|
|
376
|
+
for i, (name, f) in enumerate(zip(["gx", "gy", "gz"], [self.gx, self.gy, self.gz])):
|
|
377
|
+
x = np.linspace(range[0], range[1], 100)
|
|
378
|
+
ax[i].plot(x, f(x), label=name)
|
|
379
|
+
ax[i].set_title(name)
|
|
380
|
+
ax[i].set_xlabel("Fault frame coordinate")
|
|
381
|
+
ax[i].set_ylabel("Displacement")
|
|
382
|
+
ax[i].legend()
|
|
383
|
+
|
|
384
|
+
except ImportError:
|
|
385
|
+
logger.warning("matplotlib not installed, not plotting")
|
|
386
|
+
return
|
|
387
|
+
|
|
370
388
|
|
|
371
389
|
class BaseFault(object):
|
|
372
390
|
""" """
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
|
|
1
2
|
from ....modelling.features.fault._fault_function_feature import (
|
|
2
3
|
FaultDisplacementFeature,
|
|
3
4
|
)
|
|
4
5
|
from ....modelling.features import FeatureType
|
|
5
|
-
from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D
|
|
6
|
+
from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D, FaultDisplacement
|
|
6
7
|
from ....utils import getLogger, NegativeRegion, PositiveRegion
|
|
7
8
|
from ....modelling.features import StructuralFrame
|
|
8
9
|
|
|
@@ -39,7 +40,7 @@ class FaultSegment(StructuralFrame):
|
|
|
39
40
|
StructuralFrame.__init__(self, features, name, fold, model)
|
|
40
41
|
self.type = FeatureType.FAULT
|
|
41
42
|
self.displacement = displacement
|
|
42
|
-
self._faultfunction = BaseFault.fault_displacement
|
|
43
|
+
self._faultfunction = BaseFault().fault_displacement
|
|
43
44
|
self.steps = steps
|
|
44
45
|
self.regions = []
|
|
45
46
|
self.faults_enabled = True
|
|
@@ -57,10 +58,13 @@ class FaultSegment(StructuralFrame):
|
|
|
57
58
|
def faultfunction(self, value):
|
|
58
59
|
if callable(value):
|
|
59
60
|
self._faultfunction = value
|
|
61
|
+
if issubclass(FaultDisplacement, type(value)):
|
|
62
|
+
self._faultfunction = value
|
|
63
|
+
|
|
60
64
|
elif isinstance(value, str) and value == "BaseFault":
|
|
61
|
-
self._faultfunction = BaseFault.fault_displacement
|
|
65
|
+
self._faultfunction = BaseFault().fault_displacement
|
|
62
66
|
elif isinstance(value, str) and value == "BaseFault3D":
|
|
63
|
-
self._faultfunction = BaseFault3D.fault_displacement
|
|
67
|
+
self._faultfunction = BaseFault3D().fault_displacement
|
|
64
68
|
else:
|
|
65
69
|
raise ValueError("Fault function must be a function or BaseFault")
|
|
66
70
|
|
|
@@ -110,6 +114,26 @@ class FaultSegment(StructuralFrame):
|
|
|
110
114
|
def displacementfeature(self):
|
|
111
115
|
return FaultDisplacementFeature(self, self.faultfunction, name=self.name, model=self.model)
|
|
112
116
|
|
|
117
|
+
def fault_ellipsoid(self, **kwargs):
|
|
118
|
+
try:
|
|
119
|
+
import pyvista as pv
|
|
120
|
+
|
|
121
|
+
fault_ellipsoid = pv.PolyData(
|
|
122
|
+
self.model.rescale(self.fault_centre[None, :], inplace=False)
|
|
123
|
+
)
|
|
124
|
+
fault_ellipsoid["norm"] = self.builder.fault_normal_vector[None, :]
|
|
125
|
+
|
|
126
|
+
geom = pv.ParametricEllipsoid(
|
|
127
|
+
self.fault_minor_axis,
|
|
128
|
+
self.fault_major_axis,
|
|
129
|
+
self.fault_intermediate_axis,
|
|
130
|
+
)
|
|
131
|
+
ellipsoid = fault_ellipsoid.glyph(geom=geom, **kwargs)
|
|
132
|
+
return ellipsoid
|
|
133
|
+
except ImportError:
|
|
134
|
+
logger.error("pyvista not installed")
|
|
135
|
+
return None
|
|
136
|
+
|
|
113
137
|
def set_fault_offset(self, offset: float):
|
|
114
138
|
self.fault_offset = offset
|
|
115
139
|
|
|
@@ -173,7 +197,7 @@ class FaultSegment(StructuralFrame):
|
|
|
173
197
|
return np.abs(v) > threshold
|
|
174
198
|
# return np.all(np.logical_and(v > -1,v<1),axis=1)
|
|
175
199
|
|
|
176
|
-
def evaluate_value(self, locations):
|
|
200
|
+
def evaluate_value(self, locations, ignore_regions=False):
|
|
177
201
|
"""
|
|
178
202
|
Return the value of the fault surface scalar field
|
|
179
203
|
|
|
@@ -198,7 +222,10 @@ class FaultSegment(StructuralFrame):
|
|
|
198
222
|
# except:
|
|
199
223
|
# logger.error("nan slicing")
|
|
200
224
|
# v[mask] = self.__getitem__(0).evaluate_value(locations[mask, :])
|
|
201
|
-
return self.__getitem__(0).evaluate_value(locations)
|
|
225
|
+
return self.__getitem__(0).evaluate_value(locations, ignore_regions=ignore_regions)
|
|
226
|
+
|
|
227
|
+
def ellipsoid(self):
|
|
228
|
+
pass
|
|
202
229
|
|
|
203
230
|
def mean(self):
|
|
204
231
|
return self.__getitem__(0).mean()
|
|
@@ -282,6 +309,7 @@ class FaultSegment(StructuralFrame):
|
|
|
282
309
|
-------
|
|
283
310
|
|
|
284
311
|
"""
|
|
312
|
+
logger.info(f'Applying fault {self.name} to points {points.shape}')
|
|
285
313
|
steps = self.steps
|
|
286
314
|
newp = np.copy(points).astype(float)
|
|
287
315
|
# evaluate fault function for all points
|
|
@@ -389,51 +417,12 @@ class FaultSegment(StructuralFrame):
|
|
|
389
417
|
# the nodes of the tetrahedron are then restored by the fault and then gradient is
|
|
390
418
|
# recalculated using the updated node positions but the original corner values
|
|
391
419
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
]
|
|
399
|
-
)
|
|
400
|
-
regular_tetrahedron *= scale_parameter
|
|
401
|
-
xyz = vector[:, :3]
|
|
402
|
-
tetrahedron = np.zeros((xyz.shape[0], 4, 3))
|
|
403
|
-
tetrahedron[:] = xyz[:, None, :]
|
|
404
|
-
tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
|
|
405
|
-
|
|
406
|
-
vectors = vector[:, 3:]
|
|
407
|
-
corners = np.einsum('ikj,ij->ik', tetrahedron - xyz[:, None, :], vectors)
|
|
408
|
-
tetrahedron = tetrahedron.reshape(-1, 3)
|
|
409
|
-
tetrahedron = self.apply_to_points(tetrahedron)
|
|
410
|
-
tetrahedron = tetrahedron.reshape(-1, 4, 3)
|
|
411
|
-
m = np.array(
|
|
412
|
-
[
|
|
413
|
-
[
|
|
414
|
-
(tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
|
|
415
|
-
(tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
|
|
416
|
-
(tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
|
|
417
|
-
],
|
|
418
|
-
[
|
|
419
|
-
(tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
|
|
420
|
-
(tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
|
|
421
|
-
(tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
|
|
422
|
-
],
|
|
423
|
-
[
|
|
424
|
-
(tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
|
|
425
|
-
(tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
|
|
426
|
-
(tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
|
|
427
|
-
],
|
|
428
|
-
]
|
|
429
|
-
)
|
|
430
|
-
I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
|
|
431
|
-
m = np.swapaxes(m, 0, 2)
|
|
432
|
-
element_gradients = np.linalg.inv(m)
|
|
433
|
-
|
|
434
|
-
element_gradients = element_gradients.swapaxes(1, 2)
|
|
435
|
-
element_gradients = element_gradients @ I
|
|
436
|
-
v = np.sum(element_gradients * corners[:, None, :], axis=2)
|
|
420
|
+
tetrahedron = regular_tetraherdron_for_points(vector[:, :3], scale_parameter)
|
|
421
|
+
corners = np.einsum('ikj,ij->ik', tetrahedron - vector[:, None, :3], vector[:, 3:])
|
|
422
|
+
|
|
423
|
+
tetrahedron = self.apply_to_points(tetrahedron.reshape(-1, 3)).reshape(-1, 4, 3)
|
|
424
|
+
v = gradient_from_tetrahedron(tetrahedron, corners)
|
|
425
|
+
|
|
437
426
|
return v
|
|
438
427
|
|
|
439
428
|
def add_abutting_fault(self, abutting_fault_feature, positive=None):
|
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
from ._fold import FoldEvent
|
|
6
6
|
from ._svariogram import SVariogram
|
|
7
|
-
from ._fold_rotation_angle_feature import FoldRotationAngleFeature
|
|
7
|
+
from ._fold_rotation_angle_feature import FoldRotationAngleFeature
|
|
8
8
|
from ._foldframe import FoldFrame
|
|
9
|
-
from ._fold_rotation_angle import FoldRotationAngle
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
from ....modelling.features import BaseFeature
|
|
3
|
-
|
|
4
2
|
from ....utils import getLogger
|
|
5
3
|
|
|
6
4
|
logger = getLogger(__name__)
|
|
@@ -44,24 +42,3 @@ class FoldRotationAngleFeature(BaseFeature):
|
|
|
44
42
|
s1 = self.fold_frame.features[0].evaluate_value(location)
|
|
45
43
|
r = self.rotation(s1)
|
|
46
44
|
return r
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def fourier_series(x, c0, c1, c2, w):
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
Parameters
|
|
53
|
-
----------
|
|
54
|
-
x
|
|
55
|
-
c0
|
|
56
|
-
c1
|
|
57
|
-
c2
|
|
58
|
-
w
|
|
59
|
-
|
|
60
|
-
Returns
|
|
61
|
-
-------
|
|
62
|
-
|
|
63
|
-
"""
|
|
64
|
-
v = np.array(x.astype(float))
|
|
65
|
-
# v.fill(c0)
|
|
66
|
-
v = c0 + c1 * np.cos(2 * np.pi / w * x) + c2 * np.sin(2 * np.pi / w * x)
|
|
67
|
-
return np.rad2deg(np.arctan(v))
|