LoopStructural 1.6.14__py3-none-any.whl → 1.6.16__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 +30 -12
- 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 +9 -3
- 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 +250 -312
- LoopStructural/modelling/core/stratigraphic_column.py +473 -0
- LoopStructural/modelling/features/_base_geological_feature.py +38 -2
- LoopStructural/modelling/features/builders/_fault_builder.py +1 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +1 -1
- LoopStructural/modelling/features/fault/_fault_segment.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.16.dist-info}/METADATA +2 -2
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/RECORD +30 -27
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/WHEEL +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.dist-info}/licenses/LICENSE +0 -0
- {loopstructural-1.6.14.dist-info → loopstructural-1.6.16.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
|
|
|
@@ -37,7 +37,7 @@ from ...datatypes import BoundingBox
|
|
|
37
37
|
from ...modelling.intrusions import IntrusionBuilder
|
|
38
38
|
|
|
39
39
|
from ...modelling.intrusions import IntrusionFrameBuilder
|
|
40
|
-
|
|
40
|
+
from .stratigraphic_column import StratigraphicColumn
|
|
41
41
|
|
|
42
42
|
logger = getLogger(__name__)
|
|
43
43
|
|
|
@@ -61,33 +61,21 @@ 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
|
-
scale_factor : double
|
|
65
|
-
the scale factor used to rescale the model
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
"""
|
|
69
67
|
|
|
70
|
-
def __init__(
|
|
71
|
-
self,
|
|
72
|
-
origin: np.ndarray,
|
|
73
|
-
maximum: np.ndarray,
|
|
74
|
-
data=None,
|
|
75
|
-
nsteps=(50, 50, 25),
|
|
76
|
-
reuse_supports=False,
|
|
77
|
-
logfile=None,
|
|
78
|
-
loglevel="info",
|
|
79
|
-
):
|
|
68
|
+
def __init__(self, *args):
|
|
80
69
|
"""
|
|
81
70
|
Parameters
|
|
82
71
|
----------
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
a fudge factor for isosurfacing, used to make sure surfaces appear
|
|
72
|
+
bounding_box : BoundingBox
|
|
73
|
+
the bounding box of the model
|
|
74
|
+
origin : np.array(3,dtype=doubles)
|
|
75
|
+
the origin of the model
|
|
76
|
+
maximum : np.array(3,dtype=doubles)
|
|
77
|
+
the maximum of the model
|
|
78
|
+
|
|
91
79
|
Examples
|
|
92
80
|
--------
|
|
93
81
|
Demo data
|
|
@@ -111,39 +99,32 @@ class GeologicalModel:
|
|
|
111
99
|
|
|
112
100
|
|
|
113
101
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
args = list(args)
|
|
103
|
+
if len(args) == 0:
|
|
104
|
+
raise ValueError("Must provide either bounding_box or origin and maximum")
|
|
105
|
+
if len(args) == 1:
|
|
106
|
+
bounding_box = args[0]
|
|
107
|
+
if not isinstance(bounding_box, BoundingBox):
|
|
108
|
+
raise ValueError("Must provide a bounding box")
|
|
109
|
+
self.bounding_box = bounding_box
|
|
110
|
+
if len(args) == 2:
|
|
111
|
+
origin = np.array(args[0])
|
|
112
|
+
maximum = np.array(args[1])
|
|
113
|
+
if not isinstance(origin, np.ndarray) or not isinstance(maximum, np.ndarray):
|
|
114
|
+
raise ValueError("Must provide origin and maximum as numpy arrays")
|
|
115
|
+
self.bounding_box = BoundingBox(
|
|
116
|
+
dimensions=3,
|
|
117
|
+
origin=np.zeros(3),
|
|
118
|
+
maximum=maximum - origin,
|
|
119
|
+
global_origin=origin,
|
|
120
|
+
)
|
|
118
121
|
logger.info("Initialising geological model")
|
|
119
122
|
self.features = []
|
|
120
123
|
self.feature_name_index = {}
|
|
121
124
|
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
125
|
|
|
139
|
-
self.
|
|
140
|
-
dimensions=3,
|
|
141
|
-
origin=np.zeros(3),
|
|
142
|
-
maximum=self.maximum - self.origin,
|
|
143
|
-
global_origin=self.origin,
|
|
144
|
-
)
|
|
126
|
+
self.stratigraphic_column = StratigraphicColumn()
|
|
145
127
|
|
|
146
|
-
self.stratigraphic_column = None
|
|
147
128
|
|
|
148
129
|
self.tol = 1e-10 * np.max(self.bounding_box.maximum - self.bounding_box.origin)
|
|
149
130
|
self._dtm = None
|
|
@@ -160,119 +141,83 @@ class GeologicalModel:
|
|
|
160
141
|
json = {}
|
|
161
142
|
json["model"] = {}
|
|
162
143
|
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
|
|
144
|
+
json['model']['bounding_box'] = self.bounding_box.to_dict()
|
|
167
145
|
json["model"]["stratigraphic_column"] = self.stratigraphic_column
|
|
168
146
|
# json["features"] = [f.to_json() for f in self.features]
|
|
169
147
|
return json
|
|
170
148
|
|
|
171
|
-
# @classmethod
|
|
172
|
-
# def from_json(cls,json):
|
|
173
|
-
# """
|
|
174
|
-
# Create a geological model from a json string
|
|
175
|
-
|
|
176
|
-
# Parameters
|
|
177
|
-
# ----------
|
|
178
|
-
# json : str
|
|
179
|
-
# json string of the geological model
|
|
180
|
-
|
|
181
|
-
# Returns
|
|
182
|
-
# -------
|
|
183
|
-
# model : GeologicalModel
|
|
184
|
-
# a geological model
|
|
185
|
-
# """
|
|
186
|
-
# model = cls(json["model"]["origin"],json["model"]["maximum"],data=None)
|
|
187
|
-
# model.stratigraphic_column = json["model"]["stratigraphic_column"]
|
|
188
|
-
# model.nsteps = json["model"]["nsteps"]
|
|
189
|
-
# model.data = pd.read_json(json["model"]["data"])
|
|
190
|
-
# model.features = []
|
|
191
|
-
# for feature in json["features"]:
|
|
192
|
-
# model.features.append(GeologicalFeature.from_json(feature,model))
|
|
193
|
-
# return model
|
|
194
149
|
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
|
|
150
|
+
return f"GeologicalModel with {len(self.features)} features"
|
|
212
151
|
|
|
213
152
|
def _ipython_key_completions_(self):
|
|
214
153
|
return self.feature_name_index.keys()
|
|
215
154
|
|
|
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
|
|
155
|
+
def prepare_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
156
|
+
data = data.copy()
|
|
157
|
+
data[['X', 'Y', 'Z']] = self.bounding_box.project(data[['X', 'Y', 'Z']].to_numpy())
|
|
238
158
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
else:
|
|
270
|
-
for param_name, value in fault_params.items():
|
|
271
|
-
processor.fault_properties.loc[fault_name, param_name] = value
|
|
159
|
+
if "type" in data:
|
|
160
|
+
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
161
|
+
data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
162
|
+
if "feature_name" not in data:
|
|
163
|
+
logger.error("Data does not contain 'feature_name' column")
|
|
164
|
+
raise BaseException("Cannot load data")
|
|
165
|
+
for h in all_heading():
|
|
166
|
+
if h not in data:
|
|
167
|
+
data[h] = np.nan
|
|
168
|
+
if h == "w":
|
|
169
|
+
data[h] = 1.0
|
|
170
|
+
if h == "coord":
|
|
171
|
+
data[h] = 0
|
|
172
|
+
if h == "polarity":
|
|
173
|
+
data[h] = 1.0
|
|
174
|
+
# LS wants polarity as -1 or 1, change 0 to -1
|
|
175
|
+
data.loc[data["polarity"] == 0, "polarity"] = -1.0
|
|
176
|
+
data.loc[np.isnan(data["w"]), "w"] = 1.0
|
|
177
|
+
if "strike" in data and "dip" in data:
|
|
178
|
+
logger.info("Converting strike and dip to vectors")
|
|
179
|
+
mask = np.all(~np.isnan(data.loc[:, ["strike", "dip"]]), axis=1)
|
|
180
|
+
data.loc[mask, gradient_vec_names()] = (
|
|
181
|
+
strikedip2vector(data.loc[mask, "strike"], data.loc[mask, "dip"])
|
|
182
|
+
* data.loc[mask, "polarity"].to_numpy()[:, None]
|
|
183
|
+
)
|
|
184
|
+
data.drop(["strike", "dip"], axis=1, inplace=True)
|
|
185
|
+
data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = data[
|
|
186
|
+
['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
|
|
187
|
+
].astype(float)
|
|
188
|
+
return data
|
|
272
189
|
|
|
273
|
-
model = GeologicalModel.from_processor(processor)
|
|
274
|
-
return model, processor
|
|
275
190
|
|
|
191
|
+
if "type" in data:
|
|
192
|
+
logger.warning("'type' is deprecated replace with 'feature_name' \n")
|
|
193
|
+
data.rename(columns={"type": "feature_name"}, inplace=True)
|
|
194
|
+
if "feature_name" not in data:
|
|
195
|
+
logger.error("Data does not contain 'feature_name' column")
|
|
196
|
+
raise BaseException("Cannot load data")
|
|
197
|
+
for h in all_heading():
|
|
198
|
+
if h not in data:
|
|
199
|
+
data[h] = np.nan
|
|
200
|
+
if h == "w":
|
|
201
|
+
data[h] = 1.0
|
|
202
|
+
if h == "coord":
|
|
203
|
+
data[h] = 0
|
|
204
|
+
if h == "polarity":
|
|
205
|
+
data[h] = 1.0
|
|
206
|
+
# LS wants polarity as -1 or 1, change 0 to -1
|
|
207
|
+
data.loc[data["polarity"] == 0, "polarity"] = -1.0
|
|
208
|
+
data.loc[np.isnan(data["w"]), "w"] = 1.0
|
|
209
|
+
if "strike" in data and "dip" in data:
|
|
210
|
+
logger.info("Converting strike and dip to vectors")
|
|
211
|
+
mask = np.all(~np.isnan(data.loc[:, ["strike", "dip"]]), axis=1)
|
|
212
|
+
data.loc[mask, gradient_vec_names()] = (
|
|
213
|
+
strikedip2vector(data.loc[mask, "strike"], data.loc[mask, "dip"])
|
|
214
|
+
* data.loc[mask, "polarity"].to_numpy()[:, None]
|
|
215
|
+
)
|
|
216
|
+
data.drop(["strike", "dip"], axis=1, inplace=True)
|
|
217
|
+
data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = data[
|
|
218
|
+
['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
|
|
219
|
+
].astype(float)
|
|
220
|
+
return data
|
|
276
221
|
@classmethod
|
|
277
222
|
def from_processor(cls, processor):
|
|
278
223
|
"""Builds a model from a :class:`LoopStructural.modelling.input.ProcessInputData` object
|
|
@@ -464,12 +409,6 @@ class GeologicalModel:
|
|
|
464
409
|
"""
|
|
465
410
|
return [f.name for f in self.faults]
|
|
466
411
|
|
|
467
|
-
def check_inialisation(self):
|
|
468
|
-
if self.data is None:
|
|
469
|
-
logger.error("Data not associated with GeologicalModel. Run set_data")
|
|
470
|
-
return False
|
|
471
|
-
if self.data.shape[0] > 0:
|
|
472
|
-
return True
|
|
473
412
|
|
|
474
413
|
def to_file(self, file):
|
|
475
414
|
"""Save a model to a pickle file requires dill
|
|
@@ -543,14 +482,14 @@ class GeologicalModel:
|
|
|
543
482
|
----------
|
|
544
483
|
data : pandas data frame
|
|
545
484
|
with column headers corresponding to the
|
|
546
|
-
|
|
485
|
+
feature_name, X, Y, Z, nx, ny, nz, val, strike, dip, dip_dir, plunge,
|
|
547
486
|
plunge_dir, azimuth
|
|
548
487
|
|
|
549
488
|
Returns
|
|
550
489
|
-------
|
|
551
490
|
Note
|
|
552
491
|
----
|
|
553
|
-
|
|
492
|
+
feature_name can be any unique identifier for the feature the data point
|
|
554
493
|
'eg' 'S0', 'S2', 'F1_axis'
|
|
555
494
|
it is then used by the create functions to get the correct data
|
|
556
495
|
"""
|
|
@@ -565,48 +504,12 @@ class GeologicalModel:
|
|
|
565
504
|
raise BaseException("Cannot load data")
|
|
566
505
|
logger.info(f"Adding data to GeologicalModel with {len(data)} data points")
|
|
567
506
|
self._data = data.copy()
|
|
507
|
+
# self._data[['X','Y','Z']] = self.bounding_box.project(self._data[['X','Y','Z']].to_numpy())
|
|
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
|
"""
|
|
@@ -629,32 +532,40 @@ class GeologicalModel:
|
|
|
629
532
|
}
|
|
630
533
|
|
|
631
534
|
"""
|
|
535
|
+
self.stratigraphic_column.clear()
|
|
632
536
|
# if the colour for a unit hasn't been specified we can just sample from
|
|
633
537
|
# a colour map e.g. tab20
|
|
634
538
|
logger.info("Adding stratigraphic column to model")
|
|
635
|
-
|
|
636
|
-
|
|
539
|
+
DeprecationWarning(
|
|
540
|
+
"set_stratigraphic_column is deprecated, use model.stratigraphic_column.add_units instead"
|
|
541
|
+
)
|
|
637
542
|
for g in stratigraphic_column.keys():
|
|
638
543
|
for u in stratigraphic_column[g].keys():
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
544
|
+
thickness = 0
|
|
545
|
+
if "min" in stratigraphic_column[g][u] and "max" in stratigraphic_column[g][u]:
|
|
546
|
+
min_val = stratigraphic_column[g][u]["min"]
|
|
547
|
+
max_val = stratigraphic_column[g][u].get("max", None)
|
|
548
|
+
thickness = max_val - min_val if max_val is not None else None
|
|
549
|
+
logger.warning(
|
|
550
|
+
f"""
|
|
551
|
+
model.stratigraphic_column.add_unit({u},
|
|
552
|
+
colour={stratigraphic_column[g][u].get("colour", None)},
|
|
553
|
+
thickness={thickness})"""
|
|
554
|
+
)
|
|
555
|
+
self.stratigraphic_column.add_unit(
|
|
556
|
+
u,
|
|
557
|
+
colour=stratigraphic_column[g][u].get("colour", None),
|
|
558
|
+
thickness=thickness,
|
|
559
|
+
)
|
|
560
|
+
self.stratigraphic_column.add_unconformity(
|
|
561
|
+
name=''.join([g, 'unconformity']),
|
|
562
|
+
)
|
|
654
563
|
|
|
655
564
|
def create_and_add_foliation(
|
|
656
565
|
self,
|
|
657
|
-
|
|
566
|
+
series_surface_name: str,
|
|
567
|
+
*,
|
|
568
|
+
series_surface_data: pd.DataFrame = None,
|
|
658
569
|
interpolatortype: str = "FDI",
|
|
659
570
|
nelements: int = 1000,
|
|
660
571
|
tol=None,
|
|
@@ -664,8 +575,18 @@ class GeologicalModel:
|
|
|
664
575
|
"""
|
|
665
576
|
Parameters
|
|
666
577
|
----------
|
|
667
|
-
|
|
578
|
+
series_surface_name : string
|
|
668
579
|
corresponding to the feature_name in the data
|
|
580
|
+
series_surface_data : pd.DataFrame, optional
|
|
581
|
+
data frame containing the surface data
|
|
582
|
+
interpolatortype : str
|
|
583
|
+
the type of interpolator to use, default is 'FDI'
|
|
584
|
+
nelements : int
|
|
585
|
+
the number of elements to use in the series surface
|
|
586
|
+
tol : float, optional
|
|
587
|
+
tolerance for the solver, if not specified uses the model default
|
|
588
|
+
faults : list, optional
|
|
589
|
+
list of faults to be used in the series surface, if not specified uses the model faults
|
|
669
590
|
kwargs
|
|
670
591
|
|
|
671
592
|
Returns
|
|
@@ -685,9 +606,7 @@ class GeologicalModel:
|
|
|
685
606
|
An interpolator will be chosen by calling :meth:`LoopStructural.GeologicalModel.get_interpolator`
|
|
686
607
|
|
|
687
608
|
"""
|
|
688
|
-
|
|
689
|
-
logger.warning(f"{series_surface_data} not added, model not initialised")
|
|
690
|
-
return
|
|
609
|
+
|
|
691
610
|
# if tol is not specified use the model default
|
|
692
611
|
if tol is None:
|
|
693
612
|
tol = self.tol
|
|
@@ -696,16 +615,18 @@ class GeologicalModel:
|
|
|
696
615
|
bounding_box=self.bounding_box,
|
|
697
616
|
interpolatortype=interpolatortype,
|
|
698
617
|
nelements=nelements,
|
|
699
|
-
name=
|
|
618
|
+
name=series_surface_name,
|
|
700
619
|
model=self,
|
|
701
620
|
**kwargs,
|
|
702
621
|
)
|
|
703
622
|
# add data
|
|
704
|
-
|
|
705
|
-
|
|
623
|
+
if series_surface_data is None:
|
|
624
|
+
series_surface_data = self.data.loc[self.data["feature_name"] == series_surface_name]
|
|
625
|
+
|
|
626
|
+
if series_surface_data.shape[0] == 0:
|
|
706
627
|
logger.warning("No data for {series_surface_data}, skipping")
|
|
707
628
|
return
|
|
708
|
-
series_builder.add_data_from_data_frame(
|
|
629
|
+
series_builder.add_data_from_data_frame(self.prepare_data(series_surface_data))
|
|
709
630
|
self._add_faults(series_builder, features=faults)
|
|
710
631
|
|
|
711
632
|
# build feature
|
|
@@ -721,7 +642,9 @@ class GeologicalModel:
|
|
|
721
642
|
|
|
722
643
|
def create_and_add_fold_frame(
|
|
723
644
|
self,
|
|
724
|
-
|
|
645
|
+
fold_frame_name: str,
|
|
646
|
+
*,
|
|
647
|
+
fold_frame_data=None,
|
|
725
648
|
interpolatortype="FDI",
|
|
726
649
|
nelements=1000,
|
|
727
650
|
tol=None,
|
|
@@ -731,18 +654,31 @@ class GeologicalModel:
|
|
|
731
654
|
"""
|
|
732
655
|
Parameters
|
|
733
656
|
----------
|
|
734
|
-
|
|
657
|
+
fold_frame_name : string
|
|
735
658
|
unique string in feature_name column
|
|
659
|
+
fold_frame_data : pandas data frame
|
|
660
|
+
if not specified uses the model data
|
|
661
|
+
interpolatortype : str
|
|
662
|
+
the type of interpolator to use, default is 'FDI'
|
|
663
|
+
nelements : int
|
|
664
|
+
the number of elements to use in the fold frame
|
|
665
|
+
tol : float, optional
|
|
666
|
+
tolerance for the solver
|
|
667
|
+
buffer : float
|
|
668
|
+
buffer to add to the bounding box of the fold frame
|
|
669
|
+
**kwargs : dict
|
|
670
|
+
additional parameters to be passed to the
|
|
671
|
+
:class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
|
|
672
|
+
and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
|
|
673
|
+
and the interpolator, such as `domain` or `tol`
|
|
736
674
|
|
|
737
|
-
kwargs
|
|
738
675
|
|
|
739
676
|
Returns
|
|
740
677
|
-------
|
|
741
678
|
fold_frame : FoldFrame
|
|
742
679
|
the created fold frame
|
|
743
680
|
"""
|
|
744
|
-
|
|
745
|
-
return False
|
|
681
|
+
|
|
746
682
|
if tol is None:
|
|
747
683
|
tol = self.tol
|
|
748
684
|
|
|
@@ -751,15 +687,19 @@ class GeologicalModel:
|
|
|
751
687
|
fold_frame_builder = StructuralFrameBuilder(
|
|
752
688
|
interpolatortype=interpolatortype,
|
|
753
689
|
bounding_box=self.bounding_box.with_buffer(buffer),
|
|
754
|
-
name=
|
|
690
|
+
name=fold_frame_name,
|
|
755
691
|
frame=FoldFrame,
|
|
756
692
|
nelements=nelements,
|
|
757
693
|
model=self,
|
|
758
694
|
**kwargs,
|
|
759
695
|
)
|
|
760
696
|
# add data
|
|
761
|
-
fold_frame_data
|
|
762
|
-
|
|
697
|
+
if fold_frame_data is None:
|
|
698
|
+
fold_frame_data = self.data.loc[self.data["feature_name"] == fold_frame_name]
|
|
699
|
+
if fold_frame_data.shape[0] == 0:
|
|
700
|
+
logger.warning(f"No data for {fold_frame_name}, skipping")
|
|
701
|
+
return
|
|
702
|
+
fold_frame_builder.add_data_from_data_frame(self.prepare_data(fold_frame_data))
|
|
763
703
|
self._add_faults(fold_frame_builder[0])
|
|
764
704
|
self._add_faults(fold_frame_builder[1])
|
|
765
705
|
self._add_faults(fold_frame_builder[2])
|
|
@@ -775,7 +715,9 @@ class GeologicalModel:
|
|
|
775
715
|
|
|
776
716
|
def create_and_add_folded_foliation(
|
|
777
717
|
self,
|
|
778
|
-
|
|
718
|
+
foliation_name,
|
|
719
|
+
*,
|
|
720
|
+
foliation_data=None,
|
|
779
721
|
interpolatortype="DFI",
|
|
780
722
|
nelements=10000,
|
|
781
723
|
buffer=0.1,
|
|
@@ -812,8 +754,7 @@ class GeologicalModel:
|
|
|
812
754
|
:class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
|
|
813
755
|
|
|
814
756
|
"""
|
|
815
|
-
|
|
816
|
-
return False
|
|
757
|
+
|
|
817
758
|
if tol is None:
|
|
818
759
|
tol = self.tol
|
|
819
760
|
|
|
@@ -832,15 +773,18 @@ class GeologicalModel:
|
|
|
832
773
|
bounding_box=self.bounding_box.with_buffer(buffer),
|
|
833
774
|
nelements=nelements,
|
|
834
775
|
fold=fold,
|
|
835
|
-
name=
|
|
776
|
+
name=foliation_name,
|
|
836
777
|
svario=svario,
|
|
837
778
|
model=self,
|
|
838
779
|
**kwargs,
|
|
839
780
|
)
|
|
781
|
+
if foliation_data is None:
|
|
782
|
+
foliation_data = self.data.loc[self.data["feature_name"] == foliation_name]
|
|
783
|
+
if foliation_data.shape[0] == 0:
|
|
784
|
+
logger.warning(f"No data for {foliation_name}, skipping")
|
|
785
|
+
return
|
|
786
|
+
series_builder.add_data_from_data_frame(self.prepare_data(foliation_data))
|
|
840
787
|
|
|
841
|
-
series_builder.add_data_from_data_frame(
|
|
842
|
-
self.data[self.data["feature_name"] == foliation_data]
|
|
843
|
-
)
|
|
844
788
|
self._add_faults(series_builder)
|
|
845
789
|
# series_builder.add_data_to_interpolator(True)
|
|
846
790
|
# build feature
|
|
@@ -858,7 +802,9 @@ class GeologicalModel:
|
|
|
858
802
|
|
|
859
803
|
def create_and_add_folded_fold_frame(
|
|
860
804
|
self,
|
|
861
|
-
|
|
805
|
+
fold_frame_name: str,
|
|
806
|
+
*,
|
|
807
|
+
fold_frame_data: Optional[pd.DataFrame] = None,
|
|
862
808
|
interpolatortype="FDI",
|
|
863
809
|
nelements=10000,
|
|
864
810
|
fold_frame=None,
|
|
@@ -869,14 +815,22 @@ class GeologicalModel:
|
|
|
869
815
|
|
|
870
816
|
Parameters
|
|
871
817
|
----------
|
|
872
|
-
|
|
818
|
+
fold_frame_name : string
|
|
873
819
|
name of the feature to be added
|
|
874
|
-
|
|
820
|
+
fold_frame_data : pandas data frame, optional
|
|
821
|
+
data frame containing the fold frame data, if not specified uses the model data
|
|
822
|
+
interpolatortype : str
|
|
823
|
+
the type of interpolator to use, default is 'FDI' (unused) 5/6/2025
|
|
875
824
|
fold_frame : StructuralFrame, optional
|
|
876
825
|
the fold frame for the fold if not specified uses last feature added
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
826
|
+
nelements : int
|
|
827
|
+
the number of elements to use in the fold frame
|
|
828
|
+
tol : float, optional
|
|
829
|
+
tolerance for the solver, if not specified uses the model default
|
|
830
|
+
**kwargs : dict
|
|
831
|
+
additional parameters to be passed to the
|
|
832
|
+
:class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
|
|
833
|
+
and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
|
|
880
834
|
|
|
881
835
|
Returns
|
|
882
836
|
-------
|
|
@@ -897,8 +851,7 @@ class GeologicalModel:
|
|
|
897
851
|
see :class:`LoopStructural.modelling.features.fold.FoldEvent`,
|
|
898
852
|
:class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
|
|
899
853
|
"""
|
|
900
|
-
|
|
901
|
-
return False
|
|
854
|
+
|
|
902
855
|
if tol is None:
|
|
903
856
|
tol = self.tol
|
|
904
857
|
|
|
@@ -917,15 +870,15 @@ class GeologicalModel:
|
|
|
917
870
|
interpolatortype=interpolatortypes,
|
|
918
871
|
bounding_box=self.bounding_box.with_buffer(kwargs.get("buffer", 0.1)),
|
|
919
872
|
nelements=[nelements, nelements, nelements],
|
|
920
|
-
name=
|
|
873
|
+
name=fold_frame_name,
|
|
921
874
|
fold=fold,
|
|
922
875
|
frame=FoldFrame,
|
|
923
876
|
model=self,
|
|
924
877
|
**kwargs,
|
|
925
878
|
)
|
|
926
|
-
|
|
927
|
-
self.data[self.data["feature_name"] ==
|
|
928
|
-
)
|
|
879
|
+
if fold_frame_data is None:
|
|
880
|
+
fold_frame_data = self.data[self.data["feature_name"] == fold_frame_name]
|
|
881
|
+
fold_frame_builder.add_data_from_data_frame(self.prepare_data(fold_frame_data))
|
|
929
882
|
|
|
930
883
|
for i in range(3):
|
|
931
884
|
self._add_faults(fold_frame_builder[i])
|
|
@@ -947,6 +900,7 @@ class GeologicalModel:
|
|
|
947
900
|
self,
|
|
948
901
|
intrusion_name,
|
|
949
902
|
intrusion_frame_name,
|
|
903
|
+
*,
|
|
950
904
|
intrusion_frame_parameters={},
|
|
951
905
|
intrusion_lateral_extent_model=None,
|
|
952
906
|
intrusion_vertical_extent_model=None,
|
|
@@ -1224,7 +1178,7 @@ class GeologicalModel:
|
|
|
1224
1178
|
return uc_feature
|
|
1225
1179
|
|
|
1226
1180
|
def create_and_add_domain_fault(
|
|
1227
|
-
self, fault_surface_data, nelements=10000, interpolatortype="FDI", **kwargs
|
|
1181
|
+
self, fault_surface_data, *, nelements=10000, interpolatortype="FDI", **kwargs
|
|
1228
1182
|
):
|
|
1229
1183
|
"""
|
|
1230
1184
|
Parameters
|
|
@@ -1252,7 +1206,7 @@ class GeologicalModel:
|
|
|
1252
1206
|
)
|
|
1253
1207
|
|
|
1254
1208
|
# add data
|
|
1255
|
-
unconformity_data = self.data[self.data["feature_name"] == fault_surface_data]
|
|
1209
|
+
unconformity_data = self.data.loc[self.data["feature_name"] == fault_surface_data]
|
|
1256
1210
|
|
|
1257
1211
|
domain_fault_feature_builder.add_data_from_data_frame(unconformity_data)
|
|
1258
1212
|
# look through existing features if there is a fault before an
|
|
@@ -1275,8 +1229,10 @@ class GeologicalModel:
|
|
|
1275
1229
|
|
|
1276
1230
|
def create_and_add_fault(
|
|
1277
1231
|
self,
|
|
1278
|
-
|
|
1279
|
-
displacement,
|
|
1232
|
+
fault_name: str,
|
|
1233
|
+
displacement: float,
|
|
1234
|
+
*,
|
|
1235
|
+
fault_data: Optional[pd.DataFrame] = None,
|
|
1280
1236
|
interpolatortype="FDI",
|
|
1281
1237
|
tol=None,
|
|
1282
1238
|
fault_slip_vector=None,
|
|
@@ -1299,9 +1255,12 @@ class GeologicalModel:
|
|
|
1299
1255
|
"""
|
|
1300
1256
|
Parameters
|
|
1301
1257
|
----------
|
|
1302
|
-
|
|
1258
|
+
fault_name : string
|
|
1303
1259
|
name of the fault surface data in the dataframe
|
|
1304
1260
|
displacement : displacement magnitude
|
|
1261
|
+
displacement magnitude of the fault, in model units
|
|
1262
|
+
fault_data : pd.DataFrame, optional
|
|
1263
|
+
data frame containing the fault data, if not specified uses the model data
|
|
1305
1264
|
major_axis : [type], optional
|
|
1306
1265
|
[description], by default None
|
|
1307
1266
|
minor_axis : [type], optional
|
|
@@ -1328,7 +1287,7 @@ class GeologicalModel:
|
|
|
1328
1287
|
if "fault_vectical_radius" in kwargs and intermediate_axis is None:
|
|
1329
1288
|
intermediate_axis = kwargs["fault_vectical_radius"]
|
|
1330
1289
|
|
|
1331
|
-
logger.info(f'Creating fault "{
|
|
1290
|
+
logger.info(f'Creating fault "{fault_name}"')
|
|
1332
1291
|
logger.info(f"Displacement: {displacement}")
|
|
1333
1292
|
logger.info(f"Tolerance: {tol}")
|
|
1334
1293
|
logger.info(f"Fault function: {faultfunction}")
|
|
@@ -1353,35 +1312,39 @@ class GeologicalModel:
|
|
|
1353
1312
|
# tol *= 0.1*minor_axis
|
|
1354
1313
|
|
|
1355
1314
|
if displacement == 0:
|
|
1356
|
-
logger.warning(f"{
|
|
1315
|
+
logger.warning(f"{fault_name} displacement is 0")
|
|
1357
1316
|
|
|
1358
1317
|
if "data_region" in kwargs:
|
|
1359
1318
|
kwargs.pop("data_region")
|
|
1360
1319
|
logger.error("kwarg data_region currently not supported, disabling")
|
|
1361
|
-
displacement_scaled = displacement
|
|
1320
|
+
displacement_scaled = displacement
|
|
1362
1321
|
fault_frame_builder = FaultBuilder(
|
|
1363
1322
|
interpolatortype,
|
|
1364
1323
|
bounding_box=self.bounding_box,
|
|
1365
1324
|
nelements=kwargs.pop("nelements", 1e4),
|
|
1366
|
-
name=
|
|
1325
|
+
name=fault_name,
|
|
1367
1326
|
model=self,
|
|
1368
1327
|
**kwargs,
|
|
1369
1328
|
)
|
|
1370
|
-
|
|
1329
|
+
if fault_data is None:
|
|
1330
|
+
fault_data = self.data.loc[self.data["feature_name"] == fault_name]
|
|
1331
|
+
if fault_data.shape[0] == 0:
|
|
1332
|
+
logger.warning(f"No data for {fault_name}, skipping")
|
|
1333
|
+
return
|
|
1334
|
+
|
|
1371
1335
|
self._add_faults(fault_frame_builder, features=faults)
|
|
1372
1336
|
# add data
|
|
1373
|
-
fault_frame_data = self.data.loc[self.data["feature_name"] == fault_surface_data].copy()
|
|
1374
1337
|
|
|
1375
1338
|
if fault_center is not None and ~np.isnan(fault_center).any():
|
|
1376
1339
|
fault_center = self.scale(fault_center, inplace=False)
|
|
1377
1340
|
if minor_axis:
|
|
1378
|
-
minor_axis = minor_axis
|
|
1341
|
+
minor_axis = minor_axis
|
|
1379
1342
|
if major_axis:
|
|
1380
|
-
major_axis = major_axis
|
|
1343
|
+
major_axis = major_axis
|
|
1381
1344
|
if intermediate_axis:
|
|
1382
|
-
intermediate_axis = intermediate_axis
|
|
1345
|
+
intermediate_axis = intermediate_axis
|
|
1383
1346
|
fault_frame_builder.create_data_from_geometry(
|
|
1384
|
-
fault_frame_data=
|
|
1347
|
+
fault_frame_data=self.prepare_data(fault_data),
|
|
1385
1348
|
fault_center=fault_center,
|
|
1386
1349
|
fault_normal_vector=fault_normal_vector,
|
|
1387
1350
|
fault_slip_vector=fault_slip_vector,
|
|
@@ -1418,7 +1381,7 @@ class GeologicalModel:
|
|
|
1418
1381
|
return fault
|
|
1419
1382
|
|
|
1420
1383
|
# TODO move rescale to bounding box/transformer
|
|
1421
|
-
def rescale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1384
|
+
def rescale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
|
|
1422
1385
|
"""
|
|
1423
1386
|
Convert from model scale to real world scale - in the future this
|
|
1424
1387
|
should also do transformations?
|
|
@@ -1434,14 +1397,12 @@ class GeologicalModel:
|
|
|
1434
1397
|
points : np.array((N,3),dtype=double)
|
|
1435
1398
|
|
|
1436
1399
|
"""
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
points += self.origin
|
|
1441
|
-
return points
|
|
1400
|
+
|
|
1401
|
+
return self.bounding_box.reproject(points, inplace=inplace)
|
|
1402
|
+
|
|
1442
1403
|
|
|
1443
1404
|
# TODO move scale to bounding box/transformer
|
|
1444
|
-
def scale(self, points: np.ndarray, inplace: bool = False) -> np.ndarray:
|
|
1405
|
+
def scale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
|
|
1445
1406
|
"""Take points in UTM coordinates and reproject
|
|
1446
1407
|
into scaled model space
|
|
1447
1408
|
|
|
@@ -1456,18 +1417,10 @@ class GeologicalModel:
|
|
|
1456
1417
|
points : np.a::rray((N,3),dtype=double)
|
|
1457
1418
|
|
|
1458
1419
|
"""
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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"):
|
|
1420
|
+
return self.bounding_box.project(np.array(points).astype(float), inplace=inplace)
|
|
1421
|
+
|
|
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(
|
|
@@ -1783,30 +1736,15 @@ class GeologicalModel:
|
|
|
1783
1736
|
units = []
|
|
1784
1737
|
if self.stratigraphic_column is None:
|
|
1785
1738
|
return []
|
|
1786
|
-
|
|
1787
|
-
|
|
1739
|
+
units = self.stratigraphic_column.get_isovalues()
|
|
1740
|
+
for name, u in units.items():
|
|
1741
|
+
if u['group'] not in self:
|
|
1742
|
+
logger.warning(f"Group {u['group']} not found in model")
|
|
1788
1743
|
continue
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
units.append(series)
|
|
1792
|
-
unit_table = pd.DataFrame(units)
|
|
1793
|
-
for u in unit_table['feature_name'].unique():
|
|
1794
|
-
|
|
1795
|
-
values = unit_table.loc[unit_table['feature_name'] == u, 'min' if bottoms else 'max']
|
|
1796
|
-
if 'name' not in unit_table.columns:
|
|
1797
|
-
unit_table['name'] = unit_table['feature_name']
|
|
1798
|
-
|
|
1799
|
-
names = unit_table[unit_table['feature_name'] == u]['name']
|
|
1800
|
-
values = values.loc[~np.logical_or(values == np.inf, values == -np.inf)]
|
|
1744
|
+
feature = self.get_feature_by_name(u['group'])
|
|
1745
|
+
|
|
1801
1746
|
surfaces.extend(
|
|
1802
|
-
|
|
1803
|
-
values.to_list(),
|
|
1804
|
-
self.bounding_box,
|
|
1805
|
-
name=names.loc[values.index].to_list(),
|
|
1806
|
-
colours=unit_table.loc[unit_table['feature_name'] == u, 'colour'].tolist()[
|
|
1807
|
-
1:
|
|
1808
|
-
], # we don't isosurface basement, no value
|
|
1809
|
-
)
|
|
1747
|
+
feature.surfaces([u['value']], self.bounding_box, name=name, colours=[u['colour']])
|
|
1810
1748
|
)
|
|
1811
1749
|
|
|
1812
1750
|
return surfaces
|