LoopStructural 1.8.0__tar.gz → 1.8.2__tar.gz

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.
Files changed (128) hide show
  1. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/_model_exporter.py +21 -11
  2. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/_model_relationships.py +22 -6
  3. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/stratigraphic_column.py +90 -45
  4. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_base_geological_feature.py +10 -1
  5. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural.egg-info/PKG-INFO +1 -1
  6. {loopstructural-1.8.0 → loopstructural-1.8.2}/PKG-INFO +1 -1
  7. {loopstructural-1.8.0 → loopstructural-1.8.2}/pyproject.toml +1 -1
  8. {loopstructural-1.8.0 → loopstructural-1.8.2}/LICENSE +0 -0
  9. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/__init__.py +0 -0
  10. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/__init__.py +0 -0
  11. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/_base.py +0 -0
  12. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/_example_models.py +0 -0
  13. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/claudius.csv +0 -0
  14. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/claudiusbb.txt +0 -0
  15. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/duplex.csv +0 -0
  16. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/duplexbb.txt +0 -0
  17. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/fault_trace/fault_trace.cpg +0 -0
  18. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  19. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/fault_trace/fault_trace.prj +0 -0
  20. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  21. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  22. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/bbox.csv +0 -0
  23. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/contacts.csv +0 -0
  24. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +0 -0
  25. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/fault_edges.txt +0 -0
  26. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/fault_locations.csv +0 -0
  27. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +0 -0
  28. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +0 -0
  29. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +0 -0
  30. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +0 -0
  31. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/intrusion.csv +0 -0
  32. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/intrusionbb.txt +0 -0
  33. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/onefoldbb.txt +0 -0
  34. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/onefolddata.csv +0 -0
  35. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/refolded_bb.txt +0 -0
  36. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/refolded_fold.csv +0 -0
  37. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datasets/data/tabular_intrusion.csv +0 -0
  38. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/datatypes/__init__.py +0 -0
  39. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/export/exporters.py +0 -0
  40. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/export/file_formats.py +0 -0
  41. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/export/geoh5.py +0 -0
  42. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/export/gocad.py +0 -0
  43. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/export/omf_wrapper.py +0 -0
  44. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/geometry/__init__.py +0 -0
  45. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/geometry/_structured_grid.py +0 -0
  46. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/__init__.py +0 -0
  47. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_api.py +0 -0
  48. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_builders.py +0 -0
  49. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_constant_norm.py +0 -0
  50. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_cython/__init__.py +0 -0
  51. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_discrete_fold_interpolator.py +0 -0
  52. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/interpolators/_interpolatortype.py +0 -0
  53. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/__init__.py +0 -0
  54. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/__init__.py +0 -0
  55. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/_feature_registry.py +0 -0
  56. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/_model_feature_factory.py +0 -0
  57. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/_model_serializer.py +0 -0
  58. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/fault_topology.py +0 -0
  59. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/core/geological_model.py +0 -0
  60. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/__init__.py +0 -0
  61. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_analytical_feature.py +0 -0
  62. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_cross_product_geological_feature.py +0 -0
  63. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_feature_converters.py +0 -0
  64. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_geological_feature.py +0 -0
  65. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_lambda_geological_feature.py +0 -0
  66. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_projected_vector_feature.py +0 -0
  67. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_region.py +0 -0
  68. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_structural_frame.py +0 -0
  69. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/_unconformity_feature.py +0 -0
  70. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/__init__.py +0 -0
  71. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_analytical_fold_builder.py +0 -0
  72. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_base_builder.py +0 -0
  73. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_fault_builder.py +0 -0
  74. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_folded_feature_builder.py +0 -0
  75. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_geological_feature_builder.py +0 -0
  76. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/builders/_structural_frame_builder.py +0 -0
  77. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fault/__init__.py +0 -0
  78. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fault/_fault_function.py +0 -0
  79. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fault/_fault_function_feature.py +0 -0
  80. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fault/_fault_segment.py +0 -0
  81. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/__init__.py +0 -0
  82. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/_fold.py +0 -0
  83. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +0 -0
  84. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/_foldframe.py +0 -0
  85. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/_svariogram.py +0 -0
  86. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/fold_function/__init__.py +0 -0
  87. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/fold_function/_base_fold_rotation_angle.py +0 -0
  88. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/fold_function/_fourier_series_fold_rotation_angle.py +0 -0
  89. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/fold_function/_lambda_fold_rotation_angle.py +0 -0
  90. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/features/fold/fold_function/_trigo_fold_rotation_angle.py +0 -0
  91. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/input/__init__.py +0 -0
  92. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/input/fault_network.py +0 -0
  93. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/input/map2loop_processor.py +0 -0
  94. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/input/process_data.py +0 -0
  95. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/input/project_file.py +0 -0
  96. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/__init__.py +0 -0
  97. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/geom_conceptual_models.py +0 -0
  98. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/geometric_scaling_functions.py +0 -0
  99. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/intrusion_builder.py +0 -0
  100. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/intrusion_feature.py +0 -0
  101. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/intrusion_frame.py +0 -0
  102. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/modelling/intrusions/intrusion_frame_builder.py +0 -0
  103. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/__init__.py +0 -0
  104. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/_api_registry.py +0 -0
  105. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/_surface.py +0 -0
  106. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/_transformation.py +0 -0
  107. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/colours.py +0 -0
  108. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/config.py +0 -0
  109. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/dtm_creator.py +0 -0
  110. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/exceptions.py +0 -0
  111. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/features.py +0 -0
  112. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/helper.py +0 -0
  113. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/json_encoder.py +0 -0
  114. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/linalg.py +0 -0
  115. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/logging.py +0 -0
  116. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/maths.py +0 -0
  117. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/observer.py +0 -0
  118. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/regions.py +0 -0
  119. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/typing.py +0 -0
  120. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/utils/utils.py +0 -0
  121. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural/visualisation/__init__.py +0 -0
  122. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural.egg-info/SOURCES.txt +0 -0
  123. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural.egg-info/dependency_links.txt +0 -0
  124. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural.egg-info/requires.txt +0 -0
  125. {loopstructural-1.8.0 → loopstructural-1.8.2}/LoopStructural.egg-info/top_level.txt +0 -0
  126. {loopstructural-1.8.0 → loopstructural-1.8.2}/README.md +0 -0
  127. {loopstructural-1.8.0 → loopstructural-1.8.2}/setup.cfg +0 -0
  128. {loopstructural-1.8.0 → loopstructural-1.8.2}/setup.py +0 -0
