LoopStructural 1.6.15__py3-none-any.whl → 1.6.17__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.

@@ -6,7 +6,7 @@ from ...utils import getLogger
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
9
- from typing import List, Optional
9
+ from typing import List, Optional, Union, Dict
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,14 +61,11 @@ 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
-
64
+
65
65
 
66
66
  """
67
67
 
68
- def __init__(
69
- self,
70
- *args
71
- ):
68
+ def __init__(self, *args):
72
69
  """
73
70
  Parameters
74
71
  ----------
@@ -78,7 +75,7 @@ class GeologicalModel:
78
75
  the origin of the model
79
76
  maximum : np.array(3,dtype=doubles)
80
77
  the maximum of the model
81
-
78
+
82
79
  Examples
83
80
  --------
84
81
  Demo data
@@ -126,7 +123,7 @@ class GeologicalModel:
126
123
  self.feature_name_index = {}
127
124
  self._data = pd.DataFrame() # None
128
125
 
129
- self.stratigraphic_column = None
126
+ self._stratigraphic_column = StratigraphicColumn()
130
127
 
131
128
  self.tol = 1e-10 * np.max(self.bounding_box.maximum - self.bounding_box.origin)
132
129
  self._dtm = None
@@ -148,29 +145,6 @@ class GeologicalModel:
148
145
  # json["features"] = [f.to_json() for f in self.features]
149
146
  return json
150
147
 
151
- # @classmethod
152
- # def from_json(cls,json):
153
- # """
154
- # Create a geological model from a json string
155
-
156
- # Parameters
157
- # ----------
158
- # json : str
159
- # json string of the geological model
160
-
161
- # Returns
162
- # -------
163
- # model : GeologicalModel
164
- # a geological model
165
- # """
166
- # model = cls(json["model"]["origin"],json["model"]["maximum"],data=None)
167
- # model.stratigraphic_column = json["model"]["stratigraphic_column"]
168
- # model.nsteps = json["model"]["nsteps"]
169
- # model.data = pd.read_json(json["model"]["data"])
170
- # model.features = []
171
- # for feature in json["features"]:
172
- # model.features.append(GeologicalFeature.from_json(feature,model))
173
- # return model
174
148
  def __str__(self):
175
149
  return f"GeologicalModel with {len(self.features)} features"
176
150
 
@@ -211,6 +185,37 @@ class GeologicalModel:
211
185
  ['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
212
186
  ].astype(float)
213
187
  return data
188
+
189
+ if "type" in data:
190
+ logger.warning("'type' is deprecated replace with 'feature_name' \n")
191
+ data.rename(columns={"type": "feature_name"}, inplace=True)
192
+ if "feature_name" not in data:
193
+ logger.error("Data does not contain 'feature_name' column")
194
+ raise BaseException("Cannot load data")
195
+ for h in all_heading():
196
+ if h not in data:
197
+ data[h] = np.nan
198
+ if h == "w":
199
+ data[h] = 1.0
200
+ if h == "coord":
201
+ data[h] = 0
202
+ if h == "polarity":
203
+ data[h] = 1.0
204
+ # LS wants polarity as -1 or 1, change 0 to -1
205
+ data.loc[data["polarity"] == 0, "polarity"] = -1.0
206
+ data.loc[np.isnan(data["w"]), "w"] = 1.0
207
+ if "strike" in data and "dip" in data:
208
+ logger.info("Converting strike and dip to vectors")
209
+ mask = np.all(~np.isnan(data.loc[:, ["strike", "dip"]]), axis=1)
210
+ data.loc[mask, gradient_vec_names()] = (
211
+ strikedip2vector(data.loc[mask, "strike"], data.loc[mask, "dip"])
212
+ * data.loc[mask, "polarity"].to_numpy()[:, None]
213
+ )
214
+ data.drop(["strike", "dip"], axis=1, inplace=True)
215
+ data[['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']] = data[
216
+ ['X', 'Y', 'Z', 'val', 'nx', 'ny', 'nz', 'gx', 'gy', 'gz', 'tx', 'ty', 'tz']
217
+ ].astype(float)
218
+ return data
214
219
  @classmethod
215
220
  def from_processor(cls, processor):
216
221
  """Builds a model from a :class:`LoopStructural.modelling.input.ProcessInputData` object
@@ -402,13 +407,6 @@ class GeologicalModel:
402
407
  """
403
408
  return [f.name for f in self.faults]
404
409
 
405
- def check_inialisation(self):
406
- if self.data is None:
407
- logger.error("Data not associated with GeologicalModel. Run set_data")
408
- return False
409
- if self.data.shape[0] > 0:
410
- return True
411
-
412
410
  def to_file(self, file):
