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.

Files changed (129) hide show
  1. LoopStructural/__init__.py +52 -0
  2. LoopStructural/datasets/__init__.py +23 -0
  3. LoopStructural/datasets/_base.py +301 -0
  4. LoopStructural/datasets/_example_models.py +10 -0
  5. LoopStructural/datasets/data/claudius.csv +21049 -0
  6. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  7. LoopStructural/datasets/data/duplex.csv +126 -0
  8. LoopStructural/datasets/data/duplexbb.txt +2 -0
  9. LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
  10. LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  11. LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
  12. LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  13. LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  14. LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
  15. LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
  16. LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
  17. LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
  18. LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
  19. LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
  20. LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
  21. LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
  22. LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
  23. LoopStructural/datasets/data/intrusion.csv +1017 -0
  24. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  25. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  26. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  27. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  28. LoopStructural/datasets/data/refolded_fold.csv +205 -0
  29. LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
  30. LoopStructural/datatypes/__init__.py +4 -0
  31. LoopStructural/datatypes/_bounding_box.py +422 -0
  32. LoopStructural/datatypes/_point.py +166 -0
  33. LoopStructural/datatypes/_structured_grid.py +94 -0
  34. LoopStructural/datatypes/_surface.py +184 -0
  35. LoopStructural/export/exporters.py +554 -0
  36. LoopStructural/export/file_formats.py +15 -0
  37. LoopStructural/export/geoh5.py +100 -0
  38. LoopStructural/export/gocad.py +126 -0
  39. LoopStructural/export/omf_wrapper.py +88 -0
  40. LoopStructural/interpolators/__init__.py +105 -0
  41. LoopStructural/interpolators/_api.py +143 -0
  42. LoopStructural/interpolators/_builders.py +149 -0
  43. LoopStructural/interpolators/_cython/__init__.py +0 -0
  44. LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
  45. LoopStructural/interpolators/_discrete_interpolator.py +692 -0
  46. LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
  47. LoopStructural/interpolators/_geological_interpolator.py +380 -0
  48. LoopStructural/interpolators/_interpolator_factory.py +89 -0
  49. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  50. LoopStructural/interpolators/_operator.py +38 -0
  51. LoopStructural/interpolators/_p1interpolator.py +228 -0
  52. LoopStructural/interpolators/_p2interpolator.py +277 -0
  53. LoopStructural/interpolators/_surfe_wrapper.py +174 -0
  54. LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
  55. LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
  56. LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
  57. LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
  58. LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  59. LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
  60. LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
  61. LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
  62. LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
  63. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
  64. LoopStructural/interpolators/supports/__init__.py +55 -0
  65. LoopStructural/interpolators/supports/_aabb.py +77 -0
  66. LoopStructural/interpolators/supports/_base_support.py +114 -0
  67. LoopStructural/interpolators/supports/_face_table.py +70 -0
  68. LoopStructural/interpolators/supports/_support_factory.py +32 -0
  69. LoopStructural/modelling/__init__.py +29 -0
  70. LoopStructural/modelling/core/__init__.py +0 -0
  71. LoopStructural/modelling/core/geological_model.py +1867 -0
  72. LoopStructural/modelling/features/__init__.py +32 -0
  73. LoopStructural/modelling/features/_analytical_feature.py +79 -0
  74. LoopStructural/modelling/features/_base_geological_feature.py +364 -0
  75. LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
  76. LoopStructural/modelling/features/_geological_feature.py +288 -0
  77. LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
  78. LoopStructural/modelling/features/_region.py +18 -0
  79. LoopStructural/modelling/features/_structural_frame.py +186 -0
  80. LoopStructural/modelling/features/_unconformity_feature.py +83 -0
  81. LoopStructural/modelling/features/builders/__init__.py +5 -0
  82. LoopStructural/modelling/features/builders/_base_builder.py +111 -0
  83. LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
  84. LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
  85. LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
  86. LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
  87. LoopStructural/modelling/features/fault/__init__.py +3 -0
  88. LoopStructural/modelling/features/fault/_fault_function.py +444 -0
  89. LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
  90. LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
  91. LoopStructural/modelling/features/fold/__init__.py +9 -0
  92. LoopStructural/modelling/features/fold/_fold.py +167 -0
  93. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
  94. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
  95. LoopStructural/modelling/features/fold/_foldframe.py +194 -0
  96. LoopStructural/modelling/features/fold/_svariogram.py +188 -0
  97. LoopStructural/modelling/input/__init__.py +2 -0
  98. LoopStructural/modelling/input/fault_network.py +80 -0
  99. LoopStructural/modelling/input/map2loop_processor.py +165 -0
  100. LoopStructural/modelling/input/process_data.py +650 -0
  101. LoopStructural/modelling/input/project_file.py +84 -0
  102. LoopStructural/modelling/intrusions/__init__.py +25 -0
  103. LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
  104. LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
  105. LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
  106. LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
  107. LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
  108. LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
  109. LoopStructural/utils/__init__.py +38 -0
  110. LoopStructural/utils/_surface.py +143 -0
  111. LoopStructural/utils/_transformation.py +76 -0
  112. LoopStructural/utils/config.py +18 -0
  113. LoopStructural/utils/dtm_creator.py +17 -0
  114. LoopStructural/utils/exceptions.py +31 -0
  115. LoopStructural/utils/helper.py +292 -0
  116. LoopStructural/utils/json_encoder.py +18 -0
  117. LoopStructural/utils/linalg.py +8 -0
  118. LoopStructural/utils/logging.py +79 -0
  119. LoopStructural/utils/maths.py +245 -0
  120. LoopStructural/utils/regions.py +103 -0
  121. LoopStructural/utils/typing.py +7 -0
  122. LoopStructural/utils/utils.py +68 -0
  123. LoopStructural/version.py +1 -0
  124. LoopStructural/visualisation/__init__.py +11 -0
  125. LoopStructural-1.6.1.dist-info/LICENSE +21 -0
  126. LoopStructural-1.6.1.dist-info/METADATA +81 -0
  127. LoopStructural-1.6.1.dist-info/RECORD +129 -0
  128. LoopStructural-1.6.1.dist-info/WHEEL +5 -0
  129. 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()