@@ -8,17 +8,23 @@ of the CI-checked stable API surface -- and delegate to the staticmethods
8
8
  here.
9
9
  """
10
10
 
11
+ from __future__ import annotations
12
+
11
13
  import pathlib
14
+ from typing import TYPE_CHECKING
12
15
 
13
16
  from ...geometry import StructuredGrid
14
- from ...utils import getLogger
17
+ from ...utils import getLogger, surface_list
18
+
19
+ if TYPE_CHECKING:
20
+ from .geological_model import GeologicalModel
15
21
 
16
22
  logger = getLogger(__name__)
17
23
 
18
24
 
19
25
  class ModelExporter:
20
26
  @staticmethod
21
- def get_fault_surfaces(model, faults=None):
27
+ def get_fault_surfaces(model: GeologicalModel, faults: list[str] | None = None) -> surface_list:
22
28
  if faults is None:
23
29
  faults = []
24
30
  surfaces = []
@@ -30,7 +36,9 @@ class ModelExporter:
30
36
  return surfaces
31
37
 
32
38
  @staticmethod
33
- def get_stratigraphic_surfaces(model, units=None, bottoms=True):
39
+ def get_stratigraphic_surfaces(
40
+ model: GeologicalModel, units: list[str] | None = None, bottoms: bool = True
41
+ ) -> surface_list:
34
42
  if units is None:
35
43
  units = []
36
44
  ## TODO change the stratigraphic column to its own class and have methods to get the relevant surfaces
@@ -38,7 +46,7 @@ class ModelExporter:
38
46
  units = []
39
47
  if model.stratigraphic_column is None:
40
48
  return []
41
- units = model.stratigraphic_column.get_isovalues()
49
+ units = model.stratigraphic_column.get_isovalues(where='bottom' if bottoms else 'top')
42
50
  units_for_group = {}
43
51
  for name, u in units.items():
44
52
  if u['group'] not in model:
@@ -60,7 +68,9 @@ class ModelExporter:
60
68
  return surfaces
61
69
 
62
70
  @staticmethod
63
- def get_block_model(model, name='block model'):
71
+ def get_block_model(
72
+ model: GeologicalModel, name: str = 'block model'
73
+ ) -> tuple[StructuredGrid, list]:
64
74
  # NOTE: bounding_box.structured_grid() returns loop_common's
65
75
  # interpolation-support StructuredGrid (no properties dict); use
66
76
  # LoopStructural's own geometry StructuredGrid for storing values.
@@ -78,14 +88,14 @@ class ModelExporter:
78
88
 
79
89
  @staticmethod
80
90
  def save(
81
- model,
91
+ model: GeologicalModel,
82
92
  filename: str,
83
93
  block_model: bool = True,
84
- stratigraphic_surfaces=True,
85
- fault_surfaces=True,
86
- stratigraphic_data=True,
87
- fault_data=True,
88
- ):
94
+ stratigraphic_surfaces: bool = True,
95
+ fault_surfaces: bool = True,
96
+ stratigraphic_data: bool = True,
97
+ fault_data: bool = True,
98
+ ) -> None:
89
99
  path = pathlib.Path(filename)
90
100
  extension = path.suffix
91
101
  parent = path.parent
@@ -52,8 +52,14 @@ class FeatureRelationshipManager:
52
52
  for f in reversed(model.features):
53
53
  if f.name == feature.name:
54
54
  continue
55
- if f.type == "domain_fault":
56
- feature.add_region(lambda pos, fault=f: fault.evaluate_value(pos) < 0)
55
+ if f.type == FeatureType.DOMAINFAULT:
56
+ # A proper Feature-like region (name/parent), not a bare
57
+ # lambda -- code that walks `self.regions` expecting
58
+ # Feature-like objects (e.g. BaseFeature.surfaces) would
59
+ # otherwise raise AttributeError on this region and
60
+ # silently produce no surfaces for anything a domain fault
61
+ # crops.
62
+ feature.add_region(UnconformityFeature(f, 0, sign=True))
57
63
  break
58
64
 
59
65
  @staticmethod
@@ -70,10 +76,11 @@ class FeatureRelationshipManager:
70
76
  domain_fault : GeologicalFeatureBuilder
71
77
  the feature being added to the model where domain faults should be added
72
78
  """
