11"""
22Geological features
33"""
4-
4+ from LoopStructural . utils . maths import regular_tetraherdron_for_points , gradient_from_tetrahedron
55from ...modelling .features import BaseFeature
66from ...utils import getLogger
77from ...modelling .features import FeatureType
88import numpy as np
99from typing import Callable , Optional
10+ from ...utils import LoopValueError
1011
1112logger = getLogger (__name__ )
1213
@@ -68,11 +69,11 @@ def evaluate_value(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray:
6869 mask = self ._calculate_mask (pos , ignore_regions = ignore_regions )
6970 pos = self ._apply_faults (pos )
7071 if self .function is not None :
71-
72+
7273 v [mask ] = self .function (pos [mask ,:])
7374 return v
7475
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 :
7677 """_summary_
7778
7879 Parameters
@@ -85,7 +86,60 @@ def evaluate_gradient(self, pos: np.ndarray, ignore_regions=False) -> np.ndarray
8586 np.ndarray
8687 _description_
8788 """
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
88103 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
89143 if self .gradient_function is None :
90144 v [:, :] = np .nan
91145 else :
0 commit comments