Skip to content

Commit 7e6da5b

Browse files
committed
fix: gradient of faulted feature will not be aliased by the interpolation grid
1 parent 37bbb88 commit 7e6da5b

File tree

3 files changed

+104
-55
lines changed

3 files changed

+104
-55
lines changed

LoopStructural/modelling/features/_geological_feature.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Geological features
33
"""
44

5+
from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
56
from ...modelling.features import BaseFeature
67
from ...utils import getLogger
78
from ...modelling.features import FeatureType
@@ -131,7 +132,9 @@ def evaluate_value(self, pos: np.ndarray, ignore_regions=False, fillnan=None) ->
131132
v[nanmask] = v[~nanmask][i]
132133
return v
133134

134-
def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
135+
def evaluate_gradient(
136+
self, pos: np.ndarray, ignore_regions=False, element_scale_parameter=None
137+
) -> np.ndarray:
135138
"""
136139
137140
Parameters
@@ -147,6 +150,17 @@ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray
147150
if pos.shape[1] != 3:
148151
raise LoopValueError("Need Nx3 array of xyz points to evaluate gradient")
149152
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
150164

151165
self.builder.up_to_date()
152166

@@ -156,16 +170,38 @@ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray
156170
# evaluate the faults on the nodes of the faulted feature support
157171
# then evaluate the gradient at these points
158172
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_faulted[mask, :, :], values[mask])
159204

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)
169205
return v
170206
pos = self._apply_faults(pos)
171207
if mask.dtype not in [int, bool]:

LoopStructural/modelling/features/fault/_fault_segment.py

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from LoopStructural.utils.maths import regular_tetraherdron_for_points, gradient_from_tetrahedron
12
from ....modelling.features.fault._fault_function_feature import (
23
FaultDisplacementFeature,
34
)
@@ -390,51 +391,12 @@ def apply_to_vectors(self, vector: np.ndarray, scale_parameter: float = 1.0) ->
390391
# the nodes of the tetrahedron are then restored by the fault and then gradient is
391392
# recalculated using the updated node positions but the original corner values
392393

393-
regular_tetrahedron = np.array(
394-
[
395-
[np.sqrt(8 / 9), 0, -1 / 3],
396-
[-np.sqrt(2 / 9), np.sqrt(2 / 3), -1 / 3],
397-
[-np.sqrt(2 / 9), -np.sqrt(2 / 3), -1 / 3],
398-
[0, 0, 1],
399-
]
400-
)
401-
regular_tetrahedron *= scale_parameter
402-
xyz = vector[:, :3]
403-
tetrahedron = np.zeros((xyz.shape[0], 4, 3))
404-
tetrahedron[:] = xyz[:, None, :]
405-
tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
406-
407-
vectors = vector[:, 3:]
408-
corners = np.einsum('ikj,ij->ik', tetrahedron - xyz[:, None, :], vectors)
409-
tetrahedron = tetrahedron.reshape(-1, 3)
410-
tetrahedron = self.apply_to_points(tetrahedron)
411-
tetrahedron = tetrahedron.reshape(-1, 4, 3)
412-
m = np.array(
413-
[
414-
[
415-
(tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
416-
(tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
417-
(tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
418-
],
419-
[
420-
(tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
421-
(tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
422-
(tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
423-
],
424-
[
425-
(tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
426-
(tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
427-
(tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
428-
],
429-
]
430-
)
431-
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]])
432-
m = np.swapaxes(m, 0, 2)
433-
element_gradients = np.linalg.inv(m)
434-
435-
element_gradients = element_gradients.swapaxes(1, 2)
436-
element_gradients = element_gradients @ I
437-
v = np.sum(element_gradients * corners[:, None, :], axis=2)
394+
tetrahedron = regular_tetraherdron_for_points(vector[:, :3], scale_parameter)
395+
corners = np.einsum('ikj,ij->ik', tetrahedron - vector[:, None, :3], vector[:, 3:])
396+
397+
tetrahedron = self.apply_to_points(tetrahedron.reshape(-1, 3)).reshape(-1, 4, 3)
398+
v = gradient_from_tetrahedron(tetrahedron, corners)
399+
438400
return v
439401

440402
def add_abutting_fault(self, abutting_fault_feature, positive=None):

LoopStructural/utils/maths.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,54 @@ def get_dip_vector(strike, dip):
244244
]
245245
)
246246
return v
247+
248+
249+
def regular_tetraherdron_for_points(xyz, scale_parameter):
250+
regular_tetrahedron = np.array(
251+
[
252+
[np.sqrt(8 / 9), 0, -1 / 3],
253+
[-np.sqrt(2 / 9), np.sqrt(2 / 3), -1 / 3],
254+
[-np.sqrt(2 / 9), -np.sqrt(2 / 3), -1 / 3],
255+
[0, 0, 1],
256+
]
257+
)
258+
regular_tetrahedron *= scale_parameter
259+
tetrahedron = np.zeros((xyz.shape[0], 4, 3))
260+
tetrahedron[:] = xyz[:, None, :]
261+
tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
262+
263+
return tetrahedron
264+
265+
266+
def gradient_from_tetrahedron(tetrahedron, value):
267+
"""
268+
Calculate the gradient from a tetrahedron
269+
"""
270+
tetrahedron = tetrahedron.reshape(-1, 4, 3)
271+
m = np.array(
272+
[
273+
[
274+
(tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
275+
(tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
276+
(tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
277+
],
278+
[
279+
(tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
280+
(tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
281+
(tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
282+
],
283+
[
284+
(tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
285+
(tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
286+
(tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
287+
],
288+
]
289+
)
290+
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]])
291+
m = np.swapaxes(m, 0, 2)
292+
element_gradients = np.linalg.inv(m)
293+
294+
element_gradients = element_gradients.swapaxes(1, 2)
295+
element_gradients = element_gradients @ I
296+
v = np.sum(element_gradients * value[:, None, :], axis=2)
297+
return v

0 commit comments

Comments
 (0)