79
+ region = UnconformityFeature(domain_fault, 0, sign=False)
73
80
  for f in reversed(model.features):
74
81
  if f.name == domain_fault.name:
75
82
  continue
76
- f.add_region(lambda pos: domain_fault.evaluate_value(pos) > 0)
83
+ f.add_region(region)
77
84
  if f.type == FeatureType.UNCONFORMITY:
78
85
  break
79
86
 
@@ -129,8 +136,13 @@ class FeatureRelationshipManager:
129
136
  uc_feature = UnconformityFeature(feature, value)
130
137
  feature.add_region(uc_feature.inverse())
131
138
  for f in reversed(model.features):
132
- if f.type == FeatureType.UNCONFORMITY:
133
- logger.debug(f"Reached unconformity {f.name}")
139
+ if f.type == FeatureType.UNCONFORMITY or f.type == FeatureType.DOMAINFAULT:
140
+ # A domain fault is itself a boundary that already separates
141
+ # everything below it from what's built afterwards (see
142
+ # add_domain_fault_below/above) -- so it's a stopping point
143
+ # here too, the same as an unconformity, rather than
144
+ # something this unrelated unconformity should crop.
145
+ logger.debug(f"Reached unconformity/domain fault {f.name}")
134
146
  break
135
147
  logger.debug(f"Adding {uc_feature.name} as unconformity to {f.name}")
136
148
  if f.type == FeatureType.FAULT or f.type == FeatureType.INACTIVEFAULT:
@@ -165,7 +177,11 @@ class FeatureRelationshipManager:
165
177
  uc_feature = UnconformityFeature(feature, value, False, onlap=True)
166
178
  feature.add_region(uc_feature.inverse())
167
179
  for f in reversed(model.features):
168
- if f.type in (FeatureType.UNCONFORMITY, FeatureType.ONLAPUNCONFORMITY):
180
+ if f.type in (
181
+ FeatureType.UNCONFORMITY,
182
+ FeatureType.ONLAPUNCONFORMITY,
183
+ FeatureType.DOMAINFAULT,
184
+ ):
169
185
  logger.debug(f"Reached unconformity {f.name}")
170
186
  break
