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,82 @@
|
|
|
1
|
+
from ....modelling.features import BaseFeature, StructuralFrame
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from ....utils import getLogger
|
|
4
|
+
|
|
5
|
+
logger = getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FaultDisplacementFeature(BaseFeature):
|
|
9
|
+
""" """
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
fault_frame,
|
|
14
|
+
displacement,
|
|
15
|
+
name="fault_displacement",
|
|
16
|
+
model=None,
|
|
17
|
+
faults=[],
|
|
18
|
+
regions=[],
|
|
19
|
+
builder=None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Geological feature representing the fault displacement
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
fault_frame - geometry of the fault
|
|
27
|
+
displacement - function defining fault displacement
|
|
28
|
+
"""
|
|
29
|
+
BaseFeature.__init__(self, f"{name}_displacement", model, faults, regions, builder)
|
|
30
|
+
self.fault_frame = StructuralFrame(
|
|
31
|
+
f"{fault_frame.name}_displacementframe",
|
|
32
|
+
[fault_frame[0].copy(), fault_frame[1].copy(), fault_frame[2].copy()],
|
|
33
|
+
)
|
|
34
|
+
self.displacement = displacement
|
|
35
|
+
|
|
36
|
+
def evaluate_value(self, location):
|
|
37
|
+
"""
|
|
38
|
+
Return the value of the fault displacement
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
location
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
fault_suface = self.fault_frame.features[0].evaluate_value(location)
|
|
49
|
+
fault_displacement = self.fault_frame.features[1].evaluate_value(location)
|
|
50
|
+
fault_strike = self.fault_frame.features[2].evaluate_value(location)
|
|
51
|
+
d = self.displacement(fault_suface, fault_displacement, fault_strike)
|
|
52
|
+
return d
|
|
53
|
+
|
|
54
|
+
def evaluate_gradient(self, location):
|
|
55
|
+
"""
|
|
56
|
+
get the scaled displacement
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
location
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
fault_suface = self.fault_frame.features[0].evaluate_value(location)
|
|
67
|
+
fault_displacement = self.fault_frame.features[1].evaluate_value(location)
|
|
68
|
+
fault_strike = self.fault_frame.features[2].evaluate_value(location)
|
|
69
|
+
d = self.displacement(fault_suface, fault_displacement, fault_strike)
|
|
70
|
+
return d
|
|
71
|
+
|
|
72
|
+
def evaluate_on_surface(self, location):
|
|
73
|
+
"""
|
|
74
|
+
TODO what is this for?
|
|
75
|
+
"""
|
|
76
|
+
fault_displacement = self.fault_frame.features[1].evaluate_value(location)
|
|
77
|
+
fault_strike = self.fault_frame.features[2].evaluate_value(location)
|
|
78
|
+
d = self.displacement.evaluate(fault_displacement, fault_strike)
|
|
79
|
+
return d
|
|
80
|
+
|
|
81
|
+
def get_data(self, value_map: Optional[dict] = None):
|
|
82
|
+
pass
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
from ....modelling.features.fault._fault_function_feature import (
|
|
2
|
+
FaultDisplacementFeature,
|
|
3
|
+
)
|
|
4
|
+
from ....modelling.features import FeatureType
|
|
5
|
+
from ....modelling.features.fault._fault_function import BaseFault, BaseFault3D
|
|
6
|
+
from ....utils import getLogger, NegativeRegion, PositiveRegion
|
|
7
|
+
from ....modelling.features import StructuralFrame
|
|
8
|
+
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
use_threads = False # there is a bug with threading causing the interpolation dictionary to change size during iterations
|
|
15
|
+
# for now 19/4/24 LG is turning threading off. This will slow down evaluation of models a bit.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FaultSegment(StructuralFrame):
|
|
19
|
+
"""
|
|
20
|
+
Class for representing a slip event of a fault
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self, features, name, faultfunction=None, steps=10, displacement=1.0, fold=None, model=None
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
A slip event of a fault
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
faultframe : FaultFrame
|
|
32
|
+
the fault frame defining the faut geometry
|
|
33
|
+
faultfunction : function/lambda function
|
|
34
|
+
optional displacement function for spatially variable fault displacement
|
|
35
|
+
steps : int
|
|
36
|
+
how many integration steps for faults
|
|
37
|
+
kwargs
|
|
38
|
+
"""
|
|
39
|
+
StructuralFrame.__init__(self, features, name, fold, model)
|
|
40
|
+
self.type = FeatureType.FAULT
|
|
41
|
+
self.displacement = displacement
|
|
42
|
+
self._faultfunction = BaseFault.fault_displacement
|
|
43
|
+
self.steps = steps
|
|
44
|
+
self.regions = []
|
|
45
|
+
self.faults_enabled = True
|
|
46
|
+
|
|
47
|
+
self.builder = None
|
|
48
|
+
self.splay = {}
|
|
49
|
+
self.abut = {}
|
|
50
|
+
self.fault_offset = 0.0
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def faultfunction(self):
|
|
54
|
+
return self._faultfunction
|
|
55
|
+
|
|
56
|
+
@faultfunction.setter
|
|
57
|
+
def faultfunction(self, value):
|
|
58
|
+
if callable(value):
|
|
59
|
+
self._faultfunction = value
|
|
60
|
+
elif isinstance(value, str) and value == "BaseFault":
|
|
61
|
+
self._faultfunction = BaseFault.fault_displacement
|
|
62
|
+
elif isinstance(value, str) and value == "BaseFault3D":
|
|
63
|
+
self._faultfunction = BaseFault3D.fault_displacement
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError("Fault function must be a function or BaseFault")
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def fault_normal_vector(self):
|
|
69
|
+
if self.builder is None:
|
|
70
|
+
raise ValueError("Fault builder not set")
|
|
71
|
+
return self.builder.fault_normal_vector
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def fault_slip_vector(self):
|
|
75
|
+
if self.builder is None:
|
|
76
|
+
raise ValueError("Fault builder not set")
|
|
77
|
+
return self.builder.fault_slip_vector
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def fault_strike_vector(self):
|
|
81
|
+
if self.builder is None:
|
|
82
|
+
raise ValueError("Fault builder not set")
|
|
83
|
+
return self.builder.fault_strike_vector
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def fault_minor_axis(self):
|
|
87
|
+
if self.builder is None:
|
|
88
|
+
raise ValueError("Fault builder not set")
|
|
89
|
+
return self.builder.fault_minor_axis
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def fault_major_axis(self):
|
|
93
|
+
if self.builder is None:
|
|
94
|
+
raise ValueError("Fault builder not set")
|
|
95
|
+
return self.builder.fault_major_axis
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def fault_intermediate_axis(self):
|
|
99
|
+
if self.builder is None:
|
|
100
|
+
raise ValueError("Fault builder not set")
|
|
101
|
+
return self.builder.fault_intermediate_axis
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def fault_centre(self):
|
|
105
|
+
if self.builder is None:
|
|
106
|
+
raise ValueError("Fault builder not set")
|
|
107
|
+
return self.builder.fault_centre
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def displacementfeature(self):
|
|
111
|
+
return FaultDisplacementFeature(self, self.faultfunction, name=self.name, model=self.model)
|
|
112
|
+
|
|
113
|
+
def set_fault_offset(self, offset: float):
|
|
114
|
+
self.fault_offset = offset
|
|
115
|
+
|
|
116
|
+
def set_model(self, model):
|
|
117
|
+
"""
|
|
118
|
+
Link a geological model to the feature
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
model - GeologicalModel
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
self.model = model
|
|
129
|
+
|
|
130
|
+
def set_displacement(self, displacement, scale=True):
|
|
131
|
+
"""
|
|
132
|
+
Set the fault displacement to a new value
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
displacement - double
|
|
137
|
+
scale - boolean
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
if scale and self.model is not None:
|
|
144
|
+
self.displacement = displacement / self.model.scale_factor
|
|
145
|
+
elif not scale:
|
|
146
|
+
self.displacement = displacement
|
|
147
|
+
else:
|
|
148
|
+
logger.warning("Displacement not updated")
|
|
149
|
+
|
|
150
|
+
def evaluate(self, locations):
|
|
151
|
+
"""
|
|
152
|
+
Evaluate which side of fault
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
locations numpy array
|
|
157
|
+
location to evaluate
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
boolean array true if on hanging wall, false if on footwall
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
v = self.__getitem__(0).evaluate_value(locations)
|
|
165
|
+
v[~np.isnan(v)] = v[~np.isnan(v)] > 0
|
|
166
|
+
v[np.isnan(v)] = 0
|
|
167
|
+
return v.astype(bool)
|
|
168
|
+
|
|
169
|
+
def inside_volume(self, locations, threshold=0.001):
|
|
170
|
+
# v = self.faultframe.evaluate_value(locations)
|
|
171
|
+
v = self.evaluate_displacement(locations) / self.displacement
|
|
172
|
+
v[np.isnan(v)] = 0
|
|
173
|
+
return np.abs(v) > threshold
|
|
174
|
+
# return np.all(np.logical_and(v > -1,v<1),axis=1)
|
|
175
|
+
|
|
176
|
+
def evaluate_value(self, locations):
|
|
177
|
+
"""
|
|
178
|
+
Return the value of the fault surface scalar field
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
locations - numpy array
|
|
183
|
+
location to evaluate scalar field
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
|
|
188
|
+
"""
|
|
189
|
+
# v = np.zeros(locations.shape[0])
|
|
190
|
+
# v[:] = np.nan
|
|
191
|
+
# mask =
|
|
192
|
+
# mask = np.zeros(locations.shape[0]).astype(bool)
|
|
193
|
+
# mask[:] = True
|
|
194
|
+
# # check regions
|
|
195
|
+
# for r in self.regions:
|
|
196
|
+
# try:
|
|
197
|
+
# mask = np.logical_and(mask, r(locations))
|
|
198
|
+
# except:
|
|
199
|
+
# logger.error("nan slicing")
|
|
200
|
+
# v[mask] = self.__getitem__(0).evaluate_value(locations[mask, :])
|
|
201
|
+
return self.__getitem__(0).evaluate_value(locations)
|
|
202
|
+
|
|
203
|
+
def mean(self):
|
|
204
|
+
return self.__getitem__(0).mean()
|
|
205
|
+
|
|
206
|
+
def max(self):
|
|
207
|
+
return self.__getitem__(0).max()
|
|
208
|
+
|
|
209
|
+
def min(self):
|
|
210
|
+
return self.__getitem__(0).min()
|
|
211
|
+
|
|
212
|
+
def evaluate_gradient(self, locations):
|
|
213
|
+
"""
|
|
214
|
+
Return the fault slip direction at the location
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
locations - numpy array Nx3
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
v = np.zeros(locations.shape)
|
|
226
|
+
v[:, :] = np.nan
|
|
227
|
+
mask = np.zeros(locations.shape[0]).astype(bool)
|
|
228
|
+
mask[:] = True
|
|
229
|
+
# check regions
|
|
230
|
+
for r in self.regions:
|
|
231
|
+
try:
|
|
232
|
+
mask = np.logical_and(mask, r(locations))
|
|
233
|
+
except:
|
|
234
|
+
logger.error("nan slicing ")
|
|
235
|
+
# need to scale with fault displacement
|
|
236
|
+
v[mask, :] = self.__getitem__(1).evaluate_gradient(locations[mask, :])
|
|
237
|
+
scale = self.displacementfeature.evaluate_value(locations[mask, :])
|
|
238
|
+
v[mask, :] *= scale[:, None]
|
|
239
|
+
return v
|
|
240
|
+
|
|
241
|
+
def evaluate_displacement(self, points):
|
|
242
|
+
newp = np.copy(points).astype(float)
|
|
243
|
+
# evaluate fault function for all
|
|
244
|
+
# points then define mask for only points affected by fault
|
|
245
|
+
gx = None
|
|
246
|
+
gy = None
|
|
247
|
+
gz = None
|
|
248
|
+
if use_threads:
|
|
249
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
250
|
+
# all of these operations should be independent so
|
|
251
|
+
# just run as different threads
|
|
252
|
+
gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp)
|
|
253
|
+
gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp)
|
|
254
|
+
gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp)
|
|
255
|
+
gx = gx_future.result()
|
|
256
|
+
gy = gy_future.result()
|
|
257
|
+
gz = gz_future.result()
|
|
258
|
+
else:
|
|
259
|
+
gx = self.__getitem__(0).evaluate_value(newp)
|
|
260
|
+
gy = self.__getitem__(1).evaluate_value(newp)
|
|
261
|
+
gz = self.__getitem__(2).evaluate_value(newp)
|
|
262
|
+
d = np.zeros(gx.shape)
|
|
263
|
+
mask = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
|
|
264
|
+
mask = np.logical_and(mask, ~np.isnan(gz))
|
|
265
|
+
d[~mask] = 0
|
|
266
|
+
gx_mask = np.zeros_like(mask, dtype=bool)
|
|
267
|
+
gx_mask[mask] = gx[mask] > 0
|
|
268
|
+
d[gx_mask] = 1.0
|
|
269
|
+
if self.faultfunction is not None:
|
|
270
|
+
d[mask] = self.faultfunction(gx[mask] + self.fault_offset, gy[mask], gz[mask])
|
|
271
|
+
return d * self.displacement
|
|
272
|
+
|
|
273
|
+
def apply_to_points(self, points, reverse=False):
|
|
274
|
+
"""
|
|
275
|
+
Unfault the array of points
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
points - numpy array Nx3
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
|
|
284
|
+
"""
|
|
285
|
+
steps = self.steps
|
|
286
|
+
newp = np.copy(points).astype(float)
|
|
287
|
+
# evaluate fault function for all points
|
|
288
|
+
# then define mask for only points affected by fault
|
|
289
|
+
gx = None
|
|
290
|
+
gy = None
|
|
291
|
+
gz = None
|
|
292
|
+
if use_threads:
|
|
293
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
294
|
+
# all of these operations should be
|
|
295
|
+
# independent so just run as different threads
|
|
296
|
+
gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp)
|
|
297
|
+
gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp)
|
|
298
|
+
gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp)
|
|
299
|
+
gx = gx_future.result()
|
|
300
|
+
gy = gy_future.result()
|
|
301
|
+
gz = gz_future.result()
|
|
302
|
+
else:
|
|
303
|
+
gx = self.__getitem__(0).evaluate_value(newp)
|
|
304
|
+
gy = self.__getitem__(1).evaluate_value(newp)
|
|
305
|
+
gz = self.__getitem__(2).evaluate_value(newp)
|
|
306
|
+
d = np.zeros(gx.shape)
|
|
307
|
+
mask = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
|
|
308
|
+
mask = np.logical_and(mask, ~np.isnan(gz))
|
|
309
|
+
d[~mask] = 0
|
|
310
|
+
gx_mask = np.zeros_like(mask, dtype=bool)
|
|
311
|
+
gx_mask[mask] = gx[mask] > 0
|
|
312
|
+
d[gx_mask] = 1.0
|
|
313
|
+
if self.faultfunction is not None:
|
|
314
|
+
d[mask] = self.faultfunction(gx[mask], gy[mask], gz[mask])
|
|
315
|
+
mask = np.abs(d) > 0.0
|
|
316
|
+
|
|
317
|
+
d *= self.displacement
|
|
318
|
+
if reverse:
|
|
319
|
+
d *= -1.0
|
|
320
|
+
# calculate the fault frame for the evaluation points
|
|
321
|
+
for _i in range(steps):
|
|
322
|
+
gx = None
|
|
323
|
+
gy = None
|
|
324
|
+
gz = None
|
|
325
|
+
g = None
|
|
326
|
+
if use_threads:
|
|
327
|
+
with ThreadPoolExecutor(max_workers=8) as executor:
|
|
328
|
+
# all of these operations should be
|
|
329
|
+
# independent so just run as different threads
|
|
330
|
+
gx_future = executor.submit(self.__getitem__(0).evaluate_value, newp[mask, :])
|
|
331
|
+
g_future = executor.submit(self.__getitem__(1).evaluate_gradient, newp[mask, :])
|
|
332
|
+
gy_future = executor.submit(self.__getitem__(1).evaluate_value, newp[mask, :])
|
|
333
|
+
gz_future = executor.submit(self.__getitem__(2).evaluate_value, newp[mask, :])
|
|
334
|
+
gx = gx_future.result()
|
|
335
|
+
g = g_future.result()
|
|
336
|
+
gy = gy_future.result()
|
|
337
|
+
gz = gz_future.result()
|
|
338
|
+
else:
|
|
339
|
+
gx = self.__getitem__(0).evaluate_value(newp[mask, :])
|
|
340
|
+
gy = self.__getitem__(1).evaluate_value(newp[mask, :])
|
|
341
|
+
gz = self.__getitem__(2).evaluate_value(newp[mask, :])
|
|
342
|
+
g = self.__getitem__(1).evaluate_gradient(newp[mask, :], ignore_regions=True)
|
|
343
|
+
# # get the fault frame val/grad for the points
|
|
344
|
+
# determine displacement magnitude, for constant displacement
|
|
345
|
+
# hanging wall should be > 0
|
|
346
|
+
d = np.zeros(gx.shape)
|
|
347
|
+
mask2 = np.logical_and(~np.isnan(gx), ~np.isnan(gy))
|
|
348
|
+
mask2 = np.logical_and(mask2, ~np.isnan(gz))
|
|
349
|
+
d[~mask2] = 0
|
|
350
|
+
gx_mask2 = np.zeros_like(mask2, dtype=bool)
|
|
351
|
+
gx_mask2[mask2] = gx[mask2] > 0
|
|
352
|
+
# d[~np.isnan(gx)][gx[~np.isnan(gx)]>0] = 1
|
|
353
|
+
d[gx_mask2] = 1.0
|
|
354
|
+
# d[mask2][gx[mask2] < 0] = 0.
|
|
355
|
+
# d[gx < 0] = 0.
|
|
356
|
+
if self.faultfunction is not None:
|
|
357
|
+
d[mask2] = self.faultfunction(gx[mask2], gy[mask2], gz[mask2])
|
|
358
|
+
d *= self.displacement
|
|
359
|
+
# normalise when length is >0
|
|
360
|
+
g_mag = np.zeros(g.shape[0])
|
|
361
|
+
g_mag[mask2] = np.linalg.norm(g[mask2], axis=1)
|
|
362
|
+
# g_mag = np.linalg.norm(g[mask2], axis=1)
|
|
363
|
+
g[g_mag > 0.0] /= g_mag[g_mag > 0, None]
|
|
364
|
+
# multiply displacement vector by the displacement magnitude for
|
|
365
|
+
# step
|
|
366
|
+
|
|
367
|
+
g *= (1.0 / steps) * d[:, None]
|
|
368
|
+
# newp[mask, :].copy()
|
|
369
|
+
# apply displacement
|
|
370
|
+
newp[mask, :] += g
|
|
371
|
+
|
|
372
|
+
return newp
|
|
373
|
+
|
|
374
|
+
def apply_to_vectors(self, vector: np.ndarray, scale_parameter: float = 1.0) -> np.ndarray:
|
|
375
|
+
"""Rotates the vector through the fault displacement field
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
vector : np.ndarray
|
|
380
|
+
Nx6 array of vectors x,y,z,vx,vy,vz
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
# define a regular tetrahedron as a mask to apply to every vector observation.
|
|
384
|
+
# the regular tetrahedron is located at the location of the vector datapoint
|
|
385
|
+
# and the corner values of the tetrahedron are defined by setting the barycenter
|
|
386
|
+
# of the tetrahedron as 0 and then using the vector to calculate the values of the
|
|
387
|
+
# corner assuming the tetra is a P1 tetra with a linear gradient.
|
|
388
|
+
# a scaling parameter is used to determine the size of the tetrahedron
|
|
389
|
+
# the nodes of the tetrahedron are then restored by the fault and then gradient is
|
|
390
|
+
# recalculated using the updated node positions but the original corner values
|
|
391
|
+
|
|
392
|
+
regular_tetrahedron = np.array(
|
|
393
|
+
[
|
|
394
|
+
[np.sqrt(8 / 9), 0, -1 / 3],
|
|
395
|
+
[-np.sqrt(2 / 9), np.sqrt(2 / 3), -1 / 3],
|
|
396
|
+
[-np.sqrt(2 / 9), -np.sqrt(2 / 3), -1 / 3],
|
|
397
|
+
[0, 0, 1],
|
|
398
|
+
]
|
|
399
|
+
)
|
|
400
|
+
regular_tetrahedron *= scale_parameter
|
|
401
|
+
xyz = vector[:, :3]
|
|
402
|
+
tetrahedron = np.zeros((xyz.shape[0], 4, 3))
|
|
403
|
+
tetrahedron[:] = xyz[:, None, :]
|
|
404
|
+
tetrahedron[:, :, :] += regular_tetrahedron[None, :, :]
|
|
405
|
+
|
|
406
|
+
vectors = vector[:, 3:]
|
|
407
|
+
corners = np.einsum('ikj,ij->ik', tetrahedron - xyz[:, None, :], vectors)
|
|
408
|
+
tetrahedron = tetrahedron.reshape(-1, 3)
|
|
409
|
+
tetrahedron = self.apply_to_points(tetrahedron)
|
|
410
|
+
tetrahedron = tetrahedron.reshape(-1, 4, 3)
|
|
411
|
+
m = np.array(
|
|
412
|
+
[
|
|
413
|
+
[
|
|
414
|
+
(tetrahedron[:, 1, 0] - tetrahedron[:, 0, 0]),
|
|
415
|
+
(tetrahedron[:, 1, 1] - tetrahedron[:, 0, 1]),
|
|
416
|
+
(tetrahedron[:, 1, 2] - tetrahedron[:, 0, 2]),
|
|
417
|
+
],
|
|
418
|
+
[
|
|
419
|
+
(tetrahedron[:, 2, 0] - tetrahedron[:, 0, 0]),
|
|
420
|
+
(tetrahedron[:, 2, 1] - tetrahedron[:, 0, 1]),
|
|
421
|
+
(tetrahedron[:, 2, 2] - tetrahedron[:, 0, 2]),
|
|
422
|
+
],
|
|
423
|
+
[
|
|
424
|
+
(tetrahedron[:, 3, 0] - tetrahedron[:, 0, 0]),
|
|
425
|
+
(tetrahedron[:, 3, 1] - tetrahedron[:, 0, 1]),
|
|
426
|
+
(tetrahedron[:, 3, 2] - tetrahedron[:, 0, 2]),
|
|
427
|
+
],
|
|
428
|
+
]
|
|
429
|
+
)
|
|
430
|
+
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]])
|
|
431
|
+
m = np.swapaxes(m, 0, 2)
|
|
432
|
+
element_gradients = np.linalg.inv(m)
|
|
433
|
+
|
|
434
|
+
element_gradients = element_gradients.swapaxes(1, 2)
|
|
435
|
+
element_gradients = element_gradients @ I
|
|
436
|
+
v = np.sum(element_gradients * corners[:, None, :], axis=2)
|
|
437
|
+
return v
|
|
438
|
+
|
|
439
|
+
def add_abutting_fault(self, abutting_fault_feature, positive=None):
|
|
440
|
+
|
|
441
|
+
# check whether the fault is on the hanging wall or footwall of abutting fault
|
|
442
|
+
abutting_region = None
|
|
443
|
+
if positive is None:
|
|
444
|
+
pts = (
|
|
445
|
+
self.__getitem__(0).builder.data[["X", "Y", "Z"]].to_numpy()
|
|
446
|
+
) # get_value_constraints()
|
|
447
|
+
abut_value = np.nanmedian(abutting_fault_feature.evaluate_value(pts))
|
|
448
|
+
positive = abut_value > 0
|
|
449
|
+
# we want to crop the fault by the abutting
|
|
450
|
+
# fault so create a positive/neg region and
|
|
451
|
+
# include the fault centre and normal vector to help
|
|
452
|
+
# outside of the fault interpolation support
|
|
453
|
+
if positive:
|
|
454
|
+
abutting_region = PositiveRegion(
|
|
455
|
+
abutting_fault_feature,
|
|
456
|
+
vector=abutting_fault_feature.fault_normal_vector,
|
|
457
|
+
point=abutting_fault_feature.fault_centre,
|
|
458
|
+
)
|
|
459
|
+
if not positive:
|
|
460
|
+
abutting_region = NegativeRegion(
|
|
461
|
+
abutting_fault_feature,
|
|
462
|
+
vector=abutting_fault_feature.fault_normal_vector,
|
|
463
|
+
point=abutting_fault_feature.fault_centre,
|
|
464
|
+
)
|
|
465
|
+
self.abut[abutting_fault_feature.name] = abutting_region
|
|
466
|
+
self.__getitem__(0).add_region(abutting_region)
|
|
467
|
+
|
|
468
|
+
def save(self, filename, scalar_field=True, slip_vector=True, surface=True):
|
|
469
|
+
"""
|
|
470
|
+
Save the fault to a file
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
filename - str
|
|
475
|
+
filename to save to
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
|
|
480
|
+
"""
|
|
481
|
+
filename = str(filename)
|
|
482
|
+
ext = filename.split(".")[-1]
|
|
483
|
+
info = ''
|
|
484
|
+
if scalar_field:
|
|
485
|
+
if ext == '.geoh5':
|
|
486
|
+
info = ''
|
|
487
|
+
else:
|
|
488
|
+
info = '_scalar_field'
|
|
489
|
+
a = self.features[0].scalar_field()
|
|
490
|
+
a.merge(self.features[1].scalar_field())
|
|
491
|
+
a.merge(self.features[2].scalar_field())
|
|
492
|
+
a.merge(self.displacementfeature.scalar_field())
|
|
493
|
+
a.save(f'{filename}{info}.{ext}')
|
|
494
|
+
if slip_vector:
|
|
495
|
+
if ext == '.geoh5':
|
|
496
|
+
info = ''
|
|
497
|
+
else:
|
|
498
|
+
info = '_slip_vector'
|
|
499
|
+
self.vector_field().save(f'{filename}{info}.{ext}')
|
|
500
|
+
if surface:
|
|
501
|
+
if ext == '.geoh5':
|
|
502
|
+
info = ''
|
|
503
|
+
else:
|
|
504
|
+
info = '_surface'
|
|
505
|
+
self.surfaces([0])[0].save(f'{filename}{info}.{ext}')
|