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
|
@@ -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
|
|
@@ -112,7 +113,9 @@ class GeologicalFeature(BaseFeature):
|
|
|
112
113
|
# if evaluation_points is not a numpy array try and convert
|
|
113
114
|
# otherwise error
|
|
114
115
|
evaluation_points = np.asarray(pos)
|
|
115
|
-
|
|
116
|
+
# if there is a builder lets make sure that the feature is up to date
|
|
117
|
+
if self.builder is not None:
|
|
118
|
+
self.builder.up_to_date()
|
|
116
119
|
# check if the points are within the display region
|
|
117
120
|
v = np.zeros(evaluation_points.shape[0])
|
|
118
121
|
v[:] = np.nan
|
|
@@ -131,7 +134,9 @@ class GeologicalFeature(BaseFeature):
|
|
|
131
134
|
v[nanmask] = v[~nanmask][i]
|
|
132
135
|
return v
|
|
133
136
|
|
|
134
|
-
def evaluate_gradient(
|
|
137
|
+
def evaluate_gradient(
|
|
138
|
+
self, pos: np.ndarray, ignore_regions=False, element_scale_parameter=None
|
|
139
|
+
) -> np.ndarray:
|
|
135
140
|
"""
|
|
136
141
|
|
|
137
142
|
Parameters
|
|
@@ -147,6 +152,17 @@ class GeologicalFeature(BaseFeature):
|
|
|
147
152
|
if pos.shape[1] != 3:
|
|
148
153
|
raise LoopValueError("Need Nx3 array of xyz points to evaluate gradient")
|
|
149
154
|
logger.info(f'Calculating gradient for {self.name}')
|
|
155
|
+
if element_scale_parameter is None:
|
|
156
|
+
if self.model is not None:
|
|
157
|
+
element_scale_parameter = np.min(self.model.bounding_box.step_vector) / 10
|
|
158
|
+
else:
|
|
159
|
+
element_scale_parameter = 1
|
|
160
|
+
else:
|
|
161
|
+
try:
|
|
162
|
+
element_scale_parameter = float(element_scale_parameter)
|
|
163
|
+
except ValueError:
|
|
164
|
+
logger.error("element_scale_parameter must be a float")
|
|
165
|
+
element_scale_parameter = 1
|
|
150
166
|
|
|
151
167
|
self.builder.up_to_date()
|
|
152
168
|
|
|
@@ -156,16 +172,38 @@ class GeologicalFeature(BaseFeature):
|
|
|
156
172
|
# evaluate the faults on the nodes of the faulted feature support
|
|
157
173
|
# then evaluate the gradient at these points
|
|
158
174
|
if len(self.faults) > 0:
|
|
175
|
+
# generate a regular tetrahedron for each point
|
|
176
|
+
# we will then move these points by the fault and then recalculate the gradient.
|
|
177
|
+
# this should work...
|
|
178
|
+
resolved = False
|
|
179
|
+
tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
|
|
180
|
+
|
|
181
|
+
while resolved:
|
|
182
|
+
for f in self.faults:
|
|
183
|
+
v = (
|
|
184
|
+
f[0]
|
|
185
|
+
.evaluate_value(tetrahedron.reshape(-1, 3), fillnan='nearest')
|
|
186
|
+
.reshape(tetrahedron.shape[0], 4)
|
|
187
|
+
)
|
|
188
|
+
flag = np.logical_or(np.all(v > 0, axis=1), np.all(v < 0, axis=1))
|
|
189
|
+
if np.any(~flag):
|
|
190
|
+
logger.warning(
|
|
191
|
+
f"Points are too close to fault {f[0].name}. Refining the tetrahedron"
|
|
192
|
+
)
|
|
193
|
+
element_scale_parameter *= 0.5
|
|
194
|
+
tetrahedron = regular_tetraherdron_for_points(pos, element_scale_parameter)
|
|
195
|
+
|
|
196
|
+
resolved = True
|
|
197
|
+
|
|
198
|
+
tetrahedron_faulted = self._apply_faults(np.array(tetrahedron.reshape(-1, 3))).reshape(
|
|
199
|
+
tetrahedron.shape
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
values = self.interpolator.evaluate_value(tetrahedron_faulted.reshape(-1, 3)).reshape(
|
|
203
|
+
(-1, 4)
|
|
204
|
+
)
|
|
205
|
+
v[mask, :] = gradient_from_tetrahedron(tetrahedron[mask, :, :], values[mask])
|
|
159
206
|
|
|
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
207
|
return v
|
|
170
208
|
pos = self._apply_faults(pos)
|
|
171
209
|
if mask.dtype not in [int, bool]:
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ...modelling.features import BaseFeature
|
|
8
|
+
|
|
9
|
+
from ...utils import getLogger
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProjectedVectorFeature(BaseFeature):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
name: str,
|
|
18
|
+
vector: np.ndarray,
|
|
19
|
+
plane_feature: BaseFeature,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
Create a geological feature by projecting a vector onto a feature representing a plane
|
|
24
|
+
E.g. project a thickness vector onto an axial surface
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
name: feature name
|
|
29
|
+
vector: the vector to project
|
|
30
|
+
plane_feature: the plane
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
name : str
|
|
36
|
+
name of the feature
|
|
37
|
+
geological_feature_a : BaseFeature
|
|
38
|
+
Left hand side of cross product
|
|
39
|
+
geological_feature_b : BaseFeature
|
|
40
|
+
Right hand side of cross product
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(name)
|
|
43
|
+
self.plane_feature = plane_feature
|
|
44
|
+
self.vector = vector
|
|
45
|
+
|
|
46
|
+
self.value_feature = None
|
|
47
|
+
|
|
48
|
+
def evaluate_gradient(self, locations: np.ndarray, ignore_regions=False) -> np.ndarray:
|
|
49
|
+
"""
|
|
50
|
+
Calculate the gradient of the geological feature by using numpy to
|
|
51
|
+
calculate the cross
|
|
52
|
+
product between the two existing feature gradients.
|
|
53
|
+
This means both features have to be evaluated for the locations
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
locations
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# project s0 onto axis plane B X A X B
|
|
65
|
+
plane_normal = self.plane_feature.evaluate_gradient(locations, ignore_regions)
|
|
66
|
+
vector = np.tile(self.vector, (locations.shape[0], 1))
|
|
67
|
+
|
|
68
|
+
projected_vector = np.cross(
|
|
69
|
+
plane_normal, np.cross(vector, plane_normal, axisa=1, axisb=1), axisa=1, axisb=1
|
|
70
|
+
)
|
|
71
|
+
return projected_vector
|
|
72
|
+
|
|
73
|
+
def evaluate_value(self, evaluation_points: np.ndarray, ignore_regions=False) -> np.ndarray:
|
|
74
|
+
"""
|
|
75
|
+
Return 0 because there is no value for this feature
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
evaluation_points
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
values = np.zeros(evaluation_points.shape[0])
|
|
85
|
+
if self.value_feature is not None:
|
|
86
|
+
values[:] = self.value_feature.evaluate_value(evaluation_points, ignore_regions)
|
|
87
|
+
return values
|
|
88
|
+
|
|
89
|
+
def mean(self):
|
|
90
|
+
if self.value_feature:
|
|
91
|
+
return self.value_feature.mean()
|
|
92
|
+
return 0.0
|
|
93
|
+
|
|
94
|
+
def min(self):
|
|
95
|
+
if self.value_feature:
|
|
96
|
+
return self.value_feature.min()
|
|
97
|
+
return 0.0
|
|
98
|
+
|
|
99
|
+
def max(self):
|
|
100
|
+
if self.value_feature:
|
|
101
|
+
return self.value_feature.max()
|
|
102
|
+
return 0.0
|
|
103
|
+
|
|
104
|
+
def get_data(self, value_map: Optional[dict] = None):
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
def copy(self, name: Optional[str] = None):
|
|
108
|
+
if name is None:
|
|
109
|
+
name = f'{self.name}_copy'
|
|
110
|
+
return ProjectedVectorFeature(
|
|
111
|
+
name=name, vector=self.vector, plane_feature=self.plane_feature
|
|
112
|
+
)
|
|
@@ -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]]:
|
|
@@ -184,3 +176,9 @@ class StructuralFrame(BaseFeature):
|
|
|
184
176
|
for f in self.features:
|
|
185
177
|
data.extend(f.get_data(value_map))
|
|
186
178
|
return data
|
|
179
|
+
|
|
180
|
+
def copy(self, name: Optional[str] = None):
|
|
181
|
+
if name is None:
|
|
182
|
+
name = f'{self.name}_copy'
|
|
183
|
+
# !TODO check if this needs to be a deep copy
|
|
184
|
+
return StructuralFrame(name, self.features, self.fold, self.model)
|
|
@@ -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
|
+
logger.error("axis_function is deprecated, use a specific fold rotation angle profile type")
|
|
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
|
+
logger.error("limb_function is deprecated, use a specific fold rotation angle profile type")
|
|
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
|
|
@@ -110,8 +110,8 @@ class StructuralFrameBuilder:
|
|
|
110
110
|
) # ,region=self.region))
|
|
111
111
|
|
|
112
112
|
self._frame = frame(
|
|
113
|
-
self.name,
|
|
114
|
-
[
|
|
113
|
+
name=self.name,
|
|
114
|
+
features=[
|
|
115
115
|
self.builders[0].feature,
|
|
116
116
|
self.builders[1].feature,
|
|
117
117
|
self.builders[2].feature,
|
|
@@ -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
|
""" """
|