171
187
  if f.type == FeatureType.FAULT or f.type == FeatureType.INACTIVEFAULT:
@@ -9,9 +9,11 @@ from LoopStructural.utils._api_registry import public_api
9
9
 
10
10
  logger = getLogger(__name__)
11
11
  logger.info("Imported LoopStructural Stratigraphic Column module")
12
+
13
+
12
14
  class UnconformityType(enum.Enum):
13
15
  """Enumeration for different types of unconformities in a stratigraphic column.
14
-
16
+
15
17
  Attributes
16
18
  ----------
17
19
  ERODE : str
@@ -26,7 +28,7 @@ class UnconformityType(enum.Enum):
26
28
 
27
29
  class StratigraphicColumnElementType(enum.Enum):
28
30
  """Enumeration for different types of elements in a stratigraphic column.
29
-
31
+
30
32
  Attributes
31
33
  ----------
32
34
  UNIT : str
@@ -41,8 +43,8 @@ class StratigraphicColumnElementType(enum.Enum):
41
43
 
42
44
  class StratigraphicColumnElement:
43
45
  """Base class for elements in a stratigraphic column.
44
-
45
- This class represents a generic element that can be either a stratigraphic unit
46
+
47
+ This class represents a generic element that can be either a stratigraphic unit
46
48
  or a topological object such as an unconformity.
47
49
 
48
50
  Parameters
@@ -132,6 +134,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
132
134
  self.id = id
133
135
  self.min_value = None # Minimum scalar field value for the unit
134
136
  self.max_value = None # Maximum scalar field value for the unit
137
+
135
138
  @property
136
139
  def id(self):
137
140
  """Get the numeric identifier of the unit.
@@ -142,7 +145,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
142
145
  The numeric identifier
143
146
  """
144
147
  return self._id
145
-
148
+
146
149
  @property
147
150
  def thickness(self):
148
151
  """Get the thickness of the unit.
@@ -153,7 +156,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
153
156
  The thickness value
154
157
  """
155
158
  return self._thickness
156
-
159
+
157
160
  @thickness.setter
158
161
  def thickness(self, value):
159
162
  """Set the thickness of the unit and notify observers.
@@ -165,7 +168,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
165
168
  """
166
169
  self._thickness = value
167
170
  self.notify('unit/thickness_updated', unit=self)
168
-
171
+
169
172
  @id.setter
170
173
  def id(self, value):
171
174
  """Set the ID of the unit and notify observers.
@@ -179,6 +182,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
179
182
  raise TypeError("ID must be an integer")
180
183
  self._id = value
181
184
  self.notify('unit/id_updated', unit=self)
185
+
182
186
  def min(self):
183
187
  """Return the minimum scalar field value of the unit.
184
188
 
@@ -188,7 +192,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
188
192
  Minimum value, or 0 if not set
189
193
  """
190
194
  return self.min_value if self.min_value is not None else 0
191
-
195
+
192
196
  def max(self):
193
197
  """Return the maximum scalar field value of the unit.
194
198
 
@@ -198,7 +202,7 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
198
202
  Maximum value, or infinity if not set
199
203
  """
200
204
  return self.max_value if self.max_value is not None else np.inf
201
-
205
+
202
206
  def to_dict(self):
203
207
  """Convert the stratigraphic unit to a dictionary representation.
204
208
 
@@ -210,7 +214,13 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
210
214
  colour = self.colour
211
215
  if isinstance(colour, np.ndarray):
212
216
  colour = colour.astype(float).tolist()
213
- return {"name": self.name, "colour": colour, "thickness": self.thickness, 'uuid': self.uuid, 'id': self.id}
217
+ return {
218
+ "name": self.name,
219
+ "colour": colour,
220
+ "thickness": self.thickness,
221
+ 'uuid': self.uuid,
222
+ 'id': self.id,
223
+ }
214
224
 
215
225
  @classmethod
216
226
  def from_dict(cls, data):
@@ -237,7 +247,9 @@ class StratigraphicUnit(StratigraphicColumnElement, Observable['StratigraphicUni
237
247
  colour = data.get("colour")
238
248
  thickness = data.get("thickness", None)
239
249
  uuid = data.get("uuid", None)
240
- return cls(uuid=uuid, name=name, colour=colour, thickness=thickness, id=data.get("id", None))
250
+ return cls(
251
+ uuid=uuid, name=name, colour=colour, thickness=thickness, id=data.get("id", None)
252
+ )
241
253
 
242
254
  def __str__(self):
243
255
  """Return a string representation of the stratigraphic unit.
