1
1
"""
2
2
Geological features
3
3
"""
4
-
4
+ from LoopStructural . utils . maths import regular_tetraherdron_for_points , gradient_from_tetrahedron
5
5
from ...modelling .features import BaseFeature
6
6
from ...utils import getLogger
7
7
from ...modelling .features import FeatureType
8
8
import numpy as np
9
9
from typing import Callable , Optional
10
+ from ...utils import LoopValueError
10
11
11
12
logger = getLogger (__name__ )
12
13
@@ -68,11 +69,11 @@ def evaluate_value(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
68
69
mask = self ._calculate_mask (pos , ignore_regions = ignore_regions )
69
70
pos = self ._apply_faults (pos )
70
71
if self .function is not None :
71
-
72
+
72
73
v [mask ] = self .function (pos [mask ,:])
73
74
return v
74
75
75
- def evaluate_gradient (self , pos : np .ndarray , ignore_regions = False ) -> np .ndarray :
76
+ def evaluate_gradient (self , pos : np .ndarray , ignore_regions = False , element_scale_parameter = None ) -> np .ndarray :
76
77
"""_summary_
77
78
78
79
Parameters
@@ -85,7 +86,60 @@ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray
85
86
np.ndarray
86
87
_description_
87
88
"""
89
+ if pos .shape [1 ] != 3 :
90
+ raise LoopValueError ("Need Nx3 array of xyz points to evaluate gradient" )
91
+ logger .info (f'Calculating gradient for { self .name } ' )
92
+ if element_scale_parameter is None :
93
+ if self .model is not None :
94
+ element_scale_parameter = np .min (self .model .bounding_box .step_vector ) / 10
95
+ else :
96
+ element_scale_parameter = 1
97
+ else :
98
+ try :
99
+ element_scale_parameter = float (element_scale_parameter )
100
+ except ValueError :
101
+ logger .error ("element_scale_parameter must be a float" )
102
+ element_scale_parameter = 1
88
103
v = np .zeros ((pos .shape [0 ], 3 ))
104
+ v = np .zeros (pos .shape )
105
+ v [:] = np .nan
106
+ mask = self ._calculate_mask (pos , ignore_regions = ignore_regions )
107
+ # evaluate the faults on the nodes of the faulted feature support
108
+ # then evaluate the gradient at these points
109
+ if len (self .faults ) > 0 :
110
+ # generate a regular tetrahedron for each point
111
+ # we will then move these points by the fault and then recalculate the gradient.
112
+ # this should work...
113
+ resolved = False
114
+ tetrahedron = regular_tetraherdron_for_points (pos , element_scale_parameter )
115
+
116
+ while resolved :
117
+ for f in self .faults :
118
+ v = (
119
+ f [0 ]
120
+ .evaluate_value (tetrahedron .reshape (- 1 , 3 ), fillnan = 'nearest' )
121
+ .reshape (tetrahedron .shape [0 ], 4 )
122
+ )
123
+ flag = np .logical_or (np .all (v > 0 , axis = 1 ), np .all (v < 0 , axis = 1 ))
124
+ if np .any (~ flag ):
125
+ logger .warning (
126
+ f"Points are too close to fault { f [0 ].name } . Refining the tetrahedron"
127
+ )
128
+ element_scale_parameter *= 0.5
129
+ tetrahedron = regular_tetraherdron_for_points (pos , element_scale_parameter )
130
+
131
+ resolved = True
132
+
133
+ tetrahedron_faulted = self ._apply_faults (np .array (tetrahedron .reshape (- 1 , 3 ))).reshape (
134
+ tetrahedron .shape
135
+ )
136
+
137
+ values = self .function (tetrahedron_faulted .reshape (- 1 , 3 )).reshape (
138
+ (- 1 , 4 )
139
+ )
140
+ v [mask , :] = gradient_from_tetrahedron (tetrahedron [mask , :, :], values [mask ])
141
+
142
+ return v
89
143
if self .gradient_function is None :
90
144
v [:, :] = np .nan
91
145
else :
0 commit comments