LoopStructural 1.6.14__py3-none-any.whl → 1.6.15__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/datatypes/_bounding_box.py +22 -13
- LoopStructural/datatypes/_point.py +0 -1
- LoopStructural/export/exporters.py +2 -2
- LoopStructural/interpolators/__init__.py +33 -30
- LoopStructural/interpolators/_constant_norm.py +205 -0
- LoopStructural/interpolators/_discrete_interpolator.py +15 -14
- LoopStructural/interpolators/_finite_difference_interpolator.py +10 -10
- LoopStructural/interpolators/_geological_interpolator.py +1 -1
- LoopStructural/interpolators/_interpolatortype.py +22 -0
- LoopStructural/interpolators/_p1interpolator.py +6 -2
- LoopStructural/interpolators/_surfe_wrapper.py +4 -1
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +1 -1
- LoopStructural/interpolators/supports/_2d_structured_grid.py +16 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +16 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +7 -3
- LoopStructural/modelling/core/geological_model.py +187 -234
- LoopStructural/modelling/features/_base_geological_feature.py +38 -2
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +1 -1
- LoopStructural/modelling/intrusions/intrusion_builder.py +1 -1
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +1 -1
- LoopStructural/version.py +1 -1
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.15.dist-info}/METADATA +2 -2
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.15.dist-info}/RECORD +26 -24
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.15.dist-info}/WHEEL +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.15.dist-info}/licenses/LICENSE +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.15.dist-info}/top_level.txt +0 -0
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Main entry point for creating a geological model
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from ...utils import getLogger
|
|
5
|
+
from ...utils import getLogger
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import pandas as pd
|
|
9
|
-
from typing import List
|
|
9
|
+
from typing import List, Optional
|
|
10
10
|
import pathlib
|
|
11
11
|
from ...modelling.features.fault import FaultSegment
|
|
12
12
|
|
|
@@ -61,33 +61,24 @@ class GeologicalModel:
|
|
|
61
61
|
the origin of the model box
|
|
62
62
|
parameters : dict
|
|
63
63
|
a dictionary tracking the parameters used to build the model
|
|
64
|
-
|
|
65
|
-
the scale factor used to rescale the model
|
|
66
|
-
|
|
64
|
+
|
|
67
65
|
|
|
68
66
|
"""
|
|
69
67
|
|
|
70
68
|
def __init__(
|
|
71
69
|
self,
|
|
72
|
-
|
|
73
|
-
maximum: np.ndarray,
|
|
74
|
-
data=None,
|
|
75
|
-
nsteps=(50, 50, 25),
|
|
76
|
-
reuse_supports=False,
|
|
77
|
-
logfile=None,
|
|
78
|
-
loglevel="info",
|
|
70
|
+
*args
|
|
79
71
|
):
|
|
80
72
|
"""
|
|
81
73
|
Parameters
|
|
82
74
|
----------
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
a fudge factor for isosurfacing, used to make sure surfaces appear
|
|
75
|
+
bounding_box : BoundingBox
|
|
76
|
+
the bounding box of the model
|
|
77
|
+
origin : np.array(3,dtype=doubles)
|
|
78
|
+
the origin of the model
|
|
79
|
+
maximum : np.array(3,dtype=doubles)
|
|
80
|
+
the maximum of the model
|
|
81
|
+
|
|
91
82
|
Examples
|
|
92
83
|
--------
|
|
93
84
|
Demo data
|
|
@@ -111,37 +102,29 @@ class GeologicalModel:
|
|
|
111
102
|
|
|
112
103
|
|
|
113
104
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
args = list(args)
|
|
106
|
+
if len(args) == 0:
|
|
107
|
+
raise ValueError("Must provide either bounding_box or origin and maximum")
|
|
108
|
+
if len(args) == 1:
|
|
109
|
+
bounding_box = args[0]
|
|
110
|
+
if not isinstance(bounding_box, BoundingBox):
|
|
111
|
+
raise ValueError("Must provide a bounding box")
|
|
112
|
+
self.bounding_box = bounding_box
|
|
113
|
+
if len(args) == 2:
|
|
114
|
+
origin = np.array(args[0])
|
|
115
|
+
maximum = np.array(args[1])
|
|
116
|
+
if not isinstance(origin, np.ndarray) or not isinstance(maximum, np.ndarray):
|
|
117
|
+
raise ValueError("Must provide origin and maximum as numpy arrays")
|
|
118
|
+
self.bounding_box = BoundingBox(
|
|
119
|
+
dimensions=3,
|
|
120
|
+
origin=np.zeros(3),
|
|
121
|
+
maximum=maximum - origin,
|
|
122
|
+
global_origin=origin,
|
|
123
|
+
)
|
|
118
124
|
logger.info("Initialising geological model")
|
|
119
125
|
self.features = []
|
|
120
126
|
self.feature_name_index = {}
|
|
121
127
|
self._data = pd.DataFrame() # None
|
|
122
|
-
if data is not None:
|
|
123
|
-
self.data = data
|
|
124
|
-
self.nsteps = nsteps
|
|
125
|
-
|
|
126
|
-
# we want to rescale the model area so that the maximum length is
|
|
127
|
-
# 1
|
|
128
|
-
self.origin = np.array(origin).astype(float)
|
|
129
|
-
originstr = f"Model origin: {self.origin[0]} {self.origin[1]} {self.origin[2]}"
|
|
130
|
-
logger.info(originstr)
|
|
131
|
-
self.maximum = np.array(maximum).astype(float)
|
|
132
|
-
maximumstr = "Model maximum: {} {} {}".format(
|
|
133
|
-
self.maximum[0], self.maximum[1], self.maximum[2]
|
|
134
|
-
)
|
|
135
|
-
logger.info(maximumstr)
|
|
136
|
-
|
|
137
|
-
self.scale_factor = 1.0
|
|
138
|
-
|
|
139
|
-
self.bounding_box = BoundingBox(
|
|
140
|
-
dimensions=3,
|
|
141
|
-
origin=np.zeros(3),
|
|
142
|
-
maximum=self.maximum - self.origin,
|
|
143
|
-
global_origin=self.origin,
|
|
144
|
-
)
|
|
145
128
|
|
|
146
129
|
self.stratigraphic_column = None
|
|
147
130
|
|
|
@@ -160,10 +143,7 @@ class GeologicalModel:
|
|
|
160
143
|
json = {}
|
|
161
144
|
json["model"] = {}
|
|
162
145
|
json["model"]["features"] = [f.name for f in self.features]
|
|
163
|
-
|
|
164
|
-
# json["model"]["origin"] = self.origin.tolist()
|
|
165
|
-
# json["model"]["maximum"] = self.maximum.tolist()
|
|
166
|
-
# json["model"]["nsteps"] = self.nsteps
|
|
146
|
+
json['model']['bounding_box'] = self.bounding_box.to_dict()
|
|
167
147
|
json["model"]["stratigraphic_column"] = self.stratigraphic_column
|
|
168
148
|
# json["features"] = [f.to_json() for f in self.features]
|
|
169
149
|
return json
|
|
@@ -192,87 +172,45 @@ class GeologicalModel:
|
|
|
192
172
|
# model.features.append(GeologicalFeature.from_json(feature,model))
|
|
193
173
|
# return model
|
|
194
174
|
def __str__(self):
|
|
195
|
-
|
|
196
|
-
_str = "GeologicalModel - {} x {} x {}\n".format(*lengths)
|
|
197
|
-
_str += "------------------------------------------ \n"
|
|
198
|
-
_str += "The model contains {} GeologicalFeatures \n".format(len(self.features))
|
|
199
|
-
_str += ""
|
|
200
|
-
_str += "------------------------------------------ \n"
|
|
201
|
-
_str += ""
|
|
202
|
-
_str += "Model origin: {} {} {}\n".format(self.origin[0], self.origin[1], self.origin[2])
|
|
203
|
-
_str += "Model maximum: {} {} {}\n".format(
|
|
204
|
-
self.maximum[0], self.maximum[1], self.maximum[2]
|
|
205
|
-
)
|
|
206
|
-
_str += "Model rescale factor: {} \n".format(self.scale_factor)
|
|
207
|
-
_str += "------------------------------------------ \n"
|
|
208
|
-
_str += "Feature list: \n"
|
|
209
|
-
for feature in self.features:
|
|
210
|
-
_str += " {} \n".format(feature.name)
|
|
211
|
-
return _str
|
|
175
|
+
return f"GeologicalModel with {len(self.features)} features"
|
|
212
176
|
|
|
213
177
|
def _ipython_key_completions_(self):
|
|
214
178
|
return self.feature_name_index.keys()
|
|
215
179
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
m2l_directory,
|
|
220
|
-
foliation_params={},
|
|
221
|
-
fault_params={},
|
|
222
|
-
use_thickness=True,
|
|
223
|
-
vector_scale=1,
|
|
224
|
-
gradient=False,
|
|
225
|
-
**kwargs,
|
|
226
|
-
):
|
|
227
|
-
"""Alternate constructor for a geological model using m2l output
|
|
228
|
-
|
|
229
|
-
Uses the information saved in the map2loop files to build a geological model.
|
|
230
|
-
You can specify kwargs for building foliation using foliation_params and for
|
|
231
|
-
faults using fault_params. faults is a flag that allows for the faults to be
|
|
232
|
-
skipped.
|
|
233
|
-
|
|
234
|
-
Parameters
|
|
235
|
-
----------
|
|
236
|
-
m2l_directory : string
|
|
237
|
-
path to map2loop directory
|
|
238
|
-
|
|
239
|
-
Returns
|
|
240
|
-
-------
|
|
241
|
-
(GeologicalModel, dict)
|
|
242
|
-
the created geological model and a dictionary of the map2loop data
|
|
243
|
-
|
|
244
|
-
Notes
|
|
245
|
-
------
|
|
246
|
-
For additional information see :class:`LoopStructural.modelling.input.Map2LoopProcessor`
|
|
247
|
-
and :meth:`LoopStructural.GeologicalModel.from_processor`
|
|
248
|
-
"""
|
|
249
|
-
from LoopStructural.modelling.input.map2loop_processor import Map2LoopProcessor
|
|
250
|
-
|
|
251
|
-
log_to_file(f"{m2l_directory}/loopstructural_log.txt")
|
|
252
|
-
logger.info("Creating model from m2l directory")
|
|
253
|
-
processor = Map2LoopProcessor(m2l_directory, use_thickness)
|
|
254
|
-
processor._gradient = gradient
|
|
255
|
-
processor.vector_scale = vector_scale
|
|
256
|
-
for foliation_name in processor.stratigraphic_column.keys():
|
|
257
|
-
if foliation_name != "faults":
|
|
258
|
-
if foliation_name in foliation_params.keys():
|
|
259
|
-
processor.foliation_properties[foliation_name] = foliation_params[
|
|
260
|
-
foliation_name
|
|
261
|
-
]
|
|
262
|
-
else:
|
|
263
|
-
processor.foliation_properties[foliation_name] = foliation_params
|
|
264
|
-
|
|
265
|
-
for fault_name in processor.fault_names:
|
|
266
|
-
if fault_name in fault_params.keys():
|
|
267
|
-
for param_name, value in fault_params[fault_name].items():
|
|
268
|
-
processor.fault_properties.loc[fault_name, param_name] = value
|
|
269
|
-
else:
|
|
270
|
-
for param_name, value in fault_params.items():
|
|
271
|
-
processor.fault_properties.loc[fault_name, param_name] = value
|
|
272
|
-
|
|
273
|
-
model = GeologicalModel.from_processor(processor)
|
|
274
|
-
return model, processor
|
|
180
|
+
def prepare_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
181
|
+
data = data.copy()
|
|
182
|
+
data[['X', 'Y', 'Z']] = self.bounding_box.project(data[['X', 'Y', 'Z']].to_numpy())
|
|
275
183
|
|
|
184
|
+
if "type" in data:
|
|
185
|
+
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
186
|
+
data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
187
|
+
if "feature_name" not in data:
|
|
188
|
+
logger.error("Data does not contain 'feature_name' column")
|
|
189
|
+
raise BaseException("Cannot load data")
|
|
190
|
+
for h in all_heading():
|
|
191
|
+
if h not in data:
|
|
192
|
+
data[h] = np.nan
|
|
193
|
+
if h == "w":
|
|
194
|
+
data[h] = 1.0
|
|
195
|
+
if h == "coord":
|
|
196
|
+
data[h] = 0
|
|
197
|
+
if h == "polarity":
|
|
198
|
+
data[h] = 1.0
|
|
199
|
+
# LS wants polarity as -1 or 1, change 0 to -1
|
|
200
|
+
data.loc[data["polarity"] == 0, "polarity"] = -1.0
|
|
201
|
+
data.loc[np.isnan(data["w"]), "w"] = 1.0
|
|
202
|
+
if "strike" in data and "dip" in data:
|
|
203
|
+
logger.info("Converting strike and dip to vectors")
|
|
204
|
+
mask = np.all(~np.isnan(data.loc[:, ["strike", "dip"]]), axis=1)
|
|
205
|
+
data.loc[mask, gradient_vec_names()] = (
|
|
206
|
+
strikedip2vector(data.loc[mask, "strike"], data.loc[mask, "dip"])
|
|
207
|
+
* data.loc[mask, "polarity"].to_numpy()[:, None]
|
|
208
|
+
)
|
|
209
|
+
data.drop(["strike", "dip"], axis=1, inplace=True)
|
|
210
|
+
data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = data[
|
|
211
|
+
['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
|
|
212
|
+
].astype(float)
|
|
213
|
+
return data
|
|
276
214
|
@classmethod
|
|
277
215
|
def from_processor(cls, processor):
|
|
278
216
|
"""Builds a model from a :class:`LoopStructural.modelling.input.ProcessInputData` object
|
|
@@ -543,14 +481,14 @@ class GeologicalModel:
|
|
|
543
481
|
----------
|
|
544
482
|
data : pandas data frame
|
|
545
483
|
with column headers corresponding to the
|
|
546
|
-
|
|
484
|
+
feature_name, X, Y, Z, nx, ny, nz, val, strike, dip, dip_dir, plunge,
|
|
547
485
|
plunge_dir, azimuth
|
|
548
486
|
|
|
549
487
|
Returns
|
|
550
488
|
-------
|
|
551
489
|
Note
|
|
552
490
|
----
|
|
553
|
-
|
|
491
|
+
feature_name can be any unique identifier for the feature the data point
|
|
554
492
|
'eg' 'S0', 'S2', 'F1_axis'
|
|
555
493
|
it is then used by the create functions to get the correct data
|
|
556
494
|
"""
|
|
@@ -565,48 +503,13 @@ class GeologicalModel:
|
|
|
565
503
|
raise BaseException("Cannot load data")
|
|
566
504
|
logger.info(f"Adding data to GeologicalModel with {len(data)} data points")
|
|
567
505
|
self._data = data.copy()
|
|
506
|
+
# self._data[['X','Y','Z']] = self.bounding_box.project(self._data[['X','Y','Z']].to_numpy())
|
|
507
|
+
|
|
568
508
|
|
|
569
|
-
self._data["X"] -= self.origin[0]
|
|
570
|
-
self._data["Y"] -= self.origin[1]
|
|
571
|
-
self._data["Z"] -= self.origin[2]
|
|
572
|
-
self._data["X"] /= self.scale_factor
|
|
573
|
-
self._data["Y"] /= self.scale_factor
|
|
574
|
-
self._data["Z"] /= self.scale_factor
|
|
575
|
-
if "type" in self._data:
|
|
576
|
-
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
577
|
-
self._data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
578
|
-
if "feature_name" not in self._data:
|
|
579
|
-
logger.error("Data does not contain 'feature_name' column")
|
|
580
|
-
raise BaseException("Cannot load data")
|
|
581
|
-
for h in all_heading():
|
|
582
|
-
if h not in self._data:
|
|
583
|
-
self._data[h] = np.nan
|
|
584
|
-
if h == "w":
|
|
585
|
-
self._data[h] = 1.0
|
|
586
|
-
if h == "coord":
|
|
587
|
-
self._data[h] = 0
|
|
588
|
-
if h == "polarity":
|
|
589
|
-
self._data[h] = 1.0
|
|
590
|
-
# LS wants polarity as -1 or 1, change 0 to -1
|
|
591
|
-
self._data.loc[self._data["polarity"] == 0, "polarity"] = -1.0
|
|
592
|
-
self._data.loc[np.isnan(self._data["w"]), "w"] = 1.0
|
|
593
|
-
if "strike" in self._data and "dip" in self._data:
|
|
594
|
-
logger.info("Converting strike and dip to vectors")
|
|
595
|
-
mask = np.all(~np.isnan(self._data.loc[:, ["strike", "dip"]]), axis=1)
|
|
596
|
-
self._data.loc[mask, gradient_vec_names()] = (
|
|
597
|
-
strikedip2vector(self._data.loc[mask, "strike"], self._data.loc[mask, "dip"])
|
|
598
|
-
* self._data.loc[mask, "polarity"].to_numpy()[:, None]
|
|
599
|
-
)
|
|
600
|
-
self._data.drop(["strike", "dip"], axis=1, inplace=True)
|
|
601
|
-
self._data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = (
|
|
602
|
-
self._data[
|
|
603
|
-
['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
|
|
604
|
-
].astype(float)
|
|
605
|
-
)
|
|
606
509
|
|
|
607
510
|
def set_model_data(self, data):
|
|
608
511
|
logger.warning("deprecated method. Model data can now be set using the data attribute")
|
|
609
|
-
self.data = data
|
|
512
|
+
self.data = data.copy()
|
|
610
513
|
|
|
611
514
|
def set_stratigraphic_column(self, stratigraphic_column, cmap="tab20"):
|
|
612
515
|
"""
|
|
@@ -654,7 +557,9 @@ class GeologicalModel:
|
|
|
654
557
|
|
|
655
558
|
def create_and_add_foliation(
|
|
656
559
|
self,
|
|
657
|
-
|
|
560
|
+
series_surface_name: str,
|
|
561
|
+
*,
|
|
562
|
+
series_surface_data: pd.DataFrame = None,
|
|
658
563
|
interpolatortype: str = "FDI",
|
|
659
564
|
nelements: int = 1000,
|
|
660
565
|
tol=None,
|
|
@@ -664,8 +569,18 @@ class GeologicalModel:
|
|
|
664
569
|
"""
|
|
665
570
|
Parameters
|
|
666
571
|
----------
|
|
667
|
-
|
|
572
|
+
series_surface_name : string
|
|
668
573
|
corresponding to the feature_name in the data
|
|
574
|
+
series_surface_data : pd.DataFrame, optional
|
|
575
|
+
data frame containing the surface data
|
|
576
|
+
interpolatortype : str
|
|
577
|
+
the type of interpolator to use, default is 'FDI'
|
|
578
|
+
nelements : int
|
|
579
|
+
the number of elements to use in the series surface
|
|
580
|
+
tol : float, optional
|
|
581
|
+
tolerance for the solver, if not specified uses the model default
|
|
582
|
+
faults : list, optional
|
|
583
|
+
list of faults to be used in the series surface, if not specified uses the model faults
|
|
669
584
|
kwargs
|
|
670
585
|
|
|
671
586
|
Returns
|
|
@@ -696,16 +611,18 @@ class GeologicalModel:
|
|
|
696
611
|
bounding_box=self.bounding_box,
|
|
697
612
|
interpolatortype=interpolatortype,
|
|
698
613
|
nelements=nelements,
|
|
699
|
-
name=
|
|
614
|
+
name=series_surface_name,
|
|
700
615
|
model=self,
|
|
701
616
|
**kwargs,
|
|
702
617
|
)
|
|
703
618
|
# add data
|
|
704
|
-
|
|
705
|
-
|
|
619
|
+
if series_surface_data is None:
|
|
620
|
+
series_surface_data = self.data.loc[self.data["feature_name"] == series_surface_name]
|
|
621
|
+
|
|
622
|
+
if series_surface_data.shape[0] == 0:
|
|
706
623
|
logger.warning("No data for {series_surface_data}, skipping")
|
|
707
624
|
return
|
|
708
|
-
series_builder.add_data_from_data_frame(
|
|
625
|
+
series_builder.add_data_from_data_frame(self.prepare_data(series_surface_data))
|
|
709
626
|
self._add_faults(series_builder, features=faults)
|
|
710
627
|
|
|
711
628
|
# build feature
|
|
@@ -721,7 +638,9 @@ class GeologicalModel:
|
|
|
721
638
|
|
|
722
639
|
def create_and_add_fold_frame(
|
|
723
640
|
self,
|
|
724
|
-
|
|
641
|
+
fold_frame_name:str,
|
|
642
|
+
*,
|
|
643
|
+
fold_frame_data=None,
|
|
725
644
|
interpolatortype="FDI",
|
|
726
645
|
nelements=1000,
|
|
727
646
|
tol=None,
|
|
@@ -731,10 +650,24 @@ class GeologicalModel:
|
|
|
731
650
|
"""
|
|
732
651
|
Parameters
|
|
733
652
|
----------
|
|
734
|
-
|
|
653
|
+
fold_frame_name : string
|
|
735
654
|
unique string in feature_name column
|
|
736
|
-
|
|
737
|
-
|
|
655
|
+
fold_frame_data : pandas data frame
|
|
656
|
+
if not specified uses the model data
|
|
657
|
+
interpolatortype : str
|
|
658
|
+
the type of interpolator to use, default is 'FDI'
|
|
659
|
+
nelements : int
|
|
660
|
+
the number of elements to use in the fold frame
|
|
661
|
+
tol : float, optional
|
|
662
|
+
tolerance for the solver
|
|
663
|
+
buffer : float
|
|
664
|
+
buffer to add to the bounding box of the fold frame
|
|
665
|
+
**kwargs : dict
|
|
666
|
+
additional parameters to be passed to the
|
|
667
|
+
:class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
|
|
668
|
+
and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
|
|
669
|
+
and the interpolator, such as `domain` or `tol`
|
|
670
|
+
|
|
738
671
|
|
|
739
672
|
Returns
|
|
740
673
|
-------
|
|
@@ -751,15 +684,19 @@ class GeologicalModel:
|
|
|
751
684
|
fold_frame_builder = StructuralFrameBuilder(
|
|
752
685
|
interpolatortype=interpolatortype,
|
|
753
686
|
bounding_box=self.bounding_box.with_buffer(buffer),
|
|
754
|
-
name=
|
|
687
|
+
name=fold_frame_name,
|
|
755
688
|
frame=FoldFrame,
|
|
756
689
|
nelements=nelements,
|
|
757
690
|
model=self,
|
|
758
691
|
**kwargs,
|
|
759
692
|
)
|
|
760
693
|
# add data
|
|
761
|
-
fold_frame_data
|
|
762
|
-
|
|
694
|
+
if fold_frame_data is None:
|
|
695
|
+
fold_frame_data = self.data.loc[self.data["feature_name"] == fold_frame_name]
|
|
696
|
+
if fold_frame_data.shape[0] == 0:
|
|
697
|
+
logger.warning(f"No data for {fold_frame_name}, skipping")
|
|
698
|
+
return
|
|
699
|
+
fold_frame_builder.add_data_from_data_frame(self.prepare_data(fold_frame_data))
|
|
763
700
|
self._add_faults(fold_frame_builder[0])
|
|
764
701
|
self._add_faults(fold_frame_builder[1])
|
|
765
702
|
self._add_faults(fold_frame_builder[2])
|
|
@@ -775,7 +712,9 @@ class GeologicalModel:
|
|
|
775
712
|
|
|
776
713
|
def create_and_add_folded_foliation(
|
|
777
714
|
self,
|
|
778
|
-
|
|
715
|
+
foliation_name,
|
|
716
|
+
*,
|
|
717
|
+
foliation_data=None,
|
|
779
718
|
interpolatortype="DFI",
|
|
780
719
|
nelements=10000,
|
|
781
720
|
buffer=0.1,
|
|
@@ -832,14 +771,20 @@ class GeologicalModel:
|
|
|
832
771
|
bounding_box=self.bounding_box.with_buffer(buffer),
|
|
833
772
|
nelements=nelements,
|
|
834
773
|
fold=fold,
|
|
835
|
-
name=
|
|
774
|
+
name=foliation_name,
|
|
836
775
|
svario=svario,
|
|
837
776
|
model=self,
|
|
838
777
|
**kwargs,
|
|
839
778
|
)
|
|
840
|
-
|
|
779
|
+
if foliation_data is None:
|
|
780
|
+
foliation_data = self.data.loc[self.data["feature_name"] == foliation_name]
|
|
781
|
+
if foliation_data.shape[0] == 0:
|
|
782
|
+
logger.warning(f"No data for {foliation_name}, skipping")
|
|
783
|
+
return
|
|
841
784
|
series_builder.add_data_from_data_frame(
|
|
842
|
-
self.
|
|
785
|
+
self.prepare_data(
|
|
786
|
+
foliation_data
|
|
787
|
+
)
|
|
843
788
|
)
|
|
844
789
|
self._add_faults(series_builder)
|
|
845
790
|
# series_builder.add_data_to_interpolator(True)
|
|
@@ -858,7 +803,9 @@ class GeologicalModel:
|
|
|
858
803
|
|
|
859
804
|
def create_and_add_folded_fold_frame(
|
|
860
805
|
self,
|
|
861
|
-
|
|
806
|
+
fold_frame_name: str,
|
|
807
|
+
*,
|
|
808
|
+
fold_frame_data: Optional[pd.DataFrame] = None,
|
|
862
809
|
interpolatortype="FDI",
|
|
863
810
|
nelements=10000,
|
|
864
811
|
fold_frame=None,
|
|
@@ -869,14 +816,22 @@ class GeologicalModel:
|
|
|
869
816
|
|
|
870
817
|
Parameters
|
|
871
818
|
----------
|
|
872
|
-
|
|
819
|
+
fold_frame_name : string
|
|
873
820
|
name of the feature to be added
|
|
874
|
-
|
|
821
|
+
fold_frame_data : pandas data frame, optional
|
|
822
|
+
data frame containing the fold frame data, if not specified uses the model data
|
|
823
|
+
interpolatortype : str
|
|
824
|
+
the type of interpolator to use, default is 'FDI' (unused) 5/6/2025
|
|
875
825
|
fold_frame : StructuralFrame, optional
|
|
876
826
|
the fold frame for the fold if not specified uses last feature added
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
827
|
+
nelements : int
|
|
828
|
+
the number of elements to use in the fold frame
|
|
829
|
+
tol : float, optional
|
|
830
|
+
tolerance for the solver, if not specified uses the model default
|
|
831
|
+
**kwargs : dict
|
|
832
|
+
additional parameters to be passed to the
|
|
833
|
+
:class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
|
|
834
|
+
and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
|
|
880
835
|
|
|
881
836
|
Returns
|
|
882
837
|
-------
|
|
@@ -917,15 +872,15 @@ class GeologicalModel:
|
|
|
917
872
|
interpolatortype=interpolatortypes,
|
|
918
873
|
bounding_box=self.bounding_box.with_buffer(kwargs.get("buffer", 0.1)),
|
|
919
874
|
nelements=[nelements, nelements, nelements],
|
|
920
|
-
name=
|
|
875
|
+
name=fold_frame_name,
|
|
921
876
|
fold=fold,
|
|
922
877
|
frame=FoldFrame,
|
|
923
878
|
model=self,
|
|
924
879
|
**kwargs,
|
|
925
880
|
)
|
|
926
|
-
|
|
927
|
-
self.data[self.data["feature_name"] ==
|
|
928
|
-
)
|
|
881
|
+
if fold_frame_data is None:
|
|
882
|
+
fold_frame_data = self.data[self.data["feature_name"] == fold_frame_name]
|
|
883
|
+
fold_frame_builder.add_data_from_data_frame(self.prepare_data(fold_frame_data))
|
|
929
884
|
|
|
930
885
|
for i in range(3):
|
|
931
886
|
self._add_faults(fold_frame_builder[i])
|
|
@@ -947,6 +902,7 @@ class GeologicalModel:
|
|
|
947
902
|
self,
|
|
948
903
|
intrusion_name,
|
|
949
904
|
intrusion_frame_name,
|
|
905
|
+
*,
|
|
950
906
|
intrusion_frame_parameters={},
|
|
951
907
|
intrusion_lateral_extent_model=None,
|
|
952
908
|
intrusion_vertical_extent_model=None,
|
|
@@ -1224,7 +1180,7 @@ class GeologicalModel:
|
|
|
1224
1180
|
return uc_feature
|
|
1225
1181
|
|
|
1226
1182
|
def create_and_add_domain_fault(
|
|
1227
|
-
self, fault_surface_data
|
|
1183
|
+
self, fault_surface_data,*, nelements=10000, interpolatortype="FDI", **kwargs
|
|
1228
1184
|
):
|
|
1229
1185
|
"""
|
|
1230
1186
|
Parameters
|
|
@@ -1252,7 +1208,7 @@ class GeologicalModel:
|
|
|
1252
1208
|
)
|
|
1253
1209
|
|
|
1254
1210
|
# add data
|
|
1255
|
-
unconformity_data = self.data[self.data["feature_name"] == fault_surface_data]
|
|
1211
|
+
unconformity_data = self.data.loc[self.data["feature_name"] == fault_surface_data]
|
|
1256
1212
|
|
|
1257
1213
|
domain_fault_feature_builder.add_data_from_data_frame(unconformity_data)
|
|
1258
1214
|
# look through existing features if there is a fault before an
|
|
@@ -1275,8 +1231,10 @@ class GeologicalModel:
|
|
|
1275
1231
|
|
|
1276
1232
|
def create_and_add_fault(
|
|
1277
1233
|
self,
|
|
1278
|
-
|
|
1279
|
-
displacement,
|
|
1234
|
+
fault_name: str,
|
|
1235
|
+
displacement: float,
|
|
1236
|
+
*,
|
|
1237
|
+
fault_data:Optional[pd.DataFrame] = None,
|
|
1280
1238
|
interpolatortype="FDI",
|
|
1281
1239
|
tol=None,
|
|
1282
1240
|
fault_slip_vector=None,
|
|
@@ -1299,9 +1257,12 @@ class GeologicalModel:
|
|
|
1299
1257
|
"""
|
|
1300
1258
|
Parameters
|
|
1301
1259
|
----------
|
|
1302
|
-
|
|
1260
|
+
fault_name : string
|
|
1303
1261
|
name of the fault surface data in the dataframe
|
|
1304
1262
|
displacement : displacement magnitude
|
|
1263
|
+
displacement magnitude of the fault, in model units
|
|
1264
|
+
fault_data : pd.DataFrame, optional
|
|
1265
|
+
data frame containing the fault data, if not specified uses the model data
|
|
1305
1266
|
major_axis : [type], optional
|
|
1306
1267
|
[description], by default None
|
|
1307
1268
|
minor_axis : [type], optional
|
|
@@ -1328,7 +1289,7 @@ class GeologicalModel:
|
|
|
1328
1289
|
if "fault_vectical_radius" in kwargs and intermediate_axis is None:
|
|
1329
1290
|
intermediate_axis = kwargs["fault_vectical_radius"]
|
|
1330
1291
|
|
|
1331
|
-
logger.info(f'Creating fault "{
|
|
1292
|
+
logger.info(f'Creating fault "{fault_name}"')
|
|
1332
1293
|
logger.info(f"Displacement: {displacement}")
|
|
1333
1294
|
logger.info(f"Tolerance: {tol}")
|
|
1334
1295
|
logger.info(f"Fault function: {faultfunction}")
|
|
@@ -1353,35 +1314,39 @@ class GeologicalModel:
|
|
|
1353
1314
|
# tol *= 0.1*minor_axis
|
|
1354
1315
|
|
|
1355
1316
|
if displacement == 0:
|
|
1356
|
-
logger.warning(f"{
|
|
1317
|
+
logger.warning(f"{fault_name} displacement is 0")
|
|
1357
1318
|
|
|
1358
1319
|
if "data_region" in kwargs:
|
|
1359
1320
|
kwargs.pop("data_region")
|
|
1360
1321
|
logger.error("kwarg data_region currently not supported, disabling")
|
|
1361
|
-
displacement_scaled = displacement
|
|
1322
|
+
displacement_scaled = displacement
|
|
1362
1323
|
fault_frame_builder = FaultBuilder(
|
|
1363
1324
|
interpolatortype,
|
|
1364
1325
|
bounding_box=self.bounding_box,
|
|
1365
1326
|
nelements=kwargs.pop("nelements", 1e4),
|
|
1366
|
-
name=
|
|
1327
|
+
name=fault_name,
|
|
1367
1328
|
model=self,
|
|
1368
1329
|
**kwargs,
|
|
1369
1330
|
)
|
|
1370
|
-
|
|
1331
|
+
if fault_data is None:
|
|
1332
|
+
fault_data = self.data.loc[self.data["feature_name"] == fault_name]
|
|
1333
|
+
if fault_data.shape[0] == 0:
|
|
1334
|
+
logger.warning(f"No data for {fault_name}, skipping")
|
|
1335
|
+
return
|
|
1336
|
+
|
|
1371
1337
|
self._add_faults(fault_frame_builder, features=faults)
|
|
1372
1338
|
# add data
|
|
1373
|
-
fault_frame_data = self.data.loc[self.data["feature_name"] == fault_surface_data].copy()
|
|
1374
1339
|
|
|
1375
1340
|
if fault_center is not None and ~np.isnan(fault_center).any():
|
|
1376
1341
|
fault_center = self.scale(fault_center, inplace=False)
|
|
1377
1342
|
if minor_axis:
|
|
1378
|
-
minor_axis = minor_axis
|
|
1343
|
+
minor_axis = minor_axis
|
|
1379
1344
|
if major_axis:
|
|
1380
|
-
major_axis = major_axis
|
|
1345
|
+
major_axis = major_axis
|
|
1381
1346
|
if intermediate_axis:
|
|
1382
|
-
intermediate_axis = intermediate_axis
|
|
1347
|
+
intermediate_axis = intermediate_axis
|
|
1383
1348
|
fault_frame_builder.create_data_from_geometry(
|
|
1384
|
-
fault_frame_data=
|
|
1349
|
+
fault_frame_data=self.prepare_data(fault_data),
|
|
1385
1350
|
fault_center=fault_center,
|
|
1386
1351
|
fault_normal_vector=fault_normal_vector,
|
|
1387
1352
|
fault_slip_vector=fault_slip_vector,
|
|
@@ -1418,7 +1383,7 @@ class GeologicalModel:
|
|
|
1418
1383
|
return fault
|
|
1419
1384
|
|
|
1420
1385
|
# TODO move rescale to bounding box/transformer
|
|
1421
|
-
def rescale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1386
|
+
def rescale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
|
|
1422
1387
|
"""
|
|
1423
1388
|
Convert from model scale to real world scale - in the future this
|
|
1424
1389
|
should also do transformations?
|
|
@@ -1434,14 +1399,11 @@ class GeologicalModel:
|
|
|
1434
1399
|
points : np.array((N,3),dtype=double)
|
|
1435
1400
|
|
|
1436
1401
|
"""
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
points *= self.scale_factor
|
|
1440
|
-
points += self.origin
|
|
1441
|
-
return points
|
|
1402
|
+
|
|
1403
|
+
return self.bounding_box.reproject(points,inplace=inplace)
|
|
1442
1404
|
|
|
1443
1405
|
# TODO move scale to bounding box/transformer
|
|
1444
|
-
def scale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1406
|
+
def scale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
|
|
1445
1407
|
"""Take points in UTM coordinates and reproject
|
|
1446
1408
|
into scaled model space
|
|
1447
1409
|
|
|
@@ -1456,18 +1418,9 @@ class GeologicalModel:
|
|
|
1456
1418
|
points : np.a::rray((N,3),dtype=double)
|
|
1457
1419
|
|
|
1458
1420
|
"""
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
# if len(points.shape) == 1:
|
|
1463
|
-
# points = points[None,:]
|
|
1464
|
-
# if len(points.shape) != 2:
|
|
1465
|
-
# logger.error("cannot scale array of dimensions".format(len(points.shape)))
|
|
1466
|
-
points -= self.origin
|
|
1467
|
-
points /= self.scale_factor
|
|
1468
|
-
return points
|
|
1469
|
-
|
|
1470
|
-
def regular_grid(self, nsteps=None, shuffle=True, rescale=False, order="C"):
|
|
1421
|
+
return self.bounding_box.project(np.array(points).astype(float),inplace=inplace)
|
|
1422
|
+
|
|
1423
|
+
def regular_grid(self, *, nsteps=None, shuffle=True, rescale=False, order="C"):
|
|
1471
1424
|
"""
|
|
1472
1425
|
Return a regular grid within the model bounding box
|
|
1473
1426
|
|
|
@@ -1483,7 +1436,7 @@ class GeologicalModel:
|
|
|
1483
1436
|
"""
|
|
1484
1437
|
return self.bounding_box.regular_grid(nsteps=nsteps, shuffle=shuffle, order=order)
|
|
1485
1438
|
|
|
1486
|
-
def evaluate_model(self, xyz: np.ndarray, scale: bool = True) -> np.ndarray:
|
|
1439
|
+
def evaluate_model(self, xyz: np.ndarray, *, scale: bool = True) -> np.ndarray:
|
|
1487
1440
|
"""Evaluate the stratigraphic id at each location
|
|
1488
1441
|
|
|
1489
1442
|
Parameters
|
|
@@ -1559,7 +1512,7 @@ class GeologicalModel:
|
|
|
1559
1512
|
logger.error(f"Model does not contain {group}")
|
|
1560
1513
|
return strat_id
|
|
1561
1514
|
|
|
1562
|
-
def evaluate_model_gradient(self, points: np.ndarray, scale: bool = True) -> np.ndarray:
|
|
1515
|
+
def evaluate_model_gradient(self, points: np.ndarray, *, scale: bool = True) -> np.ndarray:
|
|
1563
1516
|
"""Evaluate the gradient of the stratigraphic column at each location
|
|
1564
1517
|
|
|
1565
1518
|
Parameters
|
|
@@ -1614,7 +1567,7 @@ class GeologicalModel:
|
|
|
1614
1567
|
if f.type == FeatureType.FAULT:
|
|
1615
1568
|
disp = f.displacementfeature.evaluate_value(points)
|
|
1616
1569
|
vals[~np.isnan(disp)] += disp[~np.isnan(disp)]
|
|
1617
|
-
return vals
|
|
1570
|
+
return vals # convert from restoration magnutude to displacement
|
|
1618
1571
|
|
|
1619
1572
|
def get_feature_by_name(self, feature_name) -> GeologicalFeature:
|
|
1620
1573
|
"""Returns a feature from the mode given a name
|
|
@@ -1717,15 +1670,15 @@ class GeologicalModel:
|
|
|
1717
1670
|
for f in self.features:
|
|
1718
1671
|
if f.type == FeatureType.FAULT:
|
|
1719
1672
|
nfeatures += 3
|
|
1720
|
-
total_dof += f[0].interpolator.
|
|
1673
|
+
total_dof += f[0].interpolator.dof * 3
|
|
1721
1674
|
continue
|
|
1722
1675
|
if isinstance(f, StructuralFrame):
|
|
1723
1676
|
nfeatures += 3
|
|
1724
|
-
total_dof += f[0].interpolator.
|
|
1677
|
+
total_dof += f[0].interpolator.dof * 3
|
|
1725
1678
|
continue
|
|
1726
1679
|
if f.type == FeatureType.INTERPOLATED:
|
|
1727
1680
|
nfeatures += 1
|
|
1728
|
-
total_dof += f.interpolator.
|
|
1681
|
+
total_dof += f.interpolator.dof
|
|
1729
1682
|
continue
|
|
1730
1683
|
if verbose:
|
|
1731
1684
|
print(
|