@@ -303,9 +315,11 @@ class StratigraphicUnconformity(StratigraphicColumnElement):
303
315
  )
304
316
  uuid = data.get("uuid", None)
305
317
  return cls(uuid=uuid, name=name, unconformity_type=unconformity_type)
318
+
319
+
306
320
  class StratigraphicGroup:
307
321
  """A class representing a group of stratigraphic units.
308
-
322
+
309
323
  This class serves as a container for related stratigraphic units and provides
310
324
  a placeholder for future group-level functionality.
311
325
 
@@ -340,9 +354,9 @@ class StratigraphicGroup:
340
354
 
341
355
  class StratigraphicColumn(Observable['StratigraphicColumn']):
342
356
  """A class representing a stratigraphic column.
343
-
344
- The stratigraphic column represents a vertical section of the Earth's crust
345
- showing the sequence of rock layers and their relationships, including
357
+
358
+ The stratigraphic column represents a vertical section of the Earth's crust
359
+ showing the sequence of rock layers and their relationships, including
346
360
  unconformities and basement rock.
347
361
 
348
362
  Attributes
@@ -372,18 +386,16 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
372
386
  if not self.order:
373
387
  return 0
374
388
  return max([u.id for u in self.order if isinstance(u, StratigraphicUnit)], default=0) + 1
375
-
389
+
376
390
  def add_basement(self):
377
391
  """Add basement unit and base unconformity to the stratigraphic column.
378
-
392
+
379
393
  This method adds the fundamental basement unit with infinite thickness
380
394
  and an erosional unconformity at the base of the column.
381
395
  """
382
396
  self.add_unit(name='Basement', colour='grey', thickness=np.inf)
383
- self.add_unconformity(
384
- name='Base Unconformity', unconformity_type=UnconformityType.ERODE
385
- )
386
-
397
+ self.add_unconformity(name='Base Unconformity', unconformity_type=UnconformityType.ERODE)
398
+
387
399
  def clear(self, basement=True):
388
400
  """Clear the stratigraphic column, removing all elements.
389
401
 
@@ -397,7 +409,8 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
397
409
  if basement:
398
410
  self.add_basement()
399
411
  self.notify('column_cleared')
400
- def add_unit(self, name,*, colour=None, thickness=None, where='top',id=None):
412
+
413
+ def add_unit(self, name, *, colour=None, thickness=None, where='top', id=None):
401
414
  if id is None:
402
415
  id = self.get_new_id()
403
416
  unit = StratigraphicUnit(name=name, colour=colour, thickness=thickness, id=id)
@@ -425,7 +438,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
425
438
 
426
439
  return False
427
440
 
428
- def add_unconformity(self, name, *, unconformity_type=UnconformityType.ERODE, where='top' ):
441
+ def add_unconformity(self, name, *, unconformity_type=UnconformityType.ERODE, where='top'):
429
442
  unconformity = StratigraphicUnconformity(
430
443
  uuid=None, name=name, unconformity_type=unconformity_type
431
444
  )
@@ -466,6 +479,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
466
479
  return unconformity
467
480
 
468
481
  return None
482
+
469
483
  def get_element_by_uuid(self, uuid):
470
484
  """
471
485
  Retrieves an element by its uuid from the stratigraphic column.
@@ -475,7 +489,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
475
489
  return element
476
490
  raise KeyError(f"No element found with uuid: {uuid}")
477
491
 
478
- def get_group_for_unit_name(self, unit_name:str) -> StratigraphicGroup | None:
492
+ def get_group_for_unit_name(self, unit_name: str) -> StratigraphicGroup | None:
479
493
  """
480
494
  Retrieves the group for a given unit name.
481
495
  """
@@ -483,6 +497,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
483
497
  if any(unit.name == unit_name for unit in group.units):
484
498
  return group
485
499
  return None
500
+
486
501
  def add_element(self, element):