413
411
  """Save a model to a pickle file requires dill
414
412
 
@@ -505,11 +503,34 @@ class GeologicalModel:
505
503
  self._data = data.copy()
506
504
  # self._data[['X','Y','Z']] = self.bounding_box.project(self._data[['X','Y','Z']].to_numpy())
507
505
 
508
-
509
-
510
506
  def set_model_data(self, data):
511
507
  logger.warning("deprecated method. Model data can now be set using the data attribute")
512
508
  self.data = data.copy()
509
+ @property
510
+ def stratigraphic_column(self):
511
+ """Get the stratigraphic column of the model
512
+
513
+ Returns
514
+ -------
515
+ StratigraphicColumn
516
+ the stratigraphic column of the model
517
+ """
518
+ return self._stratigraphic_column
519
+ @stratigraphic_column.setter
520
+ def stratigraphic_column(self, stratigraphic_column: Union[StratigraphicColumn,Dict]):
521
+ """Set the stratigraphic column of the model
522
+
523
+ Parameters
524
+ ----------
525
+ stratigraphic_column : StratigraphicColumn
526
+ the stratigraphic column to set
527
+ """
528
+ if isinstance(stratigraphic_column, dict):
529
+ self.set_stratigraphic_column(stratigraphic_column)
530
+ return
531
+ elif not isinstance(stratigraphic_column, StratigraphicColumn):
532
+ raise ValueError("stratigraphic_column must be a StratigraphicColumn object")
533
+ self._stratigraphic_column = stratigraphic_column
513
534
 
514
535
  def set_stratigraphic_column(self, stratigraphic_column, cmap="tab20"):
515
536
  """
@@ -532,28 +553,34 @@ class GeologicalModel:
532
553
  }
533
554
 
534
555
  """
556
+ self.stratigraphic_column.clear()
535
557
  # if the colour for a unit hasn't been specified we can just sample from
536
558
  # a colour map e.g. tab20
537
559
  logger.info("Adding stratigraphic column to model")
538
- random_colour = True
539
- n_units = 0
560
+ DeprecationWarning(
561
+ "set_stratigraphic_column is deprecated, use model.stratigraphic_column.add_units instead"
562
+ )
540
563
  for g in stratigraphic_column.keys():
541
564
  for u in stratigraphic_column[g].keys():
542
- if "colour" in stratigraphic_column[g][u]:
543
- random_colour = False
544
- break
545
- n_units += 1
546
- if random_colour:
547
- import matplotlib.cm as cm
548
-
549
- cmap = cm.get_cmap(cmap, n_units)
550
- cmap_colours = cmap.colors
551
- ci = 0
552
- for g in stratigraphic_column.keys():
553
- for u in stratigraphic_column[g].keys():
554
- stratigraphic_column[g][u]["colour"] = cmap_colours[ci, :]
555
- ci += 1
556
- self.stratigraphic_column = stratigraphic_column
565
+ thickness = 0
566
+ if "min" in stratigraphic_column[g][u] and "max" in stratigraphic_column[g][u]:
567
+ min_val = stratigraphic_column[g][u]["min"]
568
+ max_val = stratigraphic_column[g][u].get("max", None)
569
+ thickness = max_val - min_val if max_val is not None else None
570
+ logger.warning(
571
+ f"""
572
+ model.stratigraphic_column.add_unit({u},
573
+ colour={stratigraphic_column[g][u].get("colour", None)},
574
+ thickness={thickness})"""
575
+ )
576
+ self.stratigraphic_column.add_unit(
577
+ u,
578
+ colour=stratigraphic_column[g][u].get("colour", None),
579
+ thickness=thickness,
580
+ )
581
+ self.stratigraphic_column.add_unconformity(
582
+ name=''.join([g, 'unconformity']),
583
+ )
557
584
 
558
585
  def create_and_add_foliation(
559
586
  self,
@@ -600,9 +627,7 @@ class GeologicalModel:
600
627
  An interpolator will be chosen by calling :meth:`LoopStructural.GeologicalModel.get_interpolator`
601
628
 
602
629
  """
603
- if not self.check_inialisation():
604
- logger.warning(f"{series_surface_data} not added, model not initialised")
605
- return
630
+
606
631
  # if tol is not specified use the model default
607
632
  if tol is None:
608
633
  tol = self.tol
@@ -638,7 +663,7 @@ class GeologicalModel:
638
663
 
