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,971 @@
|
|
|
1
|
+
from ...modelling.features.builders import StructuralFrameBuilder
|
|
2
|
+
from ...modelling.features.fault import FaultSegment
|
|
3
|
+
from ...utils import getLogger, rng
|
|
4
|
+
from ...datatypes import BoundingBox
|
|
5
|
+
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from sklearn.cluster import KMeans
|
|
16
|
+
except ImportError as e:
|
|
17
|
+
logger.error('Scikitlearn cannot be imported')
|
|
18
|
+
raise e
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class IntrusionFrameBuilder(StructuralFrameBuilder):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
interpolatortype: Union[str, list],
|
|
25
|
+
bounding_box: BoundingBox,
|
|
26
|
+
nelements: Union[int, list] = 1000,
|
|
27
|
+
model=None,
|
|
28
|
+
**kwargs,
|
|
29
|
+
):
|
|
30
|
+
"""IntrusionBuilder set up the intrusion frame to build an intrusion
|
|
31
|
+
The intrusion frame is curvilinear coordinate system of the intrusion, which is then used to compute the lateral and vertical exten of the intrusion.
|
|
32
|
+
The object is constrained with the host rock geometry and other field measurements such as propagation and inflation vectors.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
interpolator : GeologicalInterpolator, optional
|
|
37
|
+
the interpolator to use for building the fault frame, by default None
|
|
38
|
+
interpolators : [GeologicalInterpolator, GeologicalInterpolator, GeologicalInterpolator], optional
|
|
39
|
+
a list of interpolators to use for building the fault frame, by default None
|
|
40
|
+
model : GeologicalModel
|
|
41
|
+
reference to the model containing the fault
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
StructuralFrameBuilder.__init__(self, interpolatortype, bounding_box, nelements, **kwargs)
|
|
45
|
+
|
|
46
|
+
self.origin = np.array([np.nan, np.nan, np.nan])
|
|
47
|
+
self.maximum = np.array([np.nan, np.nan, np.nan])
|
|
48
|
+
self.model = model
|
|
49
|
+
self.minimum_origin = self.model.bounding_box[0, :]
|
|
50
|
+
self.maximum_maximum = self.model.bounding_box[1, :]
|
|
51
|
+
self.faults = []
|
|
52
|
+
|
|
53
|
+
# -- intrusion frame parameters
|
|
54
|
+
self.intrusion_network_contact = (
|
|
55
|
+
None # string, contact which is used to constrain coordinate 0 (roof/top or floor/base)
|
|
56
|
+
)
|
|
57
|
+
self.intrusion_other_contact = (
|
|
58
|
+
None # string, the contact which is NOT used to constrain coordinate 0
|
|
59
|
+
)
|
|
60
|
+
self.intrusion_network_type = (
|
|
61
|
+
None # string, (FAN: REMOVE shortest path option, no longer working)
|
|
62
|
+
)
|
|
63
|
+
self.intrusion_network_data = None
|
|
64
|
+
self.other_contact_data = None
|
|
65
|
+
self.grid_to_evaluate_ifx = np.zeros([1, 1])
|
|
66
|
+
|
|
67
|
+
# -- host rock anisotropies exploited by the intrusion (lists of names and parameters)
|
|
68
|
+
self.anisotropies_series_list = []
|
|
69
|
+
self.anisotropies_series_parameters = {}
|
|
70
|
+
self.anisotropies_fault_list = []
|
|
71
|
+
self.anisotropies_fault_parameters = {}
|
|
72
|
+
|
|
73
|
+
self.delta_contacts = None
|
|
74
|
+
self.delta_faults = None
|
|
75
|
+
self.intrusion_steps = None
|
|
76
|
+
self.marginal_faults = None
|
|
77
|
+
self.frame_c0_points = None
|
|
78
|
+
self.frame_c0_gradients = None
|
|
79
|
+
|
|
80
|
+
self.IFf = None
|
|
81
|
+
self.IFc = None
|
|
82
|
+
|
|
83
|
+
def update_geometry(self, points):
|
|
84
|
+
self.origin = np.nanmin(np.array([np.min(points, axis=0), self.origin]), axis=0)
|
|
85
|
+
self.maximum = np.nanmax(np.array([np.max(points, axis=0), self.maximum]), axis=0)
|
|
86
|
+
self.origin[self.origin < self.minimum_origin] = self.minimum_origin[
|
|
87
|
+
self.origin < self.minimum_origin
|
|
88
|
+
]
|
|
89
|
+
self.maximum[self.maximum > self.maximum_maximum] = self.maximum_maximum[
|
|
90
|
+
self.maximum > self.maximum_maximum
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def set_model(self, model):
|
|
94
|
+
"""
|
|
95
|
+
Link a geological model to the feature
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
model - GeologicalModel
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
self.model = model
|
|
106
|
+
|
|
107
|
+
def add_fault(self, fault: FaultSegment):
|
|
108
|
+
"""
|
|
109
|
+
Add a fault to the geological feature builder
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
fault : FaultSegment
|
|
114
|
+
A faultsegment to add to the geological feature
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
self._up_to_date = False
|
|
122
|
+
self.faults.append(fault)
|
|
123
|
+
for i in range(3):
|
|
124
|
+
self.builders[i].add_fault(fault)
|
|
125
|
+
|
|
126
|
+
def set_intrusion_frame_c0_data(self, intrusion_data: pd.DataFrame):
|
|
127
|
+
"""
|
|
128
|
+
Separate data between roof and floor contacts
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
"""
|
|
135
|
+
other_contact = None
|
|
136
|
+
if self.intrusion_network_contact == "roof":
|
|
137
|
+
other_contact = "floor"
|
|
138
|
+
elif self.intrusion_network_contact == "top":
|
|
139
|
+
other_contact = "base"
|
|
140
|
+
elif self.intrusion_network_contact == "floor":
|
|
141
|
+
other_contact = "roof"
|
|
142
|
+
elif self.intrusion_network_contact == "base":
|
|
143
|
+
other_contact = "top"
|
|
144
|
+
|
|
145
|
+
intrusion_network_data = intrusion_data[
|
|
146
|
+
intrusion_data["intrusion_contact_type"] == self.intrusion_network_contact
|
|
147
|
+
]
|
|
148
|
+
other_contact_data = intrusion_data[
|
|
149
|
+
intrusion_data["intrusion_contact_type"] == other_contact
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
self.intrusion_other_contact = other_contact
|
|
153
|
+
self.intrusion_network_data = intrusion_network_data
|
|
154
|
+
self.other_contact_data = other_contact_data
|
|
155
|
+
|
|
156
|
+
def create_grid_for_indicator_fxs(self, spacing=None):
|
|
157
|
+
"""
|
|
158
|
+
Create the grid points in which to evaluate the indicator functions
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
spacing = list/array with spacing value for X,Y,Z
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
if spacing is None:
|
|
169
|
+
spacing = self.model.nsteps
|
|
170
|
+
|
|
171
|
+
grid_points = self.model.regular_grid(spacing, shuffle=False)
|
|
172
|
+
|
|
173
|
+
self.grid_to_evaluate_ifx = grid_points
|
|
174
|
+
|
|
175
|
+
return grid_points, spacing
|
|
176
|
+
|
|
177
|
+
def add_contact_anisotropies(self, series_list: list = [], **kwargs):
|
|
178
|
+
"""
|
|
179
|
+
Currently only used in 'Shortest path algorithm' (deprecated).
|
|
180
|
+
Add to the intrusion network the anisotropies
|
|
181
|
+
exploited by the intrusion (series-type geological features)
|
|
182
|
+
Given a list of series-type features, this function evaluates contact points
|
|
183
|
+
on each series and compute mean value and standard deviation.
|
|
184
|
+
Different contacts of the same series are indentify using
|
|
185
|
+
clustering algortihm. Mean and std deviation values will
|
|
186
|
+
be used to identify each contact thoughout the model using
|
|
187
|
+
the indicator functions.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
series_list: list
|
|
192
|
+
list of series-type features
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
|
|
197
|
+
Note
|
|
198
|
+
-----
|
|
199
|
+
assigns to self.anisotropies_series_parameters a list like (for each strtaigraphic contact) =
|
|
200
|
+
[series_name, mean of scalar field vals, standar dev. of scalar field val]
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
if self.intrusion_network_type == "shortest path":
|
|
204
|
+
n_clusters = self.number_of_contacts
|
|
205
|
+
|
|
206
|
+
self.anisotropies_series_list = series_list
|
|
207
|
+
series_parameters = {}
|
|
208
|
+
for i, series in enumerate(series_list):
|
|
209
|
+
data_temp = self.intrusion_network_data[
|
|
210
|
+
self.intrusion_network_data["intrusion_anisotropy"] == series.name
|
|
211
|
+
].copy()
|
|
212
|
+
data_array_temp = data_temp.loc[:, ["X", "Y", "Z"]].to_numpy()
|
|
213
|
+
series_i_vals = series.evaluate_value(data_array_temp)
|
|
214
|
+
series_array = np.zeros((len(data_array_temp), 4))
|
|
215
|
+
series_array[:, :3] = data_array_temp
|
|
216
|
+
series_array[:, 3] = series_i_vals
|
|
217
|
+
|
|
218
|
+
n_contacts = n_clusters[i]
|
|
219
|
+
|
|
220
|
+
# -- use scalar field values to find different contacts
|
|
221
|
+
series_i_vals_mod = series_i_vals.reshape(len(series_i_vals), 1)
|
|
222
|
+
# TODO create global loopstructural random state variable
|
|
223
|
+
contact_clustering = KMeans(n_clusters=n_contacts, random_state=0).fit(
|
|
224
|
+
series_i_vals_mod
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
for j in range(n_contacts):
|
|
228
|
+
z = np.ma.masked_not_equal(contact_clustering.labels_, j)
|
|
229
|
+
y = np.ma.masked_array(series_i_vals, z.mask)
|
|
230
|
+
series_ij_vals = np.ma.compressed(y)
|
|
231
|
+
series_ij_mean = np.mean(series_ij_vals)
|
|
232
|
+
series_ij_std = np.std(series_ij_vals)
|
|
233
|
+
series_ij_name = f"{series.name}_{str(series_ij_mean)}"
|
|
234
|
+
|
|
235
|
+
series_parameters[series_ij_name] = [
|
|
236
|
+
series,
|
|
237
|
+
series_ij_mean,
|
|
238
|
+
series_ij_std,
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
self.anisotropies_series_parameters = series_parameters
|
|
242
|
+
|
|
243
|
+
def add_faults_anisotropies(self, fault_list: list = []):
|
|
244
|
+
"""
|
|
245
|
+
Add to the intrusion network the anisotropies likely
|
|
246
|
+
exploited by the intrusion (fault-type geological features)
|
|
247
|
+
Given a list of fault features, evaluates the contact
|
|
248
|
+
points on each fault and compute mean value and standard
|
|
249
|
+
deviation. These values will be used to identify each
|
|
250
|
+
fault with the indicator function.
|
|
251
|
+
If no points in the fault, assign standard mean and std dev.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
fault_list: list
|
|
256
|
+
list of fault-type features
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
if fault_list is not None:
|
|
263
|
+
self.anisotropies_fault_list.append(fault_list)
|
|
264
|
+
|
|
265
|
+
for i in range(len(fault_list)):
|
|
266
|
+
if fault_list[i] in self.faults: # remove pre-intrusion faults from faults list
|
|
267
|
+
self.faults.remove(fault_list[i])
|
|
268
|
+
for j in range(3):
|
|
269
|
+
self.builders[j].faults.remove(fault_list[i])
|
|
270
|
+
|
|
271
|
+
fault = fault_list[i]
|
|
272
|
+
data_temp = self.intrusion_network_data[
|
|
273
|
+
self.intrusion_network_data["intrusion_anisotropy"] == fault.name
|
|
274
|
+
].copy()
|
|
275
|
+
data_array_temp = data_temp.loc[:, ["X", "Y", "Z"]].to_numpy()
|
|
276
|
+
|
|
277
|
+
if data_temp.empty:
|
|
278
|
+
fault_i_mean = 0
|
|
279
|
+
fault_i_std = 0.1
|
|
280
|
+
|
|
281
|
+
else:
|
|
282
|
+
# -- evaluate the value of the fault, evaluate_value called on a fault
|
|
283
|
+
# will return the scalar field value of the main structural feature
|
|
284
|
+
fault_i_vals = fault[0].evaluate_value(data_array_temp)
|
|
285
|
+
fault_i_mean = np.mean(fault_i_vals)
|
|
286
|
+
fault_i_std = np.std(fault_i_vals)
|
|
287
|
+
|
|
288
|
+
self.anisotropies_fault_parameters[fault_list[i].name] = [
|
|
289
|
+
fault_list[i],
|
|
290
|
+
fault_i_mean,
|
|
291
|
+
fault_i_std,
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
def set_intrusion_steps_parameters(self):
|
|
295
|
+
"""
|
|
296
|
+
Use intrusion contact data (reference contact for intrusion frame) to compute the parameters related to each step.
|
|
297
|
+
The parameters are the mean and std dev values of the stratigraphic units to both sides of the step.
|
|
298
|
+
These parameters are then used to add more constraints to the intrusion framce coordinate 0
|
|
299
|
+
|
|
300
|
+
For each step, at least one strigraphic unit and one fault must be provided.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
if self.model.stratigraphic_column is None:
|
|
311
|
+
logger.error(
|
|
312
|
+
"Set stratigraphic column to model using model.set_stratigraphic_column(name of stratigraphic column)"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# set data
|
|
316
|
+
intrusion_network_data = self.intrusion_network_data.copy()
|
|
317
|
+
intrusion_network_data_xyz = (
|
|
318
|
+
intrusion_network_data.loc[:, ["X", "Y", "Z"]].copy().to_numpy()
|
|
319
|
+
)
|
|
320
|
+
std_backup = 25
|
|
321
|
+
|
|
322
|
+
for step_i, step in self.intrusion_steps.items():
|
|
323
|
+
step_structure = step.get("structure")
|
|
324
|
+
unit_from_name = step.get("unit_from")
|
|
325
|
+
series_from_name = step.get("series_from")
|
|
326
|
+
unit_from_id = self.model.stratigraphic_column[series_from_name.name][
|
|
327
|
+
unit_from_name
|
|
328
|
+
].get("id")
|
|
329
|
+
|
|
330
|
+
unit_to_name = step.get("unit_to")
|
|
331
|
+
series_to_name = step.get("series_to")
|
|
332
|
+
unit_to_id = self.model.stratigraphic_column[series_to_name.name][unit_to_name].get(
|
|
333
|
+
"id"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
intrusion_network_data.loc[:, "model_values"] = self.model.evaluate_model(
|
|
337
|
+
self.model.rescale(intrusion_network_data_xyz, inplace=False)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# -- check if step is within the same unit. If so, find clusters of data:
|
|
341
|
+
if unit_from_name == unit_to_name:
|
|
342
|
+
data_points_xyz = (
|
|
343
|
+
intrusion_network_data[intrusion_network_data["model_values"] == unit_from_id]
|
|
344
|
+
.loc[:, ["X", "Y", "Z"]]
|
|
345
|
+
.copy()
|
|
346
|
+
.to_numpy()
|
|
347
|
+
)
|
|
348
|
+
series_values = series_from_name.evaluate_value(data_points_xyz)
|
|
349
|
+
series_values_mod = series_values.reshape(len(series_values), 1)
|
|
350
|
+
contact_clustering = KMeans(n_clusters=2, random_state=0).fit(series_values_mod)
|
|
351
|
+
|
|
352
|
+
# contact 0
|
|
353
|
+
z = np.ma.masked_not_equal(contact_clustering.labels_, 0)
|
|
354
|
+
y = np.ma.masked_array(series_values, z.mask)
|
|
355
|
+
contact_0_vals = np.ma.compressed(y)
|
|
356
|
+
contact_0_mean = np.mean(contact_0_vals)
|
|
357
|
+
contact_0_std = np.std(contact_0_vals)
|
|
358
|
+
|
|
359
|
+
if contact_0_std == 0 or np.isnan(contact_0_std):
|
|
360
|
+
contact_0_std = std_backup
|
|
361
|
+
|
|
362
|
+
# contact 1
|
|
363
|
+
z = np.ma.masked_not_equal(contact_clustering.labels_, 1)
|
|
364
|
+
y = np.ma.masked_array(series_values, z.mask)
|
|
365
|
+
contact_1_vals = np.ma.compressed(y)
|
|
366
|
+
contact_1_mean = np.mean(contact_1_vals)
|
|
367
|
+
contact_1_std = np.std(contact_1_vals)
|
|
368
|
+
|
|
369
|
+
if contact_1_std == 0 or np.isnan(contact_1_std):
|
|
370
|
+
contact_1_std = std_backup
|
|
371
|
+
|
|
372
|
+
if contact_0_mean <= contact_1_mean:
|
|
373
|
+
step["unit_from_mean"] = contact_0_mean
|
|
374
|
+
step["unit_from_std"] = contact_0_std
|
|
375
|
+
step["unit_to_mean"] = contact_1_mean
|
|
376
|
+
step["unit_to_std"] = contact_1_std
|
|
377
|
+
|
|
378
|
+
else:
|
|
379
|
+
step["unit_from_mean"] = contact_1_mean
|
|
380
|
+
step["unit_from_std"] = contact_1_std
|
|
381
|
+
step["unit_to_mean"] = contact_0_mean
|
|
382
|
+
step["unit_to_std"] = contact_0_std
|
|
383
|
+
|
|
384
|
+
else: # -- step between different stratigraphic units
|
|
385
|
+
data_points_from_xyz = (
|
|
386
|
+
intrusion_network_data[intrusion_network_data["model_values"] == unit_from_id]
|
|
387
|
+
.loc[:, ["X", "Y", "Z"]]
|
|
388
|
+
.copy()
|
|
389
|
+
.to_numpy()
|
|
390
|
+
)
|
|
391
|
+
step_structure_points_vals = step_structure[0].evaluate_value(data_points_from_xyz)
|
|
392
|
+
if len(data_points_from_xyz) == 0: # no data points in strat unit
|
|
393
|
+
unit_from_min = self.model.stratigraphic_column[series_from_name.name][
|
|
394
|
+
unit_from_name
|
|
395
|
+
].get("min")
|
|
396
|
+
unit_from_max = self.model.stratigraphic_column[series_from_name.name][
|
|
397
|
+
unit_from_name
|
|
398
|
+
].get("max")
|
|
399
|
+
self.intrusion_steps[step_i]["unit_from_mean"] = (
|
|
400
|
+
unit_from_min + (unit_from_max - unit_from_min) / 2
|
|
401
|
+
)
|
|
402
|
+
self.intrusion_steps[step_i]["unit_from_std"] = std_backup
|
|
403
|
+
else:
|
|
404
|
+
series_values = series_from_name.evaluate_value(data_points_from_xyz)
|
|
405
|
+
mask = step_structure_points_vals < 0
|
|
406
|
+
if len(mask) > 0:
|
|
407
|
+
series_values_mod = np.ma.compressed(
|
|
408
|
+
np.ma.masked_array(series_values, step_structure_points_vals < 0)
|
|
409
|
+
)
|
|
410
|
+
else:
|
|
411
|
+
series_values_mod = series_values
|
|
412
|
+
step["unit_from_mean"] = np.nanmean(series_values_mod)
|
|
413
|
+
step["unit_from_std"] = np.nanstd(series_values_mod)
|
|
414
|
+
|
|
415
|
+
if step["unit_from_std"] == 0:
|
|
416
|
+
step["unit_from_std"] = std_backup
|
|
417
|
+
|
|
418
|
+
data_points_to_xyz = (
|
|
419
|
+
intrusion_network_data[intrusion_network_data["model_values"] == unit_to_id]
|
|
420
|
+
.loc[:, ["X", "Y", "Z"]]
|
|
421
|
+
.copy()
|
|
422
|
+
.to_numpy()
|
|
423
|
+
)
|
|
424
|
+
step_structure_points_vals = step_structure[0].evaluate_value(data_points_to_xyz)
|
|
425
|
+
if len(data_points_to_xyz) == 0:
|
|
426
|
+
unit_to_min = self.model.stratigraphic_column[series_to_name.name][
|
|
427
|
+
unit_to_name
|
|
428
|
+
].get("min")
|
|
429
|
+
unit_to_max = self.model.stratigraphic_column[series_to_name.name][
|
|
430
|
+
unit_to_name
|
|
431
|
+
].get("max")
|
|
432
|
+
step["unit_to_mean"] = unit_to_min + (unit_to_max - unit_to_min) / 2
|
|
433
|
+
step["unit_to_std"] = std_backup
|
|
434
|
+
else:
|
|
435
|
+
series_values = series_to_name.evaluate_value(data_points_to_xyz)
|
|
436
|
+
mask = step_structure_points_vals > 0
|
|
437
|
+
if len(mask) > 0:
|
|
438
|
+
series_values_mod = np.ma.compressed(
|
|
439
|
+
np.ma.masked_array(series_values, step_structure_points_vals > 0)
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
series_values_mod = series_values
|
|
443
|
+
step["unit_to_mean"] = np.nanmean(series_values_mod)
|
|
444
|
+
step["unit_to_std"] = np.nanstd(series_values_mod)
|
|
445
|
+
check_mean = step["unit_to_mean"]
|
|
446
|
+
check_std = step["unit_to_std"]
|
|
447
|
+
|
|
448
|
+
if np.isnan(check_mean):
|
|
449
|
+
step["unit_to_mean"] = 40
|
|
450
|
+
|
|
451
|
+
if np.isnan(check_std):
|
|
452
|
+
step["unit_to_std"] = std_backup
|
|
453
|
+
|
|
454
|
+
if step["unit_to_std"] == 0:
|
|
455
|
+
step["unit_to_std"] = std_backup
|
|
456
|
+
|
|
457
|
+
def set_marginal_faults_parameters(self):
|
|
458
|
+
"""
|
|
459
|
+
Use intrusion contact data (reference contact for intrusion frame) to compute the parameters related to each fault.
|
|
460
|
+
The parameters are the mean and std dev values of the stratigraphic units to the hanging wall or foot wall of the fault.
|
|
461
|
+
These parameters are then used to add more constraints to the intrusion framce coordinate 0
|
|
462
|
+
|
|
463
|
+
For each fault, at least one strigraphic unit and one fault must be provided.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
# set data
|
|
474
|
+
intrusion_frame_c0_data = self.intrusion_network_data.copy()
|
|
475
|
+
intrusion_frame_c0_data_xyz = (
|
|
476
|
+
intrusion_frame_c0_data.loc[:, ["X", "Y", "Z"]].copy().to_numpy()
|
|
477
|
+
)
|
|
478
|
+
std_backup = 25
|
|
479
|
+
|
|
480
|
+
for fault_i in self.marginal_faults.keys():
|
|
481
|
+
marginal_fault = self.marginal_faults[fault_i].get("structure")
|
|
482
|
+
block = self.marginal_faults[fault_i].get("block") # hanging wall or foot wall
|
|
483
|
+
self.marginal_faults[fault_i].get("emplacement_mechanism")
|
|
484
|
+
series_name = self.marginal_faults[fault_i].get("series")
|
|
485
|
+
|
|
486
|
+
series_values_temp = series_name.evaluate_value(intrusion_frame_c0_data_xyz)
|
|
487
|
+
faults_values_temp = marginal_fault[0].evaluate_value(intrusion_frame_c0_data_xyz)
|
|
488
|
+
|
|
489
|
+
if block == "hanging wall":
|
|
490
|
+
series_values = series_values_temp[faults_values_temp > 0]
|
|
491
|
+
|
|
492
|
+
elif block == "foot wall":
|
|
493
|
+
series_values = series_values_temp[faults_values_temp < 0]
|
|
494
|
+
|
|
495
|
+
self.marginal_faults[fault_i]["series_mean"] = np.mean(series_values)
|
|
496
|
+
|
|
497
|
+
series_std = np.std(series_values)
|
|
498
|
+
|
|
499
|
+
if series_std == 0:
|
|
500
|
+
series_std = std_backup
|
|
501
|
+
|
|
502
|
+
self.marginal_faults[fault_i]["series_std"] = series_std
|
|
503
|
+
self.marginal_faults[fault_i]["series_vals"] = series_values
|
|
504
|
+
|
|
505
|
+
def set_intrusion_frame_parameters(
|
|
506
|
+
self, intrusion_data: pd.DataFrame, intrusion_frame_parameters: dict, **kwargs
|
|
507
|
+
):
|
|
508
|
+
"""
|
|
509
|
+
Set variables to create intrusion network.
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
intrusion_data: pd.DataFrame
|
|
514
|
+
intrusion contact data
|
|
515
|
+
intrusion_network_input: dict
|
|
516
|
+
contact : string, contact of the intrusion to be used to create the network (roof or floor)
|
|
517
|
+
type : string, type of algorithm to create the intrusion network (interpolated or shortest path).
|
|
518
|
+
Shortest path is recommended when intrusion contact is not well constrained
|
|
519
|
+
contacts_anisotropies : list of series-type features involved in intrusion emplacement
|
|
520
|
+
structures_anisotropies : list of fault-type features involved in intrusion emplacement
|
|
521
|
+
sequence_anisotropies : list of anisotropies to look for the shortest path. It could be only starting and end point.
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
|
|
526
|
+
"""
|
|
527
|
+
|
|
528
|
+
self.intrusion_network_contact = intrusion_frame_parameters.get("contact", "floor")
|
|
529
|
+
|
|
530
|
+
self.set_intrusion_frame_c0_data(intrusion_data) # separates roof and floor data
|
|
531
|
+
|
|
532
|
+
# if self.intrusion_network_type == "interpolated":
|
|
533
|
+
|
|
534
|
+
self.gradients_constraints_weight = intrusion_frame_parameters.get("g_w", None)
|
|
535
|
+
|
|
536
|
+
intrusion_steps = intrusion_frame_parameters.get("intrusion_steps", None)
|
|
537
|
+
|
|
538
|
+
marginal_faults = intrusion_frame_parameters.get("marginal_faults", None)
|
|
539
|
+
|
|
540
|
+
if intrusion_steps is not None:
|
|
541
|
+
self.intrusion_steps = intrusion_steps
|
|
542
|
+
|
|
543
|
+
self.delta_contacts = intrusion_frame_parameters.get(
|
|
544
|
+
"delta_c", [1] * len(intrusion_steps)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
self.delta_faults = intrusion_frame_parameters.get(
|
|
548
|
+
"delta_f", [1] * len(intrusion_steps)
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
self.set_intrusion_steps_parameters() # function to compute steps parameters
|
|
552
|
+
|
|
553
|
+
fault_anisotropies = []
|
|
554
|
+
for step in self.intrusion_steps.keys():
|
|
555
|
+
fault_anisotropies.append(self.intrusion_steps[step].get("structure"))
|
|
556
|
+
|
|
557
|
+
self.add_faults_anisotropies(fault_anisotropies)
|
|
558
|
+
|
|
559
|
+
if marginal_faults is not None:
|
|
560
|
+
self.marginal_faults = marginal_faults
|
|
561
|
+
|
|
562
|
+
self.delta_contacts = intrusion_frame_parameters.get(
|
|
563
|
+
"delta_c", [1] * len(marginal_faults)
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
self.delta_faults = intrusion_frame_parameters.get(
|
|
567
|
+
"delta_f", [1] * len(marginal_faults)
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
self.set_marginal_faults_parameters()
|
|
571
|
+
|
|
572
|
+
fault_anisotropies = []
|
|
573
|
+
for fault in self.marginal_faults.keys():
|
|
574
|
+
fault_anisotropies.append(self.marginal_faults[fault].get("structure"))
|
|
575
|
+
|
|
576
|
+
self.add_faults_anisotropies(fault_anisotropies)
|
|
577
|
+
|
|
578
|
+
# add contact anisotropies list
|
|
579
|
+
contact_anisotropies = intrusion_frame_parameters.get("contact_anisotropies", None)
|
|
580
|
+
|
|
581
|
+
self.anisotropies_series_list = contact_anisotropies
|
|
582
|
+
|
|
583
|
+
def indicator_function_contacts(self, delta=None):
|
|
584
|
+
"""
|
|
585
|
+
Function only used for Shortest Path method (Deprecated)
|
|
586
|
+
Function to compute indicator function for list of contacts anisotropies
|
|
587
|
+
For each point of the grid, this function assignes a 1 if contact i is present, 0 otherwise.
|
|
588
|
+
|
|
589
|
+
A contact is defined as an isovalue of an scalar field defining the
|
|
590
|
+
geological feature of which the contact is part of.
|
|
591
|
+
Each point is evaluated in the feature scalar field and is
|
|
592
|
+
identified as part of the contact if its value is around the contact isovalue.
|
|
593
|
+
|
|
594
|
+
Parameters
|
|
595
|
+
----------
|
|
596
|
+
delta : list of numbers, same lenght as number of anisotropies (series).
|
|
597
|
+
delta multiplies the standard deviation to increase
|
|
598
|
+
probability of finding the contact on a grid point.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
----------
|
|
602
|
+
Ic: array of [len(grid_points),n_contacts], containing indicator function for list of contacts
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
n_series = len(self.anisotropies_series_parameters) # number of series
|
|
606
|
+
grid_points = self.grid_to_evaluate_ifx
|
|
607
|
+
|
|
608
|
+
Ic = np.zeros([len(self.grid_to_evaluate_ifx), n_series])
|
|
609
|
+
|
|
610
|
+
delta_list = delta
|
|
611
|
+
|
|
612
|
+
for i, contact_id in enumerate(sorted(self.anisotropies_series_parameters)):
|
|
613
|
+
series_id = self.anisotropies_series_parameters[contact_id][0]
|
|
614
|
+
seriesi_mean = self.anisotropies_series_parameters[contact_id][1]
|
|
615
|
+
seriesi_std = self.anisotropies_series_parameters[contact_id][2]
|
|
616
|
+
|
|
617
|
+
# series_id.faults_enabled = True
|
|
618
|
+
seriesi_values = series_id.evaluate_value(grid_points)
|
|
619
|
+
|
|
620
|
+
# apend associated scalar field values to each anisotropy
|
|
621
|
+
self.anisotropies_series_parameters[contact_id].append(seriesi_values)
|
|
622
|
+
|
|
623
|
+
# evaluate indicator function in contact (i)
|
|
624
|
+
Ic[
|
|
625
|
+
np.logical_and(
|
|
626
|
+
(seriesi_mean - seriesi_std * delta_list[i]) <= seriesi_values,
|
|
627
|
+
seriesi_values <= (seriesi_mean + seriesi_std * delta_list[i]),
|
|
628
|
+
),
|
|
629
|
+
i,
|
|
630
|
+
] = 1
|
|
631
|
+
|
|
632
|
+
self.IFc = Ic
|
|
633
|
+
return Ic
|
|
634
|
+
|
|
635
|
+
def indicator_function_faults(self, delta=None):
|
|
636
|
+
"""
|
|
637
|
+
Function to compute indicator function for list of faults anisotropies
|
|
638
|
+
For each point of the grid, this function assignes a 1 if fault i is present, 0 otherwise.
|
|
639
|
+
|
|
640
|
+
A fault surface is defined as an isovalue 0 of the scalar field representing
|
|
641
|
+
the fault surface (coordinate 0 of its structural frame)
|
|
642
|
+
Each point of the grid is evaluated in this scalar field
|
|
643
|
+
and is identified as part of the fault if its value is around 0.
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
delta : integer, multiply the standard deviation to increase probability of finding the fault on a point.
|
|
648
|
+
|
|
649
|
+
Returns
|
|
650
|
+
----------
|
|
651
|
+
If: array of [len(grid_points),n_faults], containing indicator function for list of faults
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
n_faults = len(self.anisotropies_fault_parameters) # number of faults
|
|
655
|
+
grid_points = self.grid_to_evaluate_ifx
|
|
656
|
+
|
|
657
|
+
If = np.zeros([len(self.grid_to_evaluate_ifx), n_faults])
|
|
658
|
+
|
|
659
|
+
for i, fault_id in enumerate(self.anisotropies_fault_parameters.keys()):
|
|
660
|
+
fault_i = self.anisotropies_fault_parameters[fault_id][0]
|
|
661
|
+
faulti_mean = self.anisotropies_fault_parameters[fault_id][1]
|
|
662
|
+
faulti_std = self.anisotropies_fault_parameters[fault_id][2]
|
|
663
|
+
faulti_values = fault_i[0].evaluate_value(grid_points)
|
|
664
|
+
|
|
665
|
+
# apend associated scalar field values to each anisotropy
|
|
666
|
+
self.anisotropies_fault_parameters[fault_id].append(faulti_values)
|
|
667
|
+
|
|
668
|
+
If[
|
|
669
|
+
np.logical_and(
|
|
670
|
+
(faulti_mean - faulti_std * delta[i]) <= faulti_values,
|
|
671
|
+
faulti_values <= (faulti_mean + faulti_std * delta[i]),
|
|
672
|
+
),
|
|
673
|
+
i,
|
|
674
|
+
] = 1
|
|
675
|
+
|
|
676
|
+
self.IFf = If
|
|
677
|
+
return If
|
|
678
|
+
|
|
679
|
+
def create_constraints_for_c0(self, **kwargs):
|
|
680
|
+
"""
|
|
681
|
+
Creates a numpy array containing (x,y,z) coordinates of synthetic points located
|
|
682
|
+
in the intrusion reference contact.
|
|
683
|
+
The intrusion reference contact is the one used to build c0 of the intrusion frame.
|
|
684
|
+
--- Currently only works if steps OR marginal faults are present ---
|
|
685
|
+
|
|
686
|
+
Parameters
|
|
687
|
+
----------
|
|
688
|
+
|
|
689
|
+
Returns
|
|
690
|
+
-------
|
|
691
|
+
intrusion_contact_points = numpy array
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
# --- data points of intrusion reference contact (roof or floor)
|
|
695
|
+
inet_points_xyz = self.intrusion_network_data.loc[:, ["X", "Y", "Z"]].to_numpy()
|
|
696
|
+
|
|
697
|
+
intrusion_reference_contact_points = inet_points_xyz
|
|
698
|
+
|
|
699
|
+
grid_points, spacing = self.create_grid_for_indicator_fxs()
|
|
700
|
+
|
|
701
|
+
# --- more constraints if steps or marginal fault is present:
|
|
702
|
+
if self.intrusion_steps is not None:
|
|
703
|
+
If = self.indicator_function_faults(delta=self.delta_faults)
|
|
704
|
+
|
|
705
|
+
if len(np.where(If == 1)[0]) == 0:
|
|
706
|
+
logger.error("No faults identified, you may increase the value of delta_f")
|
|
707
|
+
|
|
708
|
+
If_sum = np.sum(If, axis=1)
|
|
709
|
+
|
|
710
|
+
# -- evaluate grid points in series
|
|
711
|
+
|
|
712
|
+
for i, step_i in enumerate(self.intrusion_steps.keys()):
|
|
713
|
+
delta_contact = self.delta_contacts[i]
|
|
714
|
+
if isinstance(delta_contact, list):
|
|
715
|
+
delta_contact0 = delta_contact[0]
|
|
716
|
+
delta_contact1 = delta_contact[1]
|
|
717
|
+
else:
|
|
718
|
+
delta_contact0 = delta_contact
|
|
719
|
+
delta_contact1 = delta_contact
|
|
720
|
+
|
|
721
|
+
step_fault = self.intrusion_steps[step_i].get("structure")
|
|
722
|
+
series_from_name = self.intrusion_steps[step_i].get("series_from")
|
|
723
|
+
series_to_name = self.intrusion_steps[step_i].get("series_to")
|
|
724
|
+
|
|
725
|
+
fault_gridpoints_vals = step_fault[0].evaluate_value(grid_points)
|
|
726
|
+
|
|
727
|
+
series_from_gridpoints_vals = series_from_name.evaluate_value(grid_points)
|
|
728
|
+
|
|
729
|
+
if series_from_name == series_to_name:
|
|
730
|
+
series_to_gridpoints_vals = series_from_gridpoints_vals
|
|
731
|
+
|
|
732
|
+
else:
|
|
733
|
+
series_to_gridpoints_vals = series_to_name.evaluate_value(grid_points)
|
|
734
|
+
|
|
735
|
+
contacts0_val_min = self.intrusion_steps[step_i].get("unit_from_mean") - (
|
|
736
|
+
self.intrusion_steps[step_i].get("unit_from_std") * delta_contact0
|
|
737
|
+
)
|
|
738
|
+
contacts0_val_max = self.intrusion_steps[step_i].get("unit_from_mean") + (
|
|
739
|
+
self.intrusion_steps[step_i].get("unit_from_std") * delta_contact0
|
|
740
|
+
)
|
|
741
|
+
contacts1_val_min = self.intrusion_steps[step_i].get("unit_to_mean") - (
|
|
742
|
+
self.intrusion_steps[step_i].get("unit_to_std") * delta_contact1
|
|
743
|
+
)
|
|
744
|
+
contacts1_val_max = self.intrusion_steps[step_i].get("unit_to_mean") + (
|
|
745
|
+
self.intrusion_steps[step_i].get("unit_to_std") * delta_contact1
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
self.intrusion_steps[step_i]["constraints_hw"] = grid_points[
|
|
749
|
+
np.logical_and(
|
|
750
|
+
If_sum == 0,
|
|
751
|
+
(fault_gridpoints_vals >= 0)
|
|
752
|
+
& (series_from_gridpoints_vals >= contacts0_val_min)
|
|
753
|
+
& (series_from_gridpoints_vals <= contacts0_val_max),
|
|
754
|
+
)
|
|
755
|
+
]
|
|
756
|
+
|
|
757
|
+
self.intrusion_steps[step_i]["constraints_fw"] = grid_points[
|
|
758
|
+
np.logical_and(
|
|
759
|
+
If_sum == 0,
|
|
760
|
+
(fault_gridpoints_vals < 0)
|
|
761
|
+
& (series_from_gridpoints_vals >= contacts0_val_min)
|
|
762
|
+
& (series_from_gridpoints_vals <= contacts0_val_max),
|
|
763
|
+
)
|
|
764
|
+
]
|
|
765
|
+
|
|
766
|
+
step_i_constraints_temp = grid_points[
|
|
767
|
+
np.logical_and(
|
|
768
|
+
If_sum == 0,
|
|
769
|
+
np.logical_or(
|
|
770
|
+
(fault_gridpoints_vals >= 0)
|
|
771
|
+
& (series_from_gridpoints_vals >= contacts0_val_min)
|
|
772
|
+
& (series_from_gridpoints_vals <= contacts0_val_max),
|
|
773
|
+
(fault_gridpoints_vals < 0)
|
|
774
|
+
& (series_to_gridpoints_vals >= contacts1_val_min)
|
|
775
|
+
& (series_to_gridpoints_vals <= contacts1_val_max),
|
|
776
|
+
),
|
|
777
|
+
)
|
|
778
|
+
]
|
|
779
|
+
|
|
780
|
+
region = self.intrusion_steps[step_i].get("region", None)
|
|
781
|
+
if region is None:
|
|
782
|
+
step_i_constraints = step_i_constraints_temp
|
|
783
|
+
else:
|
|
784
|
+
mask = region(step_i_constraints_temp)
|
|
785
|
+
step_i_constraints = step_i_constraints_temp[mask]
|
|
786
|
+
|
|
787
|
+
intrusion_reference_contact_points = np.vstack(
|
|
788
|
+
[intrusion_reference_contact_points, step_i_constraints]
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
splits_from_sill_name = self.intrusion_steps[step_i].get("splits_from", None)
|
|
792
|
+
|
|
793
|
+
# check if sill comes from anothe sill
|
|
794
|
+
# (add all the constraint from original sill, this have to be changed and adapted so it adds constraints for specific faults)
|
|
795
|
+
|
|
796
|
+
if splits_from_sill_name is not None:
|
|
797
|
+
splits_from_sill_steps = self.model.__getitem__(
|
|
798
|
+
splits_from_sill_name
|
|
799
|
+
).intrusion_frame.builder.intrusion_steps
|
|
800
|
+
for step_j in splits_from_sill_steps.keys():
|
|
801
|
+
step_j_hg_constraints = splits_from_sill_steps[step_j].get("constraints_hw")
|
|
802
|
+
intrusion_reference_contact_points = np.vstack(
|
|
803
|
+
[intrusion_reference_contact_points, step_j_hg_constraints]
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# --- more constraints if steps or marginal fault is present:
|
|
807
|
+
if self.marginal_faults is not None:
|
|
808
|
+
If = self.indicator_function_faults(delta=self.delta_faults)
|
|
809
|
+
|
|
810
|
+
if len(np.where(If == 1)[0]) == 0:
|
|
811
|
+
logger.error("No faults identified, yo may increase value of delta_f")
|
|
812
|
+
|
|
813
|
+
If_sum = np.sum(If, axis=1)
|
|
814
|
+
|
|
815
|
+
# evaluate grid points in series
|
|
816
|
+
for fault_i in self.marginal_faults.keys():
|
|
817
|
+
delta_contact = self.marginal_faults[fault_i].get("delta_c", 1)
|
|
818
|
+
marginal_fault = self.marginal_faults[fault_i].get("structure")
|
|
819
|
+
block = self.marginal_faults[fault_i].get("block") # hanging wall or foot wall
|
|
820
|
+
self.marginal_faults[fault_i].get("emplacement_mechanism")
|
|
821
|
+
series_name = self.marginal_faults[fault_i].get("series")
|
|
822
|
+
|
|
823
|
+
fault_gridpoints_vals = marginal_fault[0].evaluate_value(grid_points)
|
|
824
|
+
series_gridpoints_vals = series_name.evaluate_value(grid_points)
|
|
825
|
+
|
|
826
|
+
contact_min = self.marginal_faults[fault_i].get("series_mean") - (
|
|
827
|
+
self.marginal_faults[fault_i].get("series_std") * delta_contact
|
|
828
|
+
)
|
|
829
|
+
contact_max = self.marginal_faults[fault_i].get("series_mean") + (
|
|
830
|
+
self.marginal_faults[fault_i].get("series_std") * delta_contact
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
if block == "hanging wall":
|
|
834
|
+
marginalfault_i_constraints_temp = grid_points[
|
|
835
|
+
np.logical_and(
|
|
836
|
+
If_sum == 0,
|
|
837
|
+
np.logical_and(
|
|
838
|
+
fault_gridpoints_vals >= 0,
|
|
839
|
+
np.logical_and(
|
|
840
|
+
series_gridpoints_vals >= contact_min,
|
|
841
|
+
series_gridpoints_vals <= contact_max,
|
|
842
|
+
),
|
|
843
|
+
),
|
|
844
|
+
)
|
|
845
|
+
]
|
|
846
|
+
|
|
847
|
+
elif block == "foot wall":
|
|
848
|
+
marginalfault_i_constraints_temp = grid_points[
|
|
849
|
+
np.logical_and(
|
|
850
|
+
If_sum == 0,
|
|
851
|
+
np.logical_and(
|
|
852
|
+
fault_gridpoints_vals <= 0,
|
|
853
|
+
np.logical_and(
|
|
854
|
+
series_gridpoints_vals >= contact_min,
|
|
855
|
+
series_gridpoints_vals <= contact_max,
|
|
856
|
+
),
|
|
857
|
+
),
|
|
858
|
+
)
|
|
859
|
+
]
|
|
860
|
+
|
|
861
|
+
region = self.marginal_faults[fault_i].get("region", None)
|
|
862
|
+
|
|
863
|
+
if region is None:
|
|
864
|
+
marginalfault_i_constraints = marginalfault_i_constraints_temp
|
|
865
|
+
else:
|
|
866
|
+
mask = region(marginalfault_i_constraints_temp)
|
|
867
|
+
marginalfault_i_constraints = marginalfault_i_constraints_temp[mask]
|
|
868
|
+
|
|
869
|
+
intrusion_reference_contact_points = np.vstack(
|
|
870
|
+
[intrusion_reference_contact_points, marginalfault_i_constraints]
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
self.frame_c0_points = intrusion_reference_contact_points
|
|
874
|
+
|
|
875
|
+
# -- Gradient contraints for intrusion frame c0
|
|
876
|
+
# currently only used if no inflation data, and for one/main series anisotropy
|
|
877
|
+
# Evaluate points in gradient of stratigraphy, and exclude points around faults
|
|
878
|
+
|
|
879
|
+
series_id = self.anisotropies_series_list[0]
|
|
880
|
+
stratigraphy_gradient_grid_points = series_id.evaluate_gradient(grid_points)
|
|
881
|
+
|
|
882
|
+
# If intrusion frame c0 is built usinf roof/top contact, then change vector direction
|
|
883
|
+
if self.intrusion_network_contact == "roof" or self.intrusion_network_contact == "top":
|
|
884
|
+
grid_points_inflation = stratigraphy_gradient_grid_points * (-1)
|
|
885
|
+
else:
|
|
886
|
+
grid_points_inflation = stratigraphy_gradient_grid_points
|
|
887
|
+
|
|
888
|
+
grid_points_and_inflation_all = np.hstack([grid_points, grid_points_inflation])
|
|
889
|
+
|
|
890
|
+
if self.intrusion_steps is None:
|
|
891
|
+
If_sum = np.zeros(
|
|
892
|
+
len(grid_points_and_inflation_all)
|
|
893
|
+
).T # mask for region without faults afecting the intrusion
|
|
894
|
+
|
|
895
|
+
grid_points_and_inflation = grid_points_and_inflation_all[If_sum == 0]
|
|
896
|
+
self.frame_c0_gradients = grid_points_and_inflation
|
|
897
|
+
|
|
898
|
+
def get_indicator_function_points(self, ifx_type="contacts"):
|
|
899
|
+
"""returns the points indicated as p[art of contact or fault anisotropies.
|
|
900
|
+
|
|
901
|
+
Parameters
|
|
902
|
+
----------
|
|
903
|
+
ifx_type : string,
|
|
904
|
+
'contacts' or 'faults'
|
|
905
|
+
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
grid_points = self.grid_to_evaluate_ifx
|
|
909
|
+
|
|
910
|
+
if ifx_type == "contacts":
|
|
911
|
+
IF = self.IFc
|
|
912
|
+
else:
|
|
913
|
+
IF = self.IFf
|
|
914
|
+
|
|
915
|
+
If_points = []
|
|
916
|
+
|
|
917
|
+
for j in range(len(IF[0])):
|
|
918
|
+
IF_temp = IF[:, j]
|
|
919
|
+
If_points_temp = grid_points[IF_temp == 1]
|
|
920
|
+
If_points.append(If_points_temp)
|
|
921
|
+
|
|
922
|
+
return If_points
|
|
923
|
+
|
|
924
|
+
def set_intrusion_frame_data(self, intrusion_frame_data): # , intrusion_network_points):
|
|
925
|
+
"""Adds the intrusion network points as coordinate 0 data for the intrusion frame
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
intrusion_frame_data : DataFrame,
|
|
930
|
+
intrusion frame data from model data
|
|
931
|
+
|
|
932
|
+
"""
|
|
933
|
+
# Coordinate 0 - Represents growth, isovalue 0 represents the location of the roof or floor contact:
|
|
934
|
+
c0_points = self.frame_c0_points[:, :3]
|
|
935
|
+
coord_0_values = pd.DataFrame(c0_points, columns=["X", "Y", "Z"])
|
|
936
|
+
coord_0_values["val"] = 0
|
|
937
|
+
coord_0_values["coord"] = 0
|
|
938
|
+
coord_0_values["feature_name"] = self.name
|
|
939
|
+
coord_0_values["w"] = 1
|
|
940
|
+
|
|
941
|
+
intrusion_frame_data_temp = pd.concat([intrusion_frame_data, coord_0_values])
|
|
942
|
+
|
|
943
|
+
coord_0_data = intrusion_frame_data[intrusion_frame_data["coord"] == 0].copy()
|
|
944
|
+
if len(coord_0_data) == 0:
|
|
945
|
+
rng.shuffle(self.frame_c0_gradients)
|
|
946
|
+
if self.gradients_constraints_weight is None:
|
|
947
|
+
n_grad_constraints = 100
|
|
948
|
+
else:
|
|
949
|
+
n_grad_constraints = int(
|
|
950
|
+
len(self.frame_c0_gradients) * self.gradients_constraints_weight
|
|
951
|
+
)
|
|
952
|
+
sliced_grads = self.frame_c0_gradients[:n_grad_constraints, :]
|
|
953
|
+
|
|
954
|
+
coord_0_grads = pd.DataFrame(
|
|
955
|
+
sliced_grads[:, :6], columns=["X", "Y", "Z", "gx", "gy", "gz"]
|
|
956
|
+
)
|
|
957
|
+
coord_0_grads["coord"] = 0
|
|
958
|
+
coord_0_grads["feature_name"] = self.name
|
|
959
|
+
coord_0_grads["w"] = 1
|
|
960
|
+
|
|
961
|
+
intrusion_frame_data_complete = pd.concat([intrusion_frame_data_temp, coord_0_grads])
|
|
962
|
+
|
|
963
|
+
else:
|
|
964
|
+
intrusion_frame_data_complete = intrusion_frame_data_temp
|
|
965
|
+
|
|
966
|
+
self.add_data_from_data_frame(intrusion_frame_data_complete)
|
|
967
|
+
self.update_geometry(intrusion_frame_data_complete[["X", "Y", "Z"]].to_numpy())
|
|
968
|
+
|
|
969
|
+
def update(self):
|
|
970
|
+
for i in range(3):
|
|
971
|
+
self.builders[i].update()
|