487
502
  """
488
503
  Adds a StratigraphicColumnElement to the stratigraphic column.
@@ -500,27 +515,22 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
500
515
 
501
516
  def get_groups(self):
502
517
  groups = []
503
- i=0
504
- group = StratigraphicGroup(
505
- name=(
506
- self.group_mapping.get(f'Group_{i}', f'Group_{i}')
507
- )
508
- )
518
+ i = 0
519
+ group = StratigraphicGroup(name=(self.group_mapping.get(f'Group_{i}', f'Group_{i}')))
509
520
  for e in reversed(self.order):
510
521
  if isinstance(e, StratigraphicUnit):
511
522
  group.units.append(e)
512
523
  else:
513
524
  if group.units:
514
525
  groups.append(group)
515
- i+=1
526
+ i += 1
516
527
  group = StratigraphicGroup(
517
- name=(
518
- self.group_mapping.get(f'Group_{i}', f'Group_{i}')
519
- )
528
+ name=(self.group_mapping.get(f'Group_{i}', f'Group_{i}'))
520
529
  )
521
530
  if group:
522
531
  groups.append(group)
523
532
  return groups
533
+
524
534
  def get_stratigraphic_ids(self) -> list[list[str]]:
525
535
  ids = []
526
536
  for group in self.get_groups():
@@ -530,6 +540,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
530
540
  for unit in group.units:
531
541
  ids.append([unit.id, group, unit.name, unit.min(), unit.max()])
532
542
  return ids
543
+
533
544
  def get_unitname_groups(self):
534
545
  groups = self.get_groups()
535
546
  groups_list = []
@@ -539,7 +550,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
539
550
  groups_list.append(group)
540
551
  return groups_list
541
552
 
542
- def get_group_unit_pairs(self) -> list[tuple[str,str]]:
553
+ def get_group_unit_pairs(self) -> list[tuple[str, str]]:
543
554
  """
544
555
  Returns a list of tuples containing group names and unit names.
545
556
  """
@@ -571,7 +582,10 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
571
582
  ]
572
583
  self.notify('order_updated', new_order=self.order)
573
584
  self.update_unit_values() # Update min and max values after updating the order
574
- def update_unit_values(self, observable: Observable | None = None, event: str | None = None, **kwargs):
585
+
586
+ def update_unit_values(
587
+ self, observable: Observable | None = None, event: str | None = None, **kwargs
588
+ ):
575
589
  """
576
590
  Updates the min and max values for each unit based on their position in the column.
577
591
 
@@ -628,6 +642,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
628
642
  return {
629
643
  "elements": [element.to_dict() for element in self.order],
630
644
  }
645
+
631
646
  def update_from_dict(self, data):
632
647
  """
633
648
  Updates the stratigraphic column from a dictionary representation.
@@ -643,6 +658,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
643
658
  else:
644
659
  element = StratigraphicUnit.from_dict(element_data)
645
660
  self.add_element(element)
661
+
646
662
  @classmethod
647
663
  def from_dict(cls, data):
648
664
  """
@@ -661,23 +677,37 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
661
677
  column.add_element(element)
662
678
  return column
663
679
 
664
- def get_isovalues(self) -> dict[str, float]:
680
+ def get_isovalues(self, where: str = 'bottom') -> dict[str, float]:
665
681
  """
666
682
  Returns a dictionary of isovalues for the stratigraphic units in the column.
683
+
684
+ Parameters
685
+ ----------
686
+ where : str, optional
687
+ 'bottom' (default) returns the value at the base of each unit.
688
+ 'top' returns the value at the top of each unit.
667
689
  """
690
+ if where not in ('top', 'bottom'):
691
+ raise ValueError("Invalid 'where' argument. Use 'top' or 'bottom'.")
668
692
  surface_values = {}
669
693
  for g in reversed(self.get_groups()):
670
694
  v = 0
671
- for u in g.units:
672
- surface_values[u.name] = {'value':v,'group':g.name,'colour':u.colour}
695
+ for u in reversed(g.units):
696
+ base = v
673
697
  v += u.thickness
698
+ surface_values[u.name] = {
699
+ 'value': v if where == 'top' else base,
700
+ 'group': g.name,
701
+ 'colour': u.colour,
702
+ }
674
703
  return surface_values
675
704
 
676
- def plot(self,*, ax=None, **kwargs):
705
+ def plot(self, *, ax=None, **kwargs):
677
706
  import matplotlib.pyplot as plt
678
707
  from matplotlib import cm
679
708
  from matplotlib.collections import PatchCollection
680
709
  from matplotlib.patches import Polygon
710
+
681
711
  n_units = 0 # count how many discrete colours (number of stratigraphic units)