639
664
  def create_and_add_fold_frame(
640
665
  self,
641
- fold_frame_name:str,
666
+ fold_frame_name: str,
642
667
  *,
643
668
  fold_frame_data=None,
644
669
  interpolatortype="FDI",
@@ -667,15 +692,14 @@ class GeologicalModel:
667
692
  :class:`LoopStructural.modelling.features.builders.StructuralFrameBuilder`
668
693
  and :meth:`LoopStructural.modelling.features.builders.StructuralFrameBuilder.setup`
669
694
  and the interpolator, such as `domain` or `tol`
670
-
695
+
671
696
 
672
697
  Returns
673
698
  -------
674
699
  fold_frame : FoldFrame
675
700
  the created fold frame
676
701
  """
677
- if not self.check_inialisation():
678
- return False
702
+
679
703
  if tol is None:
680
704
  tol = self.tol
681
705
 
@@ -751,8 +775,7 @@ class GeologicalModel:
751
775
  :class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
752
776
 
753
777
  """
754
- if not self.check_inialisation():
755
- return False
778
+
756
779
  if tol is None:
757
780
  tol = self.tol
758
781
 
@@ -781,11 +804,8 @@ class GeologicalModel:
781
804
  if foliation_data.shape[0] == 0:
782
805
  logger.warning(f"No data for {foliation_name}, skipping")
783
806
  return
784
- series_builder.add_data_from_data_frame(
785
- self.prepare_data(
786
- foliation_data
787
- )
788
- )
807
+ series_builder.add_data_from_data_frame(self.prepare_data(foliation_data))
808
+
789
809
  self._add_faults(series_builder)
790
810
  # series_builder.add_data_to_interpolator(True)
791
811
  # build feature
@@ -852,8 +872,7 @@ class GeologicalModel:
852
872
  see :class:`LoopStructural.modelling.features.fold.FoldEvent`,
853
873
  :class:`LoopStructural.modelling.features.builders.FoldedFeatureBuilder`
854
874
  """
855
- if not self.check_inialisation():
856
- return False
875
+
857
876
  if tol is None:
858
877
  tol = self.tol
859
878
 
@@ -1180,7 +1199,7 @@ class GeologicalModel:
1180
1199
  return uc_feature
1181
1200
 
1182
1201
  def create_and_add_domain_fault(
1183
- self, fault_surface_data,*, nelements=10000, interpolatortype="FDI", **kwargs
1202
+ self, fault_surface_data, *, nelements=10000, interpolatortype="FDI", **kwargs
1184
1203
  ):
1185
1204
  """
1186
1205
  Parameters
@@ -1234,7 +1253,7 @@ class GeologicalModel:
1234
1253
  fault_name: str,
1235
1254
  displacement: float,
1236
1255
  *,
1237
- fault_data:Optional[pd.DataFrame] = None,
1256
+ fault_data: Optional[pd.DataFrame] = None,
1238
1257
  interpolatortype="FDI",
1239
1258
  tol=None,
1240
1259
  fault_slip_vector=None,
@@ -1319,7 +1338,7 @@ class GeologicalModel:
1319
1338
  if "data_region" in kwargs:
1320
1339
  kwargs.pop("data_region")
1321
1340
  logger.error("kwarg data_region currently not supported, disabling")
1322
- displacement_scaled = displacement
1341
+ displacement_scaled = displacement
1323
1342
  fault_frame_builder = FaultBuilder(
1324
1343
  interpolatortype,
1325
1344
  bounding_box=self.bounding_box,
@@ -1340,11 +1359,11 @@ class GeologicalModel:
1340
1359
  if fault_center is not None and ~np.isnan(fault_center).any():
1341
1360
  fault_center = self.scale(fault_center, inplace=False)
1342
1361
  if minor_axis:
1343
- minor_axis = minor_axis
1362
+ minor_axis = minor_axis
1344
1363
  if major_axis:
1345
- major_axis = major_axis
1364
+ major_axis = major_axis
1346
1365
  if intermediate_axis:
1347
- intermediate_axis = intermediate_axis
1366
+ intermediate_axis = intermediate_axis
1348
1367
  fault_frame_builder.create_data_from_geometry(
1349
1368
  fault_frame_data=self.prepare_data(fault_data),
1350
1369
  fault_center=fault_center,
@@ -1400,7 +1419,7 @@ class GeologicalModel:
1400
1419
 
1401
1420
  """
1402
1421
 
1403
- return self.bounding_box.reproject(points,inplace=inplace)
1422
+ return self.bounding_box.reproject(points, inplace=inplace)
1404
1423
 
1405
1424
  # TODO move scale to bounding box/transformer
1406
1425
  def scale(self, points: np.ndarray, *, inplace: bool = False) -> np.ndarray:
@@ -1418,7 +1437,7 @@ class GeologicalModel:
1418
1437
  points : np.a::rray((N,3),dtype=double)
1419
1438
 
1420
1439
  """
1421
- return self.bounding_box.project(np.array(points).astype(float),inplace=inplace)
1440
+ return self.bounding_box.project(np.array(points).astype(float), inplace=inplace)
1422
1441
 
1423
1442
  def regular_grid(self, *, nsteps=None, shuffle=True, rescale=False, order="C"):
1424
1443
  """
@@ -1494,22 +1513,18 @@ class GeologicalModel:
1494
1513
  if self.stratigraphic_column is None:
1495
1514
  logger.warning("No stratigraphic column defined")
1496
1515
  return strat_id
1497
- for group in reversed(self.stratigraphic_column.keys()):
1498
- if group == "faults":
1499
- continue
1500
- feature_id = self.feature_name_index.get(group, -1)
1516
+
1517
+ s_id = 0
1518
+ for g in reversed(self.stratigraphic_column.get_groups()):
1519
+ feature_id = self.feature_name_index.get(g.name, -1)
1501
1520
  if feature_id >= 0:
1502
- feature = self.features[feature_id]
1503
- vals = feature.evaluate_value(xyz)
1504
- for series in self.stratigraphic_column[group].values():
1505
- strat_id[
1506
- np.logical_and(
1507
- vals < series.get("max", feature.max()),
1508
- vals > series.get("min", feature.min()),
1509
- )
1510
- ] = series["id"]
1521
+ vals = self.features[feature_id].evaluate_value(xyz)
1522
+ for u in g.units:
1523
+ strat_id[np.logical_and(vals < u.max, vals > u.min)] = s_id
1524
+ s_id += 1
1511
1525
  if feature_id == -1:
1512
- logger.error(f"Model does not contain {group}")
1526
+ logger.error(f"Model does not contain {g.name}")
1527
+
1513
1528
  return strat_id
1514
1529
 
1515
1530
  def evaluate_model_gradient(self, points: np.ndarray, *, scale: bool = True) -> np.ndarray:
@@ -1567,7 +1582,7 @@ class GeologicalModel:
1567
1582
  if f.type == FeatureType.FAULT:
1568
1583
  disp = f.displacementfeature.evaluate_value(points)
1569
1584
  vals[~np.isnan(disp)] += disp[~np.isnan(disp)]
1570
- return vals # convert from restoration magnutude to displacement
1585
+ return vals # convert from restoration magnutude to displacement
1571
1586
 
1572
1587
  def get_feature_by_name(self, feature_name) -> GeologicalFeature:
1573
1588
  """Returns a feature from the mode given a name
@@ -1736,30 +1751,15 @@ class GeologicalModel:
1736
1751
  units = []
1737
1752
  if self.stratigraphic_column is None:
1738
1753
  return []
1739
- for group in self.stratigraphic_column.keys():
1740
- if group == "faults":
1754
+ units = self.stratigraphic_column.get_isovalues()
1755
+ for name, u in units.items():
1756
+ if u['group'] not in self:
1757
+ logger.warning(f"Group {u['group']} not found in model")
1741
1758
  continue
1742
- for series in self.stratigraphic_column[group].values():
1743
- series['feature_name'] = group
1744
- units.append(series)
1745
- unit_table = pd.DataFrame(units)
1746
- for u in unit_table['feature_name'].unique():
1747
-
1748
- values = unit_table.loc[unit_table['feature_name'] == u, 'min' if bottoms else 'max']
1749
- if 'name' not in unit_table.columns:
1750
- unit_table['name'] = unit_table['feature_name']
1751
-
1752
- names = unit_table[unit_table['feature_name'] == u]['name']
1753
- values = values.loc[~np.logical_or(values == np.inf, values == -np.inf)]
1759
+ feature = self.get_feature_by_name(u['group'])
1760
+
1754
1761
  surfaces.extend(
1755
- self.get_feature_by_name(u).surfaces(
1756
- values.to_list(),
1757
- self.bounding_box,
1758
- name=names.loc[values.index].to_list(),
1759
- colours=unit_table.loc[unit_table['feature_name'] == u, 'colour'].tolist()[
1760
- 1:
1761
- ], # we don't isosurface basement, no value
1762
- )
1762
+ feature.surfaces([u['value']], self.bounding_box, name=name, colours=[u['colour']])
1763
1763
  )
1764
1764
 
1765
1765
  return surfaces