LoopStructural 1.6.1__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 +52 -0
- LoopStructural/datasets/__init__.py +23 -0
- LoopStructural/datasets/_base.py +301 -0
- LoopStructural/datasets/_example_models.py +10 -0
- LoopStructural/datasets/data/claudius.csv +21049 -0
- LoopStructural/datasets/data/claudiusbb.txt +2 -0
- LoopStructural/datasets/data/duplex.csv +126 -0
- LoopStructural/datasets/data/duplexbb.txt +2 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
- LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
- LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
- LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
- LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
- LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
- LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
- LoopStructural/datasets/data/intrusion.csv +1017 -0
- LoopStructural/datasets/data/intrusionbb.txt +2 -0
- LoopStructural/datasets/data/onefoldbb.txt +2 -0
- LoopStructural/datasets/data/onefolddata.csv +2226 -0
- LoopStructural/datasets/data/refolded_bb.txt +2 -0
- LoopStructural/datasets/data/refolded_fold.csv +205 -0
- LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
- LoopStructural/datatypes/__init__.py +4 -0
- LoopStructural/datatypes/_bounding_box.py +422 -0
- LoopStructural/datatypes/_point.py +166 -0
- LoopStructural/datatypes/_structured_grid.py +94 -0
- LoopStructural/datatypes/_surface.py +184 -0
- LoopStructural/export/exporters.py +554 -0
- LoopStructural/export/file_formats.py +15 -0
- LoopStructural/export/geoh5.py +100 -0
- LoopStructural/export/gocad.py +126 -0
- LoopStructural/export/omf_wrapper.py +88 -0
- LoopStructural/interpolators/__init__.py +105 -0
- LoopStructural/interpolators/_api.py +143 -0
- LoopStructural/interpolators/_builders.py +149 -0
- LoopStructural/interpolators/_cython/__init__.py +0 -0
- LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
- LoopStructural/interpolators/_discrete_interpolator.py +692 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
- LoopStructural/interpolators/_geological_interpolator.py +380 -0
- LoopStructural/interpolators/_interpolator_factory.py +89 -0
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/interpolators/_operator.py +38 -0
- LoopStructural/interpolators/_p1interpolator.py +228 -0
- LoopStructural/interpolators/_p2interpolator.py +277 -0
- LoopStructural/interpolators/_surfe_wrapper.py +174 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
- LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
- LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
- LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
- LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
- LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
- LoopStructural/interpolators/supports/__init__.py +55 -0
- LoopStructural/interpolators/supports/_aabb.py +77 -0
- LoopStructural/interpolators/supports/_base_support.py +114 -0
- LoopStructural/interpolators/supports/_face_table.py +70 -0
- LoopStructural/interpolators/supports/_support_factory.py +32 -0
- LoopStructural/modelling/__init__.py +29 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1867 -0
- LoopStructural/modelling/features/__init__.py +32 -0
- LoopStructural/modelling/features/_analytical_feature.py +79 -0
- LoopStructural/modelling/features/_base_geological_feature.py +364 -0
- LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
- LoopStructural/modelling/features/_geological_feature.py +288 -0
- LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
- LoopStructural/modelling/features/_region.py +18 -0
- LoopStructural/modelling/features/_structural_frame.py +186 -0
- LoopStructural/modelling/features/_unconformity_feature.py +83 -0
- LoopStructural/modelling/features/builders/__init__.py +5 -0
- LoopStructural/modelling/features/builders/_base_builder.py +111 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
- LoopStructural/modelling/features/fault/__init__.py +3 -0
- LoopStructural/modelling/features/fault/_fault_function.py +444 -0
- LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
- LoopStructural/modelling/features/fold/__init__.py +9 -0
- LoopStructural/modelling/features/fold/_fold.py +167 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
- LoopStructural/modelling/features/fold/_foldframe.py +194 -0
- LoopStructural/modelling/features/fold/_svariogram.py +188 -0
- LoopStructural/modelling/input/__init__.py +2 -0
- LoopStructural/modelling/input/fault_network.py +80 -0
- LoopStructural/modelling/input/map2loop_processor.py +165 -0
- LoopStructural/modelling/input/process_data.py +650 -0
- LoopStructural/modelling/input/project_file.py +84 -0
- LoopStructural/modelling/intrusions/__init__.py +25 -0
- LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
- LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
- LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
- LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
- LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
- LoopStructural/utils/__init__.py +38 -0
- LoopStructural/utils/_surface.py +143 -0
- LoopStructural/utils/_transformation.py +76 -0
- LoopStructural/utils/config.py +18 -0
- LoopStructural/utils/dtm_creator.py +17 -0
- LoopStructural/utils/exceptions.py +31 -0
- LoopStructural/utils/helper.py +292 -0
- LoopStructural/utils/json_encoder.py +18 -0
- LoopStructural/utils/linalg.py +8 -0
- LoopStructural/utils/logging.py +79 -0
- LoopStructural/utils/maths.py +245 -0
- LoopStructural/utils/regions.py +103 -0
- LoopStructural/utils/typing.py +7 -0
- LoopStructural/utils/utils.py +68 -0
- LoopStructural/version.py +1 -0
- LoopStructural/visualisation/__init__.py +11 -0
- LoopStructural-1.6.1.dist-info/LICENSE +21 -0
- LoopStructural-1.6.1.dist-info/METADATA +81 -0
- LoopStructural-1.6.1.dist-info/RECORD +129 -0
- LoopStructural-1.6.1.dist-info/WHEEL +5 -0
- LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Piecewise linear interpolator
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ._discrete_interpolator import DiscreteInterpolator
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class P1Interpolator(DiscreteInterpolator):
|
|
15
|
+
def __init__(self, mesh):
|
|
16
|
+
"""
|
|
17
|
+
Piecewise Linear Interpolator
|
|
18
|
+
Approximates scalar field by finding coefficients to a piecewise linear
|
|
19
|
+
equation on a tetrahedral mesh. Uses constant gradient regularisation.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
mesh - TetMesh
|
|
24
|
+
interpolation support
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
self.shape = "rectangular"
|
|
28
|
+
DiscreteInterpolator.__init__(self, mesh)
|
|
29
|
+
# whether to assemble a rectangular matrix or a square matrix
|
|
30
|
+
self.support = mesh
|
|
31
|
+
|
|
32
|
+
self.interpolation_weights = {
|
|
33
|
+
"cgw": 0.1,
|
|
34
|
+
"cpw": 1.0,
|
|
35
|
+
"npw": 1.0,
|
|
36
|
+
"gpw": 1.0,
|
|
37
|
+
"tpw": 1.0,
|
|
38
|
+
"ipw": 1.0,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def add_gradient_constraints(self, w=1.0):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def add_norm_constraints(self, w=1.0):
|
|
45
|
+
points = self.get_norm_constraints()
|
|
46
|
+
if points.shape[0] > 0:
|
|
47
|
+
grad, elements, inside = self.support.evaluate_shape_derivatives(points[:, :3])
|
|
48
|
+
size = self.support.element_size[elements[inside]]
|
|
49
|
+
wt = np.ones(size.shape[0])
|
|
50
|
+
wt *= w # s* size
|
|
51
|
+
elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
|
|
52
|
+
|
|
53
|
+
elements = elements.swapaxes(0, 1)
|
|
54
|
+
# elements = elements.swapaxes(0, 2)
|
|
55
|
+
# grad = grad.swapaxes(1, 2)
|
|
56
|
+
# elements = elements.swapaxes(1, 2)
|
|
57
|
+
|
|
58
|
+
self.add_constraints_to_least_squares(
|
|
59
|
+
grad[inside, :, :],
|
|
60
|
+
points[inside, 3:6],
|
|
61
|
+
elements,
|
|
62
|
+
w=wt,
|
|
63
|
+
name="norm",
|
|
64
|
+
)
|
|
65
|
+
self.up_to_date = False
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def add_value_constraints(self, w=1.0):
|
|
69
|
+
points = self.get_value_constraints()
|
|
70
|
+
if points.shape[0] > 1:
|
|
71
|
+
N, elements, inside = self.support.evaluate_shape(points[:, :3])
|
|
72
|
+
size = self.support.element_size[elements[inside]]
|
|
73
|
+
wt = np.ones(size.shape[0])
|
|
74
|
+
wt *= w # * size
|
|
75
|
+
self.add_constraints_to_least_squares(
|
|
76
|
+
N[inside, :],
|
|
77
|
+
points[inside, 3],
|
|
78
|
+
self.support.elements[elements[inside], :],
|
|
79
|
+
w=wt,
|
|
80
|
+
name="value",
|
|
81
|
+
)
|
|
82
|
+
self.up_to_date = False
|
|
83
|
+
|
|
84
|
+
def minimise_edge_jumps(self, w=0.1, vector_func=None, vector=None, name="edge jump"):
|
|
85
|
+
# NOTE: imposes \phi_T1(xi)-\phi_T2(xi) dot n =0
|
|
86
|
+
# iterate over all triangles
|
|
87
|
+
# flag inidicate which triangles have had all their relationships added
|
|
88
|
+
v1 = self.support.nodes[self.support.shared_elements][:, 0, :]
|
|
89
|
+
v2 = self.support.nodes[self.support.shared_elements][:, 1, :]
|
|
90
|
+
bc_t1 = self.support.barycentre[self.support.shared_element_relationships[:, 0]]
|
|
91
|
+
bc_t2 = self.support.barycentre[self.support.shared_element_relationships[:, 1]]
|
|
92
|
+
norm = self.support.shared_element_norm
|
|
93
|
+
# shared_element_scale = self.support.shared_element_scale
|
|
94
|
+
|
|
95
|
+
# evaluate normal if using vector func for cp2
|
|
96
|
+
if vector_func:
|
|
97
|
+
norm = vector_func((v1 + v2) / 2)
|
|
98
|
+
if vector is not None:
|
|
99
|
+
if bc_t1.shape[0] == vector.shape[0]:
|
|
100
|
+
norm = vector
|
|
101
|
+
# evaluate the shape function for the edges for each neighbouring triangle
|
|
102
|
+
Dt, tri1, inside = self.support.evaluate_shape_derivatives(
|
|
103
|
+
bc_t1, elements=self.support.shared_element_relationships[:, 0]
|
|
104
|
+
)
|
|
105
|
+
Dn, tri2, inside = self.support.evaluate_shape_derivatives(
|
|
106
|
+
bc_t2, elements=self.support.shared_element_relationships[:, 1]
|
|
107
|
+
)
|
|
108
|
+
# constraint for each cp is triangle - neighbour create a Nx12 matrix
|
|
109
|
+
const_t = np.einsum("ij,ijk->ik", norm, Dt)
|
|
110
|
+
const_n = -np.einsum("ij,ijk->ik", norm, Dn)
|
|
111
|
+
# const_t_cp2 = np.einsum('ij,ikj->ik',normal,cp2_Dt)
|
|
112
|
+
# const_n_cp2 = -np.einsum('ij,ikj->ik',normal,cp2_Dn)
|
|
113
|
+
|
|
114
|
+
const = np.hstack([const_t, const_n])
|
|
115
|
+
|
|
116
|
+
# get vertex indexes
|
|
117
|
+
tri_cp1 = np.hstack([self.support.elements[tri1], self.support.elements[tri2]])
|
|
118
|
+
# tri_cp2 = np.hstack([self.support.elements[cp2_tri1],self.support.elements[tri2]])
|
|
119
|
+
# add cp1 and cp2 to the least squares system
|
|
120
|
+
self.add_constraints_to_least_squares(
|
|
121
|
+
const,
|
|
122
|
+
np.zeros(const.shape[0]),
|
|
123
|
+
tri_cp1,
|
|
124
|
+
w=w,
|
|
125
|
+
name=name,
|
|
126
|
+
)
|
|
127
|
+
self.up_to_date = False
|
|
128
|
+
# p2.add_constraints_to_least_squares(const_cp2*e_len[:,None]*w,np.zeros(const_cp1.shape[0]),tri_cp2, name='edge jump cp2')
|
|
129
|
+
|
|
130
|
+
def setup_interpolator(self, **kwargs):
|
|
131
|
+
"""
|
|
132
|
+
Searches through kwargs for any interpolation weights and updates
|
|
133
|
+
the dictionary.
|
|
134
|
+
Then adds the constraints to the linear system using the
|
|
135
|
+
interpolation weights values
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
kwargs -
|
|
139
|
+
interpolation weights
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
# can't reset here, clears fold constraints
|
|
146
|
+
self.reset()
|
|
147
|
+
for key in kwargs:
|
|
148
|
+
if "regularisation" in kwargs:
|
|
149
|
+
self.interpolation_weights["cgw"] = 0.1 * kwargs["regularisation"]
|
|
150
|
+
self.up_to_date = False
|
|
151
|
+
self.interpolation_weights[key] = kwargs[key]
|
|
152
|
+
if self.interpolation_weights["cgw"] > 0.0:
|
|
153
|
+
self.up_to_date = False
|
|
154
|
+
self.minimise_edge_jumps(self.interpolation_weights["cgw"])
|
|
155
|
+
# direction_feature=kwargs.get("direction_feature", None),
|
|
156
|
+
# direction_vector=kwargs.get("direction_vector", None),
|
|
157
|
+
# )
|
|
158
|
+
# self.minimise_grad_steepness(
|
|
159
|
+
# w=self.interpolation_weights.get("steepness_weight", 0.01),
|
|
160
|
+
# wtfunc=self.interpolation_weights.get("steepness_wtfunc", None),
|
|
161
|
+
# )
|
|
162
|
+
logger.info(
|
|
163
|
+
"Using constant gradient regularisation w = %f" % self.interpolation_weights["cgw"]
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
logger.info(
|
|
167
|
+
"Added %i gradient constraints, %i normal constraints,"
|
|
168
|
+
"%i tangent constraints and %i value constraints"
|
|
169
|
+
% (self.n_g, self.n_n, self.n_t, self.n_i)
|
|
170
|
+
)
|
|
171
|
+
self.add_gradient_constraints(self.interpolation_weights["gpw"])
|
|
172
|
+
self.add_norm_constraints(self.interpolation_weights["npw"])
|
|
173
|
+
self.add_value_constraints(self.interpolation_weights["cpw"])
|
|
174
|
+
self.add_tangent_constraints(self.interpolation_weights["tpw"])
|
|
175
|
+
self.add_value_inequality_constraints()
|
|
176
|
+
self.add_inequality_pairs_constraints()
|
|
177
|
+
# self.add_interface_constraints(self.interpolation_weights["ipw"])
|
|
178
|
+
|
|
179
|
+
def add_gradient_orthogonal_constraints(
|
|
180
|
+
self,
|
|
181
|
+
points: np.ndarray,
|
|
182
|
+
vectors: np.ndarray,
|
|
183
|
+
w: float = 1.0,
|
|
184
|
+
B: float = 0,
|
|
185
|
+
name='undefined gradient orthogonal constraint',
|
|
186
|
+
):
|
|
187
|
+
"""
|
|
188
|
+
constraints scalar field to be orthogonal to a given vector
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
points : np.darray
|
|
193
|
+
location to add gradient orthogonal constraint
|
|
194
|
+
vector : np.darray
|
|
195
|
+
vector to be orthogonal to, should be the same shape as points
|
|
196
|
+
w : double
|
|
197
|
+
B : np.array
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
if points.shape[0] > 0:
|
|
204
|
+
grad, elements, inside = self.support.evaluate_shape_derivatives(points[:, :3])
|
|
205
|
+
size = self.support.element_size[elements[inside]]
|
|
206
|
+
wt = np.ones(size.shape[0])
|
|
207
|
+
wt *= w * size
|
|
208
|
+
elements = self.support.elements[elements[inside], :]
|
|
209
|
+
# elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
|
|
210
|
+
|
|
211
|
+
# elements = elements.swapaxes(0, 1)
|
|
212
|
+
# elements = elements.swapaxes(0, 2)
|
|
213
|
+
# grad = grad.swapaxes(1, 2)
|
|
214
|
+
# elements = elements.swapaxes(1, 2)
|
|
215
|
+
norm = np.linalg.norm(vectors, axis=1)
|
|
216
|
+
vectors[norm > 0, :] /= norm[norm > 0, None]
|
|
217
|
+
A = np.einsum("ij,ijk->ik", vectors[inside, :3], grad[inside, :, :])
|
|
218
|
+
B = np.zeros(points[inside, :].shape[0]) + B
|
|
219
|
+
self.add_constraints_to_least_squares(A, B, elements, w=wt, name="gradient orthogonal")
|
|
220
|
+
if np.sum(inside) <= 0:
|
|
221
|
+
logger.warning(
|
|
222
|
+
f"{np.sum(~inside)} \
|
|
223
|
+
gradient constraints not added: outside of model bounding box"
|
|
224
|
+
)
|
|
225
|
+
self.up_to_date = False
|
|
226
|
+
|
|
227
|
+
def add_interface_constraints(self, w: float = 1):
|
|
228
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Piecewise linear interpolator
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional, Callable
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..interpolators import DiscreteInterpolator
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class P2Interpolator(DiscreteInterpolator):
|
|
16
|
+
""" """
|
|
17
|
+
|
|
18
|
+
def __init__(self, mesh):
|
|
19
|
+
"""
|
|
20
|
+
Piecewise Linear Interpolator
|
|
21
|
+
Approximates scalar field by finding coefficients to a piecewise linear
|
|
22
|
+
equation on a tetrahedral mesh. Uses constant gradient regularisation.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
mesh - TetMesh
|
|
27
|
+
interpolation support
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
self.shape = "rectangular"
|
|
31
|
+
DiscreteInterpolator.__init__(self, mesh)
|
|
32
|
+
# whether to assemble a rectangular matrix or a square matrix
|
|
33
|
+
self.interpolator_type = "P2"
|
|
34
|
+
self.support = mesh
|
|
35
|
+
|
|
36
|
+
self.interpolation_weights = {
|
|
37
|
+
"cgw": 0.1,
|
|
38
|
+
"cpw": 1.0,
|
|
39
|
+
"npw": 1.0,
|
|
40
|
+
"gpw": 1.0,
|
|
41
|
+
"tpw": 1.0,
|
|
42
|
+
"ipw": 1.0,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def setup_interpolator(self, **kwargs):
|
|
46
|
+
"""
|
|
47
|
+
Searches through kwargs for any interpolation weights and updates
|
|
48
|
+
the dictionary.
|
|
49
|
+
Then adds the constraints to the linear system using the
|
|
50
|
+
interpolation weights values
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
kwargs -
|
|
54
|
+
interpolation weights
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
# can't reset here, clears fold constraints
|
|
61
|
+
# self.reset()
|
|
62
|
+
for key in kwargs:
|
|
63
|
+
if "regularisation" in kwargs:
|
|
64
|
+
self.interpolation_weights["cgw"] = 0.1 * kwargs["regularisation"]
|
|
65
|
+
self.up_to_date = False
|
|
66
|
+
self.interpolation_weights[key] = kwargs[key]
|
|
67
|
+
if self.interpolation_weights["cgw"] > 0.0:
|
|
68
|
+
self.up_to_date = False
|
|
69
|
+
self.minimise_edge_jumps(self.interpolation_weights["cgw"])
|
|
70
|
+
# direction_feature=kwargs.get("direction_feature", None),
|
|
71
|
+
# direction_vector=kwargs.get("direction_vector", None),
|
|
72
|
+
# )
|
|
73
|
+
self.minimise_grad_steepness(
|
|
74
|
+
w=self.interpolation_weights.get("steepness_weight", 0.01),
|
|
75
|
+
wtfunc=self.interpolation_weights.get("steepness_wtfunc", None),
|
|
76
|
+
)
|
|
77
|
+
logger.info(
|
|
78
|
+
"Using constant gradient regularisation w = %f" % self.interpolation_weights["cgw"]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
logger.info(
|
|
82
|
+
"Added %i gradient constraints, %i normal constraints,"
|
|
83
|
+
"%i tangent constraints and %i value constraints"
|
|
84
|
+
% (self.n_g, self.n_n, self.n_t, self.n_i)
|
|
85
|
+
)
|
|
86
|
+
self.add_gradient_constraints(self.interpolation_weights["gpw"])
|
|
87
|
+
self.add_norm_constraints(self.interpolation_weights["npw"])
|
|
88
|
+
self.add_value_constraints(self.interpolation_weights["cpw"])
|
|
89
|
+
self.add_tangent_constraints(self.interpolation_weights["tpw"])
|
|
90
|
+
# self.add_interface_constraints(self.interpolation_weights["ipw"])
|
|
91
|
+
|
|
92
|
+
def copy(self):
|
|
93
|
+
return P2Interpolator(self.support)
|
|
94
|
+
|
|
95
|
+
def add_gradient_constraints(self, w: float = 1.0):
|
|
96
|
+
points = self.get_gradient_constraints()
|
|
97
|
+
if points.shape[0] > 0:
|
|
98
|
+
grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
|
|
99
|
+
inside = elements > -1
|
|
100
|
+
area = self.support.element_size[elements[inside]]
|
|
101
|
+
wt = np.ones(area.shape[0])
|
|
102
|
+
wt *= w * area
|
|
103
|
+
A = np.einsum("ikj,ij->ik", grad[inside, :], points[inside, 3:6])
|
|
104
|
+
B = np.zeros(A.shape[0])
|
|
105
|
+
elements = self.support[elements[inside]]
|
|
106
|
+
self.add_constraints_to_least_squares(A * wt[:, None], B, elements, name="gradient")
|
|
107
|
+
|
|
108
|
+
def add_gradient_orthogonal_constraints(
|
|
109
|
+
self, points: np.ndarray, vector: np.ndarray, w=1.0, B=0
|
|
110
|
+
):
|
|
111
|
+
"""
|
|
112
|
+
constraints scalar field to be orthogonal to a given vector
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
position
|
|
117
|
+
normals
|
|
118
|
+
w
|
|
119
|
+
B
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
if points.shape[0] > 0:
|
|
126
|
+
grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
|
|
127
|
+
inside = elements > -1
|
|
128
|
+
area = self.support.element_size[elements[inside]]
|
|
129
|
+
wt = np.ones(area.shape[0])
|
|
130
|
+
wt *= w * area
|
|
131
|
+
A = np.einsum("ijk,ij->ik", grad[inside, :], vector[inside, :])
|
|
132
|
+
B = np.zeros(A.shape[0])
|
|
133
|
+
elements = self.support.elements[elements[inside]]
|
|
134
|
+
self.add_constraints_to_least_squares(
|
|
135
|
+
A * wt[:, None], B, elements, name="gradient orthogonal"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def add_norm_constraints(self, w: float = 1.0):
|
|
139
|
+
points = self.get_norm_constraints()
|
|
140
|
+
if points.shape[0] > 0:
|
|
141
|
+
grad, elements = self.support.evaluate_shape_derivatives(points[:, :3])
|
|
142
|
+
inside = elements > -1
|
|
143
|
+
area = self.support.element_size[elements[inside]]
|
|
144
|
+
wt = np.ones(area.shape[0])
|
|
145
|
+
wt *= w * area
|
|
146
|
+
elements = np.tile(self.support.elements[elements[inside]], (3, 1, 1))
|
|
147
|
+
elements = elements.swapaxes(0, 1)
|
|
148
|
+
self.add_constraints_to_least_squares(
|
|
149
|
+
grad[inside, :, :] * wt[:, None, None],
|
|
150
|
+
points[inside, 3:6] * wt[:, None],
|
|
151
|
+
elements,
|
|
152
|
+
name="norm",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def add_value_constraints(self, w: float = 1.0):
|
|
156
|
+
points = self.get_value_constraints()
|
|
157
|
+
if points.shape[0] > 1:
|
|
158
|
+
N, elements, mask = self.support.evaluate_shape(points[:, :3])
|
|
159
|
+
# mask = elements > 0
|
|
160
|
+
size = self.support.element_size[elements[mask]]
|
|
161
|
+
wt = np.ones(size.shape[0])
|
|
162
|
+
wt *= w
|
|
163
|
+
self.add_constraints_to_least_squares(
|
|
164
|
+
N[mask, :],
|
|
165
|
+
points[mask, 3],
|
|
166
|
+
self.support.elements[elements[mask], :],
|
|
167
|
+
w=wt,
|
|
168
|
+
name="value",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def minimise_grad_steepness(
|
|
172
|
+
self,
|
|
173
|
+
w: float = 0.1,
|
|
174
|
+
maskall: bool = False,
|
|
175
|
+
wtfunc: Optional[Callable[[np.ndarray], np.ndarray]] = None,
|
|
176
|
+
):
|
|
177
|
+
"""This constraint minimises the second derivative of the gradient
|
|
178
|
+
mimimising the 2nd derivative should prevent high curvature solutions
|
|
179
|
+
It is not added on the borders
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
w : float, optional
|
|
184
|
+
[description], by default 0.1
|
|
185
|
+
maskall : bool, default False
|
|
186
|
+
whether to apply on all elements or just internal elements (default)
|
|
187
|
+
wtfunc : callable, optional
|
|
188
|
+
a function that returns the weight to be applied at xyz. Called on the barycentre
|
|
189
|
+
of the tetrahedron
|
|
190
|
+
"""
|
|
191
|
+
elements = np.arange(0, len(self.support.elements))
|
|
192
|
+
mask = np.ones(self.support.neighbours.shape[0], dtype=bool)
|
|
193
|
+
if not maskall:
|
|
194
|
+
mask[:] = np.all(self.support.neighbours > 3, axis=1)
|
|
195
|
+
|
|
196
|
+
d2 = self.support.evaluate_shape_d2(elements[mask])
|
|
197
|
+
# d2 shape is [ele_idx, deriv, node]
|
|
198
|
+
wt = np.ones(d2.shape[0])
|
|
199
|
+
wt *= w # * self.support.element_size[mask]
|
|
200
|
+
if callable(wtfunc):
|
|
201
|
+
logger.info("Using function to weight gradient steepness")
|
|
202
|
+
wt = wtfunc(self.support.barycentre) * self.support.element_size[mask]
|
|
203
|
+
idc = self.support.elements[elements[mask]]
|
|
204
|
+
for i in range(d2.shape[1]):
|
|
205
|
+
self.add_constraints_to_least_squares(
|
|
206
|
+
d2[:, i, :],
|
|
207
|
+
np.zeros(d2.shape[0]),
|
|
208
|
+
idc[:, :],
|
|
209
|
+
w=wt,
|
|
210
|
+
name=f"gradsteepness_{i}",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def minimise_edge_jumps(
|
|
214
|
+
self,
|
|
215
|
+
w: float = 0.1,
|
|
216
|
+
wtfunc: Optional[Callable[[np.ndarray], np.ndarray]] = None,
|
|
217
|
+
vector_func: Optional[Callable[[np.ndarray], np.ndarray]] = None,
|
|
218
|
+
quadrature_points: Optional[int] = None,
|
|
219
|
+
):
|
|
220
|
+
"""_summary_
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
w : float, optional
|
|
225
|
+
_description_, by default 0.1
|
|
226
|
+
wtfunc : callable, optional
|
|
227
|
+
_description_, by default None
|
|
228
|
+
vector_func : callable, optional
|
|
229
|
+
_description_, by default None
|
|
230
|
+
"""
|
|
231
|
+
# NOTE: imposes \phi_T1(xi)-\phi_T2(xi) dot n =0
|
|
232
|
+
# iterate over all triangles
|
|
233
|
+
|
|
234
|
+
cp, weight = self.support.get_quadrature_points()
|
|
235
|
+
|
|
236
|
+
norm = self.support.shared_element_norm
|
|
237
|
+
|
|
238
|
+
# evaluate normal if using vector func for cp1
|
|
239
|
+
for i in range(cp.shape[1]):
|
|
240
|
+
if callable(vector_func):
|
|
241
|
+
norm = vector_func(cp[:, i, :])
|
|
242
|
+
# evaluate the shape function for the edges for each neighbouring triangle
|
|
243
|
+
cp_Dt, cp_tri1 = self.support.evaluate_shape_derivatives(
|
|
244
|
+
cp[:, i, :], elements=self.support.shared_element_relationships[:, 0]
|
|
245
|
+
)
|
|
246
|
+
cp_Dn, cp_tri2 = self.support.evaluate_shape_derivatives(
|
|
247
|
+
cp[:, i, :], elements=self.support.shared_element_relationships[:, 1]
|
|
248
|
+
)
|
|
249
|
+
# constraint for each cp is triangle - neighbour create a Nx12 matrix
|
|
250
|
+
const_t_cp = np.einsum("ij,ijk->ik", norm, cp_Dt)
|
|
251
|
+
const_n_cp = -np.einsum("ij,ijk->ik", norm, cp_Dn)
|
|
252
|
+
|
|
253
|
+
const_cp = np.hstack([const_t_cp, const_n_cp])
|
|
254
|
+
tri_cp = np.hstack([self.support.elements[cp_tri1], self.support.elements[cp_tri2]])
|
|
255
|
+
wt = np.zeros(tri_cp.shape[0])
|
|
256
|
+
wt[:] = w * weight[:, i]
|
|
257
|
+
if wtfunc:
|
|
258
|
+
wt = wtfunc(tri_cp)
|
|
259
|
+
self.add_constraints_to_least_squares(
|
|
260
|
+
const_cp,
|
|
261
|
+
np.zeros(const_cp.shape[0]),
|
|
262
|
+
tri_cp,
|
|
263
|
+
w=wt,
|
|
264
|
+
name=f"shared element jump cp{i}",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def evaluate_d2(self, evaluation_points: np.ndarray) -> np.ndarray:
|
|
268
|
+
evaluation_points = np.array(evaluation_points)
|
|
269
|
+
evaluated = np.zeros(evaluation_points.shape[0])
|
|
270
|
+
mask = np.any(evaluation_points == np.nan, axis=1)
|
|
271
|
+
|
|
272
|
+
if evaluation_points[~mask, :].shape[0] > 0:
|
|
273
|
+
evaluated[~mask] = self.support.evaluate_d2(evaluation_points[~mask], self.c)
|
|
274
|
+
return evaluated
|
|
275
|
+
|
|
276
|
+
def add_interface_constraints(self, w: float = 1):
|
|
277
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapper for using surfepy
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ..utils.helper import get_vectors
|
|
6
|
+
from ..interpolators import GeologicalInterpolator
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..utils import getLogger
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
import surfepy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SurfeRBFInterpolator(GeologicalInterpolator):
|
|
17
|
+
""" """
|
|
18
|
+
|
|
19
|
+
def __init__(self, method="single_surface"):
|
|
20
|
+
GeologicalInterpolator.__init__(self)
|
|
21
|
+
self.surfe = None
|
|
22
|
+
if method == "single_surface":
|
|
23
|
+
logger.info("Using single surface interpolator")
|
|
24
|
+
self.surfe = surfepy.Surfe_API(1)
|
|
25
|
+
if method == "Lajaunie" or method == "increments":
|
|
26
|
+
logger.info("Using Lajaunie method")
|
|
27
|
+
self.surfe = surfepy.Surfe_API(2)
|
|
28
|
+
if method == "horizons":
|
|
29
|
+
logger.info("Using surfe horizon")
|
|
30
|
+
self.surfe = surfepy.Surfe_API(4)
|
|
31
|
+
|
|
32
|
+
def add_gradient_ctr_pts(self):
|
|
33
|
+
points = self.get_gradient_constraints()
|
|
34
|
+
if points.shape[0] > 0:
|
|
35
|
+
logger.info("Adding ")
|
|
36
|
+
strike_vector, dip_vector = get_vectors(points[:, 3:6])
|
|
37
|
+
|
|
38
|
+
strike_vector = np.hstack([points[:, :3], strike_vector.T])
|
|
39
|
+
dip_vector = np.hstack([points[:, :3], dip_vector.T])
|
|
40
|
+
self.surfe.SetTangentConstraints(strike_vector)
|
|
41
|
+
self.surfe.SetTangentConstraints(dip_vector)
|
|
42
|
+
|
|
43
|
+
def add_norm_ctr_pts(self):
|
|
44
|
+
points = self.get_norm_constraints()
|
|
45
|
+
if points.shape[0] > 0:
|
|
46
|
+
self.surfe.SetPlanarConstraints(points[:, :6])
|
|
47
|
+
|
|
48
|
+
def add_ctr_pts(self):
|
|
49
|
+
|
|
50
|
+
points = self.get_value_constraints()
|
|
51
|
+
if points.shape[0] > 0:
|
|
52
|
+
# self.surfe.SetInterfaceConstraints(points[:,:4])
|
|
53
|
+
for i in range(points.shape[0]):
|
|
54
|
+
|
|
55
|
+
self.surfe.AddInterfaceConstraint(
|
|
56
|
+
points[i, 0],
|
|
57
|
+
points[i, 1],
|
|
58
|
+
points[i, 2],
|
|
59
|
+
points[i, 3],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def add_tangent_ctr_pts(self):
|
|
63
|
+
points = self.get_tangent_constraints()
|
|
64
|
+
if points.shape[0] > 0:
|
|
65
|
+
self.surfe.SetTangentConstraints(points[:, :6])
|
|
66
|
+
|
|
67
|
+
def _solve(self, **kwargs):
|
|
68
|
+
self.surfe.ComputeInterpolant()
|
|
69
|
+
|
|
70
|
+
def _setup_interpolator(self, **kwargs):
|
|
71
|
+
"""
|
|
72
|
+
Setup the interpolator
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
kernel: str
|
|
77
|
+
kernel for interpolation r3, r, Gaussian, Multiquadrics, Inverse Multiquadrics
|
|
78
|
+
Thin Plate Spline, WendlandC2, MaternC4
|
|
79
|
+
regression: float
|
|
80
|
+
smoothing parameter default 0
|
|
81
|
+
greedy: tuple
|
|
82
|
+
greedy parameters first is interface threshold, second is angular threshold
|
|
83
|
+
default (0,0)
|
|
84
|
+
poly_order: int
|
|
85
|
+
order of the polynomial used for interpolation, default 1
|
|
86
|
+
radius: float
|
|
87
|
+
radius of the kernel, default None but required for SPD kernels
|
|
88
|
+
anisotropy: bool
|
|
89
|
+
apply global anisotropy from eigenvectors of orientation constraints, default False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
self.add_gradient_ctr_pts()
|
|
94
|
+
self.add_norm_ctr_pts()
|
|
95
|
+
self.add_ctr_pts()
|
|
96
|
+
self.add_tangent_ctr_pts()
|
|
97
|
+
|
|
98
|
+
kernel = kwargs.get("kernel", "r3")
|
|
99
|
+
logger.info("Setting surfe RBF kernel to %s" % kernel)
|
|
100
|
+
self.surfe.SetRBFKernel(kernel)
|
|
101
|
+
regression = kwargs.get("regression_smoothing", 0.0)
|
|
102
|
+
if regression > 0:
|
|
103
|
+
logger.info("Using regression smoothing %f" % regression)
|
|
104
|
+
self.surfe.SetRegressionSmoothing(True, regression)
|
|
105
|
+
greedy = kwargs.get("greedy", (0, 0))
|
|
106
|
+
|
|
107
|
+
if greedy[0] > 0 or greedy[1] > 0:
|
|
108
|
+
logger.info(
|
|
109
|
+
"Using greedy algorithm: inferface %f and angular %f" % (greedy[0], greedy[1])
|
|
110
|
+
)
|
|
111
|
+
self.surfe.SetGreedyAlgorithm(True, greedy[0], greedy[1])
|
|
112
|
+
poly_order = kwargs.get("poly_order", None)
|
|
113
|
+
if poly_order:
|
|
114
|
+
logger.info("Setting poly order to %i" % poly_order)
|
|
115
|
+
self.surfe.SetPolynomialOrder(poly_order)
|
|
116
|
+
global_anisotropy = kwargs.get("anisotropy", False)
|
|
117
|
+
if global_anisotropy:
|
|
118
|
+
logger.info("Using global anisotropy")
|
|
119
|
+
self.surfe.SetGlobalAnisotropy(global_anisotropy)
|
|
120
|
+
radius = kwargs.get("radius", False)
|
|
121
|
+
if radius:
|
|
122
|
+
logger.info("Setting RBF radius to %f" % radius)
|
|
123
|
+
self.surfe.SetRBFShapeParameter(radius)
|
|
124
|
+
|
|
125
|
+
def update(self):
|
|
126
|
+
return self.surfe.InterpolantComputed()
|
|
127
|
+
|
|
128
|
+
def evaluate_value(self, evaluation_points):
|
|
129
|
+
"""Evaluate surfe interpolant at points
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
evaluation_points : array of locations N,3
|
|
134
|
+
xyz of locations to evaluate
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
np.array (N)
|
|
139
|
+
value of interpolant at points
|
|
140
|
+
"""
|
|
141
|
+
evaluation_points = np.array(evaluation_points)
|
|
142
|
+
evaluated = np.zeros(evaluation_points.shape[0])
|
|
143
|
+
mask = np.any(evaluation_points == np.nan, axis=1)
|
|
144
|
+
|
|
145
|
+
if evaluation_points[~mask, :].shape[0] > 0:
|
|
146
|
+
evaluated[~mask] = self.surfe.EvaluateInterpolantAtPoints(evaluation_points[~mask])
|
|
147
|
+
return evaluated
|
|
148
|
+
|
|
149
|
+
def evaluate_gradient(self, evaluation_points):
|
|
150
|
+
"""Evaluate surfe interpolant gradient at points
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
evaluation_points : array of locations N,3
|
|
155
|
+
xyz of locations to evaluate
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
np.array (N,3)
|
|
160
|
+
gradient of interpolant at points
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
evaluation_points = np.array(evaluation_points)
|
|
164
|
+
evaluated = np.zeros(evaluation_points.shape)
|
|
165
|
+
mask = np.any(evaluation_points == np.nan, axis=1)
|
|
166
|
+
if evaluation_points[~mask, :].shape[0] > 0:
|
|
167
|
+
evaluated[~mask, :] = self.surfe.EvaluateVectorInterpolantAtPoints(
|
|
168
|
+
evaluation_points[~mask]
|
|
169
|
+
)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def nx(self):
|
|
174
|
+
return self.get_data_locations().shape[0]
|