682
712
  xmin = 0
683
713
  ymin = 0
@@ -712,7 +742,17 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
712
742
  n_units += 1
713
743
 
714
744
  ymax = total_height
715
- ymin = ymax - (getattr(u, 'thickness', np.nan) if not np.isinf(getattr(u, 'thickness', np.nan)) else np.nanmean([getattr(e, 'thickness', np.nan) for e in self.order if not np.isinf(getattr(e, 'thickness', np.nan))]))
745
+ ymin = ymax - (
746
+ getattr(u, 'thickness', np.nan)
747
+ if not np.isinf(getattr(u, 'thickness', np.nan))
748
+ else np.nanmean(
749
+ [
750
+ getattr(e, 'thickness', np.nan)
751
+ for e in self.order
752
+ if not np.isinf(getattr(e, 'thickness', np.nan))
753
+ ]
754
+ )
755
+ )
716
756
 
717
757
  if not np.isfinite(ymin):
718
758
  ymin = prev_coords[1] - (prev_coords[1] - prev_coords[0]) * (1 + rng.random())
@@ -723,7 +763,12 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
723
763
 
724
764
  polygon_points = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]])
725
765
  patches.append(Polygon(polygon_points))
726
- ax.annotate(getattr(u, 'name', 'Unknown'), xy=(xmin+(xmax-xmin)/2, (ymax-ymin)/2+ymin), fontsize=8, ha='left')
766
+ ax.annotate(
767
+ getattr(u, 'name', 'Unknown'),
768
+ xy=(xmin + (xmax - xmin) / 2, (ymax - ymin) / 2 + ymin),
769
+ fontsize=8,
770
+ ha='left',
771
+ )
727
772
 
728
773
  if 'cmap' not in kwargs:
729
774
  from matplotlib import colors
@@ -752,7 +797,7 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
752
797
  ax.axis("off")
753
798
 
754
799
  return fig
755
-
800
+
756
801
  def cmap(self):
757
802
  try:
758
803
  from matplotlib import colors
@@ -772,9 +817,9 @@ class StratigraphicColumn(Observable['StratigraphicColumn']):
772
817
  data.append((u.id, u.colour))
773
818
  colours.append(u.colour)
774
819
  boundaries.append(u.id)
775
- # print(u,v)
820
+ # print(u,v)
776
821
  cmap = colors.ListedColormap(colours)
777
822
  except ImportError:
778
823
  logger.warning("Cannot use predefined colours as I can't import matplotlib")
779
824
  cmap = "tab20"
780
- return cmap
825
+ return cmap
@@ -340,8 +340,17 @@ class BaseFeature(metaclass=ABCMeta):
340
340
  regions = self.regions
341
341
 
342
342
  try:
343
+ # A region can also be a plain callable (e.g. the lambdas
344
+ # add_domain_fault_below/above attach for a domain-fault crop),
345
+ # not just another BaseFeature -- those have no .name/.parent,
346
+ # so getattr(..., None) rather than direct attribute access,
347
+ # or this raises AttributeError and silently drops every
348
+ # surface for any feature cropped by a domain fault.
343
349
  self.regions = [
344
- r for r in self.regions if r.name != self.name and r.parent.name != self.name
350
+ r
351
+ for r in self.regions
352
+ if getattr(r, 'name', None) != self.name
353
+ and getattr(getattr(r, 'parent', None), 'name', None) != self.name
345
354
  ]
346
355
 
347
356
  callable = lambda xyz: self.evaluate_value(xyz)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LoopStructural
3
- Version: 1.8.0
3
+ Version: 1.8.2
4
4
  Summary: 3D geological modelling
5
5
  Author-email: Lachlan Grose <lachlan.grose@monash.edu>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LoopStructural
3
- Version: 1.8.0
3
+ Version: 1.8.2
4
4
  Summary: 3D geological modelling
5
5
  Author-email: Lachlan Grose <lachlan.grose@monash.edu>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
4
4
 
5
5
  [project]
6
6
  name = 'LoopStructural'
7
- version = "1.8.0"
7
+ version = "1.8.2"
8
8
  description = '3D geological modelling'
9
9
  authors = [{ name = 'Lachlan Grose', email = 'lachlan.grose@monash.edu' }]
10
10
  readme = 'README.md'
File without changes
File without changes
File without changes
File without changes